Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tmp2000/fd631a4a73ef85acc22920e624bd3c23 to your computer and use it in GitHub Desktop.
Save tmp2000/fd631a4a73ef85acc22920e624bd3c23 to your computer and use it in GitHub Desktop.
osTicket 1.7.2+ MOD Archive Tickets. Modifies the delete ticket function to first create a PDF export of the file, includes a cron-runnable script to auto-prune the db!
<?php
/** MOD_Archive START COPY FROM THIS LINE, paste beneath /main.inc.php " require(INCLUDE_DIR.'mysql.php');"
* Which subfolder would you like to start archiving tickets into?
*/
define ( 'BACKUPDIR', ROOT_DIR . 'backups' . DIRECTORY_SEPARATOR );
/**
* Change to false to show more output, makes cron send emails which you may not care about.
*/
define ( 'CRONFRIENDLY', true );
/**
* Change to true to allow anyone on the internet to initiate..
* not recommended unless you have a remote-cron requirement.
*/
define ( 'REMOTECRONENABLED', false );
/**
* Where do you want your temporary files stored..
* could be /tmp/ostickets/ or anywhere writable by the user.
*/
define ( 'URI_FOLDER', ROOT_DIR . 'scp' . DIRECTORY_SEPARATOR . 'restricted' );
/**
* Clean up the temp directory? Do you not have a cron-job checking this folder for old files and cleaning it up?
* Hmm.. I guess you wouldn't.. I do, but I use these for in-line attachments for PDF previewing.. maybe you don't.
*/
define( 'DELETE_TEMPS_AFTER_USE', true);
require_once(INCLUDE_DIR.'class.mod_archive.php');
<?php
/**
* Removes obsolete tickets, can be run as often as you choose.
* Ideally via cron:
# Every 28 days?
* * /28 * * nobody /usr/bin/php /path/to/remove_obsolete_tickets.php
*
* Currently only tested/designed for linux, specifically Debian, but should work on others.
* Requires Ghostscript (gs) installed for PDF merging to work.
*
* @author Grizly
*
*
* YOU WILL NEED TO CONFIGURE THE FOLLLOWING:
*
* Specify your osTicket username, probably best to use an Admins username for this. DO NOT NEED PASSWORD.
*/
define ( 'MY_USER_ID', 'Grizly' );
/**
* Any closed ticket older than this many months will be archived/deleted.
*/
define ( 'DELETEMONTHS', 1 );
define ( 'DEBUG' , false); //enable to show all errors while running.. useful if you change things.
// This starts osTicket code
require_once 'main.inc.php';
//re-enable all error displays.
if(DEBUG){
error_reporting(E_ALL); //Respect whatever is set in php.ini (sysadmin knows better??)
#display errors
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
}
// Only required if you are archiving.. the PDF printer needs a userobject global to function correctly.
$thisstaff = new Staff ( MY_USER_ID );
// select old messages.. we probably don't need any of them where we're going.
$select = 'SELECT ticket_id FROM ' . TICKET_TABLE . " WHERE status='closed' AND closed < DATE_SUB(NOW(), INTERVAL " . DELETEMONTHS . ' MONTH)';
// Begin
$res = db_query ( $select );
if ($res && $num = db_num_rows ( $res )) {
$messages = db_assoc_array ( $res, true );
if ($messages && ! CRONFRIENDLY)
print "Found $num matching deletables!\nDeleting messages older than " . DELETEMONTHS . " months, using " . BACKUPDIR . " for archives.\n";
foreach ( $messages as $ticket ) {
$t = new Ticket($ticket['ticket_id']);
if(DEBUG)
$t->archive();
else
$t->delete();
}
} elseif (! CRONFRIENDLY) {
print "No matching tickets found, either expand the months to include more tickets, create some and change the dates, or ignore this as it probably isn't an actual error.";
}
<?php
/**
* Removes obsolete tickets, can be run as often as you choose.
*
* I've added a MOD to hijack the $ticket->delete() function, which first archives a ticket.. ;-)
*
* Ideally via cron:
# Every 28 days?
* * /28 * * nobody /usr/bin/php /path/to/remove_obsolete_tickets.php
*
* Currently only tested/designed for linux, specifically Debian, but should work on others.
* Requires Ghostscript (gs) installed for PDF merging to work.
*
* @author Grizly
*
*
* YOU WILL NEED TO CONFIGURE THE FOLLLOWING:
*
* Specify your osTicket username, probably best to use an Admins username for this. DO NOT NEED PASSWORD.
*/
// determine if gs (ghostscript) is installed and accessible to php process. (Yet another reason to run in CLI mode)
// Unfortunately, when processing some broken PDF's, you will see some errors even with CRONFRIENDLY enabled.
define ( 'PDF_MERGE_ENABLED', (is_readable ( exec ( 'which gs' ) )) );
// png->jpg converter requires GD
define ( 'GD_ENABLED', (function_exists ( 'imagecreatefrompng' )) );
define ( 'DPI', 64 );
define ( 'MM_IN_INCH', 25.4 );
define ( 'A4_WIDTH', 297 );
define ( 'A4_HEIGHT', 210 );
define ( 'MAX_HEIGHT', 800 );
define ( 'MAX_WIDTH', 500 );
class MOD_Archive_Ticket {
public $isArchived;
private $t, $outputfile, $name;
/**
* Create an Archive of a ticket in PDF format, based on the department.
*
* @param
* osTicket Ticket object $ticket
*/
function MOD_Archive_Ticket($ticket) {
$this->t = $ticket;
// Create an output file for the exported PDF.
// Add department subfolder name
$folder = BACKUPDIR . $ticket->getDeptName () . DIRECTORY_SEPARATOR;
// Department name might have a space in it.. this apparently b0rks the pdf merger
$folder = str_replace ( ' ', '_', $folder );
// Department might be new, or we haven't made a folder for it yet.
if (! is_writable ( $folder ))
mkdir ( $folder, 0777, true );
if (! is_writable ( $folder ))
throw new Exception ( "Permissions problem with the folder.. fix ASAP" );
// Create a filename for the PDF
$name = 'Ticket-' . $ticket->getExtId ();
$name .= ' ' . $ticket->getSubject ();
// if you don't have getOrderNumber, its because you didn't modify class.tickets.php to create it, good for you!
if (method_exists ( $ticket, 'getOrderNumber' ))
$on = $ticket->getOrderNumber ();
else
$on = false;
$name .= ($on) ? ' Order-' . $on : '';
// Some subjects may contain names that are unsuitable for filenames, and would therefore break the script, or the filesystem :-(
// currently set to remove all non-alphanumerics.
$name = $this->sanitize ( $name, False, False );
// Put the PDF's name at the end of the filename, this is the final filename.
$this->outputfile = $folder . $name . '.pdf';
if (is_readable ( $this->outputfile )) {
// we already have the file, lets just proceed straight to deleting the ticket
$this->isArchived = true;
} else {
$this->isArchived = false;
}
$this->name = $name;
}
/**
* Convert the ticket into a PDF, much like the normal PDF function, except, this includes all attachments!
*
* @return boolean
*/
public function export_ticket_to_PDF() {
if ($this->isArchived) {
return true;
}
$ticket = $this->t;
if ($ticket->isOpen () || ! $ticket->getCloseDate ()) {
throw new Exception ( "Unable to delete open tickets!" );
}
$name = $this->name;
// Create a PDF object to represent the ticket.
$pdf = new Ticket2PDF ( $ticket, 'A4', true ); // specifying TRUE will save all notes/responses & messages to the start of the PDF.
// An array of pdf's which will be merged together with the created one at the end.
$merge_queue = array ();
// Append any attachments to the pdf.. ;-)
// Start by getting the tickets thread of messages.
$thread = $ticket->getThread ();
// Find all the attachments to the thread.
if ($total = $thread->getNumAttachments ()) {
if (! CRONFRIENDLY)
print "Found $total Attachments for $name!\n ";
// Attachments are actually stored in relation to the messages in the thread, not the ticket.
// So, get the entries to the thread themselves
$thread = $ticket->getThreadEntries ( array ('M','R','N' ) ); // Specify Notes, Messages and Responses.. all entries!
foreach ( $thread as $entry ) {
// This line almost word-for-word copied from the normal ticket-view page for staff.
if ($entry ['attachments'] && ($tentry = $ticket->getThreadEntry ( $entry ['id'] )) && ($attachments = $tentry->getAttachments ())) {
foreach ( $attachments as $attachment ) {
// By default, saves the file into a filesystem addressable container.. this means we can import it into the PDF
// Note, this $attachment, is simply an array at this point.
$attached_file = new Export_Parse_Attachment_File ( $attachment );
if (! CRONFRIENDLY)
print ("Attachment is a " . $attached_file->type . " file. \n") ;
switch ($attached_file->type) {
case 'pdf' :
{
$merge_queue [] = $attached_file->uri;
break;
}
case 'text' :
{
// Text/HTML can be added as a Cell of text. We don't even need a new page for it! (Should flow into a new page if required)
$pdf->Cell ( 0, 0, $attached_file->str, 0, 1, 'C' );
break;
}
case 'image' :
{
if (strlen ( $attached_file->uri ) && is_readable ( $attached_file->uri ) && filesize ( $attached_file->uri ) > 10) { // skip empty uris.. christ.
@$pdf->AddPage ();
if (! CRONFRIENDLY)
print "Creating new PDF page..\n";
// Looks better if the image is zoomed in, centered and such.
$this->centreImage ( $pdf, $attached_file->uri, $attached_file->ext );
}
break;
}
default :
if (! CRONFRIENDLY)
print ("Unable to merge attachment: " . $attached_file->uri . ' As its type was not set') ;
}
} // end foreach
} // endif
} // endforeach
} elseif (! CRONFRIENDLY) {
print "Didn't find any attachments.\n";
}
// Initiate the actual output of the PDF including our text-based and image-inserted additions.
$pdf->Output ( $this->outputfile, 'F' );
// Begin appending existing PDF attachments to the end of the ticket PDF.
if (count ( $merge_queue ) > 0 && PDF_MERGE_ENABLED) {
if (! CRONFRIENDLY)
print ("Merging PDFs! \n") ;
// We have some PDF's that need to be appended to the generated file.
$merge_me = $this->outputfile;
// Create list of pdf filenames to merge together as a string
foreach ( $merge_queue as $file ) {
if (is_readable ( $file ) && filesize ( $file ) > 10)
$merge_me .= ' ' . $file;
}
MOD_Archive_Ticket::mergePDFS ( $merge_me, $this->outputfile );
} else {
if (! CRONFRIENDLY)
print "No mergeable pdfs found. \n";
}
// Reset time & permissions on file.
$this->reDate ( $this->outputfile );
$this->isArchived = true;
}
/**
* From <a href="http://stackoverflow.com/a/2668953">StackOverflow</a>
*
* Function: sanitize
* Returns a sanitized string, typically for URLs.
*
* Parameters:
* $string - The string to sanitize.
* $force_lowercase - Force the string to lowercase?
* $anal - If set to *true*, will remove all non-alphanumeric characters.
*
* This is used to allow things like the subject line as a part of the tickets filename in the export, makes it much quicker to re-locate after archiving.
*/
private static function sanitize($string, $force_lowercase = true, $anal = false) {
$strip = array ("~","`","!","@","#","$","%","^","&","*","(",")","_","=","+","[","{","]","}","\\","|",";",":","\"","'","&#8216;","&#8217;","&#8220;","&#8221;","&#8211;","&#8212;","—","–",",","<",".",">","/","?" );
$clean = trim ( str_replace ( $strip, "", strip_tags ( $string ) ) );
$clean = preg_replace ( '/\s+/', "-", $clean );
$clean = ($anal) ? preg_replace ( "/[^a-zA-Z0-9]/", "", $clean ) : $clean;
return ($force_lowercase) ? (function_exists ( 'mb_strtolower' )) ? mb_strtolower ( $clean, 'UTF-8' ) : strtolower ( $clean ) : $clean;
}
/**
* From: <a href="http://www.php.net/manual/en/function.pcntl-setpriority.php#48430">http://www.php.net/manual/en/function.pcntl-setpriority.php#48430</a>
*
* Possibly unnecessary now.
*
* @param int $priority
* @param string $pid
* @return boolean
*/
private function _pcntl_setpriority($priority, $pid = false) {
$pid = ($pid) ? $pid : getmypid ();
$priority = ( int ) $priority;
$pid = ( int ) $pid;
// check if out of bounds
if ($priority > 20 && $priority < - 20) {
return False;
}
if ($pid == 0) {
$pid = getmypid ();
}
// check if already set.
if ($priority == pcntl_getpriority ( $pid ))
return true;
return exec ( "renice $priority -p $pid" ) != false;
}
/**
* Calculates the correct height/width in MM for the DPI and returns to centreImage.
* * From: <a href="https://gist.github.com/benshimmin/4088493">BenShimmin gisthub.com</a>
*
* @param string $imgFilename
* @return multitype:number
*
*/
static private function resizeToFit($imgFilename) {
list ( $width, $height ) = getimagesize ( $imgFilename );
if (! $width)
$width = 500;
if (! $height)
$height = 800;
$widthScale = MAX_WIDTH / $width;
$heightScale = MAX_HEIGHT / $height;
$scale = min ( $widthScale, $heightScale );
return array (round ( ($scale * $width) * MM_IN_INCH / DPI ),round ( ($scale * $height) * MM_IN_INCH / DPI ) );
}
/**
* Centers an image on a page, useful for large images embedded within a pdf page..
* ;-)
*
* From: <a href="https://gist.github.com/benshimmin/4088493">BenShimmin gisthub.com</a>
* Modified for this, removed a few function-calls, and de-OOP'd
*
* @param Ticket2PDF $pdf
* @param String $img
* the full path to the file
* @param String $ext
* extension of image.
*/
static private function centreImage(&$pdf, $img, $ext) {
list ( $width, $height ) = MOD_Archive_Ticket::resizeToFit ( $img );
// you will probably want to swap the width/height
// around depending on the page's orientation
$pdf->Image ( $img, (A4_HEIGHT - $width) / 2, (A4_WIDTH - $height) / 2, $width, $height, $ext );
}
/**
* From: <a href="http://www.linux.com/news/software/applications/8229-putting-together-pdf-files">linux.com</a>
*
* Uses Ghostscript to combine PDF files, something like the following:
*
* gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=finished.pdf file1.pdf file2.pdf
*
* Unless you're very familiar with Ghostscript, that string of commands won't mean much to you. Here's a quick breakdown:
* gs -- starts the Ghostscript program
* -dBATCH -- once Ghostscript processes the PDF files, it should exit. If you don't include this option, Ghostscript will just keep running
* -dNOPAUSE -- forces Ghostscript to process each page without pausing for user interaction
* -q -- stops Ghostscript from displaying messages while it works
* -sDEVICE=pdfwrite -- tells Ghostscript to use its built-in PDF writer to process the files
* -sOutputFile=finished.pdf -- tells Ghostscript to save the combined PDF file with the name that you specified
*
* @param String $pdfs
* @param String $outputname
* @throws Exception
*/
static private function mergePDFS($pdfs, $outputname) {
if ($pdfs && $outputname) {
// Create temporary file to merge them into
$tmpfile = tempnam ( '/tmp', 'pdfmerge_' );
// let GhostScript do the merge, fast!
system ( "gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=$tmpfile $pdfs" );
// Rename merged file (this will move as well) back to the original file, overwriting it.
rename ( $tmpfile, $outputname );
} else {
throw new Exception ( "incorrect argument, no system call for you!" );
}
}
/**
* Change the file modification time to match the date on the ticket..
* makes it easier to locate a ticket from 3 weeks ago etc.
*
* @param String $this->outputfile
* @throws Exception
*/
private function reDate() {
if (is_writable ( $this->outputfile ) && $d = strtotime ( $this->t->getCloseDate () )) {
if (! CRONFRIENDLY)
print "New timestamp for $this->outputfile = $d";
touch ( $this->outputfile, $d );
// oddly, it was only setting 600 permissions on most files, some had 622.. don't know why.
// I was immediately moving these to the fileserver, which of course, locked them to me!
chmod ( $this->outputfile, 0666 );
} else {
throw new Exception ( "Unable to modify file we just created!" );
}
}
}
/**
* Basically an attachment type checker..
*
* But downloads the attachment itself into a named file, maintains an association between the attachment_id and the URI for the file.. etc.
*
* If file is text based, contains the string representing the contents of the file instead.
*
* @author Grizly
*
*/
class Export_Parse_Attachment_File {
public $uri, $str, $type, $ext;
function Export_Parse_Attachment_File($attachment) {
$this->uri = array ();
$this->str = '';
$id = $attachment ['attach_id'];
// Make the folder if it doesn't exist.
$dir = URI_FOLDER;
if (! is_dir ( $dir )) {
error_log ( "Making dir: $dir" );
mkdir ( $dir );
}
// Locate osTickets attachment object for this id
$a = Attachment::lookup ( $id );
// Get the file object itself, which is also an osTicket object/db entity etc.
$f = $a->getFile ();
// Route by attachment I wish I could remember where I found this majestic bitty.. ;-)
$this->ext = $ext = strtolower ( substr ( strrchr ( $f->getName (), '.' ), 1 ) );
$filename = $dir . DIRECTORY_SEPARATOR . "t_$id.$ext";
switch ($ext) {
case 'pdf' :
{
if (file_exists ( $filename ) != TRUE) {
file_put_contents ( $filename, $f->getData () );
}
$this->uri = $filename;
$this->type = 'pdf';
break;
}
case 'txt' :
case 'htm' :
case 'html' :
{
$this->type = 'text';
$this->str = ($ext == 'txt') ? '<pre>' . $f->getData () . '</pre>' : Format::safe_html ( $f->getData () );
// we don't set the URI here, as we are using the string value itself. No need to create a file.
$this->uri = false;
break;
}
case 'png' :
{
if (! GD_ENABLED)
break;
// gotta save the file first.. doh!
if (file_exists ( $filename ) != TRUE) {
file_put_contents ( $filename, $f->getData () );
}
// PDF doesn't support alpha channels for some reason, so we need to quickly and painlessly remove them.
$tmp = tempnam ( '/tmp/osticket', '_png_converter_' );
$jpeg_version = $this->png2jpg ( $filename, $tmp );
// need to change its file-extension to match its new colorful format ;-)
$this->ext = $ext = 'jpg';
$filename = str_replace ( '.png', '.jpg', $filename );
rename ( $tmp, $filename );
// don't break, let the image section continue to work.
}
case 'jpg' :
case 'gif' :
case 'jpeg' :
case 'tif' :
{
$this->type = 'image';
if (file_exists ( $filename ) != TRUE) {
file_put_contents ( $filename, $f->getData () );
}
$this->uri = $filename;
break;
}
case 'doc' :
case 'docx' : // stupid word docs
case 'xls' :
case 'xlsx' : // stupid excel docs
{
$this->type = 'text';
if (file_exists ( $filename ) != TRUE) {
file_put_contents ( $filename, $f->getData () );
}
// We don't really have time to format this shite, but we can at least grok some of the contents.. ;-)
$docObj = new DocxConversion ( $filename );
$this->str = $docObj->convertToText ();
break;
}
default :
}
}
/**
* I'm starting to wonder how much is original code, and how much I just ripped off from StackOverflow!
*
* from: <a href="http://stackoverflow.com/a/1201823/276663">StackOverflow!</a>
*
* @param string $originalFile
* The full path to the PNG file.
* @param string $this->outputfile
* where does he go?
* @param int $quality
* (default should be 75 on imagejpeg anyway)
*/
private function png2jpg($originalFile, $outputfile, $quality = 75) {
$image = imagecreatefrompng ( $originalFile );
imagejpeg ( $image, $outputfile, $quality );
imagedestroy ( $image );
}
}
/**
* Here is simple and easily understandable class which does the right job for .
*
* doc/.docx , PHP docx reader: Convert MS Word Docx files to text.
* http://www.phpclasses.org/package/7934-PHP-Convert-MS-Word-Docx-files-to-text.html
* http://stackoverflow.com/a/19503654
*/
class DocxConversion {
private $filename;
public function __construct($filePath) {
$this->filename = $filePath;
}
private function read_doc() {
$fileHandle = fopen ( $this->filename, "r" );
$line = @fread ( $fileHandle, filesize ( $this->filename ) );
$lines = explode ( chr ( 0x0D ), $line );
$outtext = "";
foreach ( $lines as $thisline ) {
$pos = strpos ( $thisline, chr ( 0x00 ) );
if (($pos !== FALSE) || (strlen ( $thisline ) == 0)) {
} else {
$outtext .= $thisline . " ";
}
}
$outtext = preg_replace ( "/[^a-zA-Z0-9\s\,\.\-\n\r\t@\/\_\(\)]/", "", $outtext );
return $outtext;
}
private function read_docx() {
$striped_content = '';
$content = '';
$zip = zip_open ( $this->filename );
if (! $zip || is_numeric ( $zip ))
return false;
while ( $zip_entry = zip_read ( $zip ) ) {
if (zip_entry_open ( $zip, $zip_entry ) == FALSE)
continue;
if (zip_entry_name ( $zip_entry ) != "word/document.xml")
continue;
$content .= zip_entry_read ( $zip_entry, zip_entry_filesize ( $zip_entry ) );
zip_entry_close ( $zip_entry );
} // end while
zip_close ( $zip );
$content = str_replace ( '</w:r></w:p></w:tc><w:tc>', " ", $content );
$content = str_replace ( '</w:r></w:p>', "\r\n", $content );
$striped_content = strip_tags ( $content );
return $striped_content;
}
/**
* **********************excel sheet***********************************
*/
private function xlsx_to_text($input_file) {
$xml_filename = "xl/sharedStrings.xml"; // content file name
$zip_handle = new ZipArchive ();
$output_text = "";
if (true === $zip_handle->open ( $input_file )) {
if (($xml_index = $zip_handle->locateName ( $xml_filename )) !== false) {
$xml_datas = $zip_handle->getFromIndex ( $xml_index );
$xml_handle = DOMDocument::loadXML ( $xml_datas, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING );
$output_text = strip_tags ( $xml_handle->saveXML () );
} else {
$output_text .= "";
}
$zip_handle->close ();
} else {
$output_text .= "";
}
return $output_text;
}
/**
* ***********************power point files****************************
*/
private function pptx_to_text($input_file) {
$zip_handle = new ZipArchive ();
$output_text = "";
if (true === $zip_handle->open ( $input_file )) {
$slide_number = 1; // loop through slide files
while ( ($xml_index = $zip_handle->locateName ( "ppt/slides/slide" . $slide_number . ".xml" )) !== false ) {
$xml_datas = $zip_handle->getFromIndex ( $xml_index );
$xml_handle = DOMDocument::loadXML ( $xml_datas, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING );
$output_text .= strip_tags ( $xml_handle->saveXML () );
$slide_number ++;
}
if ($slide_number == 1) {
$output_text .= "";
}
$zip_handle->close ();
} else {
$output_text .= "";
}
return $output_text;
}
public function convertToText() {
if (isset ( $this->filename ) && ! file_exists ( $this->filename )) {
return "File Not exists";
}
$fileArray = pathinfo ( $this->filename );
$file_ext = $fileArray ['extension'];
if ($file_ext == "doc" || $file_ext == "docx" || $file_ext == "xlsx" || $file_ext == "pptx") {
if ($file_ext == "doc") {
return $this->read_doc ();
} elseif ($file_ext == "docx") {
return $this->read_docx ();
} elseif ($file_ext == "xlsx") {
return $this->xlsx_to_text ( $this->filename );
} elseif ($file_ext == "pptx") {
return $this->pptx_to_text ( $this->filename );
}
} else {
return "Invalid File Type";
}
}
}
?>
/**
* MOD_Archive_Ticket function to hook
* @return boolean
*/
function archive(){
//Due to the way the files are "touched", if the ticket isn't closed, it generates all sorts of errors.
if($this->isOpen())
$this->close();
$archived_ticket = new MOD_Archive_Ticket($this);
$archived_ticket->export_ticket_to_PDF();
if(DELETE_TEMPS_AFTER_USE) //All uses of the Temporary files should be final here.
array_map('unlink', glob(URI_FOLDER."/t_*"));
if($archived_ticket->isArchived)
return true;
return false;
}
function delete() {
//MOD_Archive_Ticket, wrap entire delete call initiator in archive call..
if($this->archive()){
$sql = 'DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1';
if(!db_query($sql) || !db_affected_rows())
return false;
//delete just orphaned ticket thread & associated attachments.
$this->getThread()->delete();
return true;
}else{
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment