Advertisement
Grizly

remove_obsolete_tickets.php

Dec 10th, 2013
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 14.87 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * Removes obsolete tickets, can be run as often as you choose.
  5.  * Ideally via cron:
  6. # Every 28 days?
  7. * * /28 * * nobody /usr/bin/php /path/to/remove_obsolete_tickets.php
  8. *
  9. * Currently only tested/designed for linux, specifically Debian, but should work on others.
  10. * Requires Ghostscript (gs) installed for PDF merging to work.
  11. *
  12. * @author Grizly
  13. *
  14. * http://pastebin.com/yjQbAPqR
  15. * http://osticket.com/forum/discussion/comment/75406
  16. *
  17. * YOU WILL NEED TO CONFIGURE THE FOLLLOWING:
  18. *
  19. *  Specify your osTicket username, probably best to use an Admins username for this. DO NOT NEED PASSWORD.
  20. * this is the user account on your installation, not forum or anything like that.
  21. */
  22. define ( 'MY_USER_ID', 'Grizly' );
  23. /**
  24.  * Any closed ticket older than this many months will be archived/deleted.
  25.  */
  26. define ( 'DELETEMONTHS', 1 );
  27. /**
  28.  * Which subfolder would you like to start archiving tickets into?
  29.  */
  30. define ( 'BACKUPDIR', getcwd () . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR );
  31. /**
  32.  * Change to false to show more output, makes cron send emails which you may not care about.
  33.  */
  34. define ( 'CRONFRIENDLY', true );
  35. /**
  36.  * Change to true to allow anyone on the internet to initiate..
  37.  * not recommended unless you have a remote-cron requirement.
  38.  */
  39. define ( 'REMOTECRONENABLED', false );
  40. /**
  41.  * Where do you want your temporary files stored..
  42.  * could be /tmp/ostickets/ or anywhere writable by the user.
  43.  */
  44. define ( 'URI_FOLDER', getcwd () . DIRECTORY_SEPARATOR . 'scp' . DIRECTORY_SEPARATOR . 'restricted' );
  45. /**
  46.  * Change this to true, to start deleting.
  47.  * ;-) This will start actually deleting tickets, so, make sure its backing them up properly first.
  48.  */
  49. define ( 'ACTUALLY_DELETE', false );
  50.  
  51. /******************************************************************************************************************************
  52.  * Should be able to ignore from here
  53.  */
  54. // determine if gs (ghostscript) is installed and accessible to php process. (Yet another reason to run in CLI mode)
  55. // Unfortunately, when processing some broken PDF's, you will see some errors even with CRONFRIENDLY enabled.
  56. define ( 'PDF_MERGE_ENABLED', (is_readable ( exec ( 'which gs' ) )) );
  57. // png->jpg converter requires GD
  58. define ( 'GD_ENABLED', (function_exists ( 'imagecreatefrompng' )) );
  59. // This starts osTicket code
  60. require_once 'main.inc.php';
  61. // Code to convert osTicket db files, into filesystem objects or strings.
  62. // Includes code to convert msdocs into text..
  63. require_once 'class.attachment.file.php';
  64.  
  65. // basically debug mode.. show all errors! Has to be set after main require, as main turns all this off.. which is great for client/user web-pages, not so great if you want to know why something is broken!
  66. if (! CRONFRIENDLY) {
  67.     error_reporting ( E_ALL );
  68.     ini_set ( 'display_errors', 1 );
  69.     ini_set ( 'display_startup_errors', 1 );
  70. }
  71.  
  72. // Disable in defines above, or leave alone for more secure mode..
  73. if (REMOTECRONENABLED || (! (php_sapi_name () == 'cli')))
  74.     die ( 'Cannot be run from browser.. ' );
  75.     // These reduce errors running in CLI mode.
  76. $_SERVER ['REMOTE_ADDR'] = 'localhost';
  77. $_SERVER ['HTTP_USER_AGENT'] = 'Script';
  78. // PDF image centering required info.. need to scale and center images on pages
  79. // especially if they were PDF's themselves originally!
  80. define ( 'DPI', 64 );
  81. define ( 'MM_IN_INCH', 25.4 );
  82. define ( 'A4_WIDTH', 297 );
  83. define ( 'A4_HEIGHT', 210 );
  84. define ( 'MAX_HEIGHT', 800 );
  85. define ( 'MAX_WIDTH', 500 );
  86.  
  87. // Only required if you are archiving.. the PDF printer needs a userobject global to function correctly.
  88. $thisstaff = new Staff ( MY_USER_ID );
  89. // select old messages.. we probably don't need any of them where we're going.
  90. $select = 'SELECT ticket_id FROM ' . TICKET_TABLE . " WHERE status='closed' AND closed < DATE_SUB(NOW(), INTERVAL " . DELETEMONTHS . ' MONTH)';
  91.  
  92. /**
  93.  * From <a href="http://stackoverflow.com/a/2668953">StackOverflow</a>
  94.  *
  95.  * Function: sanitize
  96.  * Returns a sanitized string, typically for URLs.
  97.  *
  98.  * Parameters:
  99.  * $string - The string to sanitize.
  100.  * $force_lowercase - Force the string to lowercase?
  101.  * $anal - If set to *true*, will remove all non-alphanumeric characters.
  102.  *
  103.  * 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.
  104.  */
  105. function sanitize($string, $force_lowercase = true, $anal = false) {
  106.     $strip = array ("~","`","!","@","#","$","%","^","&","*","(",")","_","=","+","[","{","]","}","\\","|",";",":","\"","'","&#8216;","&#8217;","&#8220;","&#8221;","&#8211;","&#8212;","—","–",",","<",".",">","/","?" );
  107.     $clean = trim ( str_replace ( $strip, "", strip_tags ( $string ) ) );
  108.     $clean = preg_replace ( '/\s+/', "-", $clean );
  109.     $clean = ($anal) ? preg_replace ( "/[^a-zA-Z0-9]/", "", $clean ) : $clean;
  110.     return ($force_lowercase) ? (function_exists ( 'mb_strtolower' )) ? mb_strtolower ( $clean, 'UTF-8' ) : strtolower ( $clean ) : $clean;
  111. }
  112.  
  113. // Begin
  114. // We know archiving is expensive to the system, so lower it to bottom of system process priority (LINUX ONLY)
  115. _pcntl_setpriority ( 19 );
  116.  
  117. $res = db_query ( $select );
  118. if ($res && $num = db_num_rows ( $res )) {
  119.     $messages = db_assoc_array ( $res, true );
  120.     if ($messages && ! CRONFRIENDLY)
  121.         print "Found $num matching deletables!\nDeleting messages older than " . DELETEMONTHS . " months, using " . BACKUPDIR . " for archives.\n";
  122.    
  123.     foreach ( $messages as $ticket ) {
  124.         if (! delete_ticket ( $ticket ['ticket_id'] ))
  125.             print 'Failed to delete ticket_id: ' . $ticket ['ticket_id'] . "\n\n";
  126.     }
  127. } elseif (! CRONFRIENDLY) {
  128.     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.";
  129. }
  130.  
  131. /**
  132.  * 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>
  133.  *
  134.  * @param int $priority        
  135.  * @param string $pid          
  136.  * @return boolean
  137.  */
  138. function _pcntl_setpriority($priority, $pid = false) {
  139.     $pid = ($pid) ? $pid : getmypid ();
  140.     $priority = ( int ) $priority;
  141.     $pid = ( int ) $pid;
  142.    
  143.     // check if out of bounds
  144.     if ($priority > 20 && $priority < - 20) {
  145.         return False;
  146.     }
  147.     if ($pid == 0) {
  148.         $pid = getmypid ();
  149.     }
  150.     // check if already set.
  151.     if ($priority == pcntl_getpriority ( $pid ))
  152.         return true;
  153.    
  154.     return exec ( "renice  $priority -p $pid" ) != false;
  155. }
  156.  
  157. /**
  158.  * Calculates the correct height/width in MM for the DPI and returns to centreImage.
  159.  * * From: <a href="https://gist.github.com/benshimmin/4088493">BenShimmin gisthub.com</a>
  160.  *
  161.  * @param string $imgFilename          
  162.  * @return multitype:number
  163.  *
  164.  */
  165. function resizeToFit($imgFilename) {
  166.     list ( $width, $height ) = getimagesize ( $imgFilename );
  167.    
  168.     if (! $width)
  169.         $width = 500;
  170.     if (! $height)
  171.         $height = 800;
  172.    
  173.     $widthScale = MAX_WIDTH / $width;
  174.     $heightScale = MAX_HEIGHT / $height;
  175.    
  176.     $scale = min ( $widthScale, $heightScale );
  177.    
  178.     return array (round ( ($scale * $width) * MM_IN_INCH / DPI ),round ( ($scale * $height) * MM_IN_INCH / DPI ) );
  179. }
  180.  
  181. /**
  182.  * Centers an image on a page, useful for large images embedded within a pdf page..
  183.  * ;-)
  184.  *
  185.  * From: <a href="https://gist.github.com/benshimmin/4088493">BenShimmin gisthub.com</a>
  186.  * Modified for this, removed a few function-calls, and de-OOP'd
  187.  *
  188.  * @param Ticket2PDF $pdf          
  189.  * @param String $img
  190.  *          the full path to the file
  191.  * @param String $ext
  192.  *          extension of image.
  193.  */
  194. function centreImage($pdf, $img, $ext) {
  195.     list ( $width, $height ) = resizeToFit ( $img );
  196.    
  197.     // you will probably want to swap the width/height
  198.     // around depending on the page's orientation
  199.     $pdf->Image ( $img, (A4_HEIGHT - $width) / 2, (A4_WIDTH - $height) / 2, $width, $height, $ext );
  200. }
  201.  
  202. /**
  203.  * From: <a href="http://www.linux.com/news/software/applications/8229-putting-together-pdf-files">linux.com</a>
  204.  *
  205.  * Uses Ghostscript to combine PDF files, something like the following:
  206.  *
  207.  * gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=finished.pdf file1.pdf file2.pdf
  208.  *
  209.  * Unless you're very familiar with Ghostscript, that string of commands won't mean much to you. Here's a quick breakdown:
  210.  * gs -- starts the Ghostscript program
  211.  * -dBATCH -- once Ghostscript processes the PDF files, it should exit. If you don't include this option, Ghostscript will just keep running
  212.  * -dNOPAUSE -- forces Ghostscript to process each page without pausing for user interaction
  213.  * -q -- stops Ghostscript from displaying messages while it works
  214.  * -sDEVICE=pdfwrite -- tells Ghostscript to use its built-in PDF writer to process the files
  215.  * -sOutputFile=finished.pdf -- tells Ghostscript to save the combined PDF file with the name that you specified
  216.  *
  217.  *
  218.  * @param String $pdfs
  219.  *          The filenames to merge
  220.  * @param String $outputname
  221.  *          The filename to end up with. (Use the first one we generated, as that is what we really wanted)
  222.  */
  223. function mergePDFS($pdfs, $outputname) {
  224.     if ($pdfs && $outputname) {
  225.         // Create temporary file to merge them into
  226.         $tmpfile = tempnam ( '/tmp', 'pdfmerge_' );
  227.         // let GhostScript do the merge, fast!
  228.         system ( "gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=$tmpfile $pdfs" );
  229.         // Rename merged file (this will move as well) back to the original file, overwriting it.
  230.         rename ( $tmpfile, $outputname );
  231.     } else {
  232.         throw new Exception ( "incorrect argument, no system call for you!" );
  233.     }
  234. }
  235.  
  236. /**
  237.  * Uses <a href="http://www.osticket.com">osTicket</a> functions to remove a ticket correctly.
  238.  * By default it creates a "backup directory" for each department, saves a PDF copy of the ticket, uses PDF->IMG converter to save PDF attachements.
  239.  * Image and Plain-text/HTML attachments are embedded directly. All at the end of the generated file.
  240.  *
  241.  * Useful for archival purposes and purging of obsolete tickets.
  242.  */
  243. function delete_ticket($id) {
  244.     // Create an osTicket object
  245.     $t = new Ticket ( $id );
  246.     $msg = " ticket $id:" . $t->getSubject () . "\n";
  247.     if(!CRONFRIENDLY && ACTUALLY_DELETE)
  248.         print "Deleting $msg";
  249.     elseif (!CRONFRIENDLY)
  250.         print "Archving $msg";
  251.     // Add department subfolder name
  252.     $folder = BACKUPDIR . $t->getDeptName () . '/';
  253.     // Department might be new, or we haven't made a folder for it yet.
  254.     if (! is_writable ( $folder ))
  255.         mkdir ( $folder );
  256.         // Create a filename for the PDF
  257.     $name = 'Ticket-' . $t->getExtId ();
  258.     $name .= ' ' . $t->getSubject ();
  259.     // if you don't have getOrderNumber, its because you didn't modify class.tickets.php to create it, good for you!
  260.     if (method_exists ( $t, 'getOrderNumber' ))
  261.         $on = $t->getOrderNumber ();
  262.     else
  263.         $on = false;
  264.     $name .= ($on) ? ' Order-' . $on : '';
  265.    
  266.     // Some subjects may contain names that are unsuitable for filenames, and would therefore break the script, or the filesystem :-(
  267.     // currently set to remove all non-alphanumerics.
  268.     $name = sanitize ( $name, False, False );
  269.    
  270.     // Put the PDF's name at the end of the filename, this is the final filename.
  271.     $outputfile = $folder . $name . '.pdf';
  272.    
  273.     if (is_readable ( $outputfile ) && ACTUALLY_DELETE) {
  274.         // we already have the file, lets just proceed straight to deleting the ticket
  275.         $t->delete ();
  276.         return true;
  277.     } elseif (is_readable ( $outputfile ))
  278.         return true; // no point reinventing the wheel and continually re-archving a ticket thread over-and-over-and-over..
  279.                          
  280.     // Create a PDF object to represent the ticket.
  281.     $pdf = new Ticket2PDF ( $t, 'A4', true ); // specifying TRUE will save all notes/responses & messages to the start of the PDF.
  282.                                              
  283.     // An array of pdf's which will be merged together with the created one at the end.
  284.     $merge_queue = array ();
  285.    
  286.     // Append any attachments to the pdf.. ;-)
  287.     // Start by getting the tickets thread of messages.
  288.     $thread = $t->getThread ();
  289.     // Find all the attachments to the thread.
  290.     if ($total = $thread->getNumAttachments ()) {
  291.         if (! CRONFRIENDLY)
  292.             print "Found $total Attachments for $name!\n ";
  293.            
  294.             // Attachments are actually stored in relation to the messages in the thread, not the ticket.
  295.             // So, get the entries to the thread themselves
  296.         $thread = $t->getThreadEntries ( array ('M','R','N' ) ); // Specify Notes, Messages and Responses.. all entries!
  297.        
  298.         foreach ( $thread as $entry ) {
  299.             // This line almost word-for-word copied from the normal ticket-view page for staff.
  300.             if ($entry ['attachments'] && ($tentry = $t->getThreadEntry ( $entry ['id'] )) && ($attachments = $tentry->getAttachments ())) {
  301.                 foreach ( $attachments as $attachment ) {
  302.                     // By default, saves the file into a filesystem addressable container.. this means we can import it into the PDF
  303.                     // Note, this $attachment, is simply an array at this point.
  304.                     $attached_file = new TDJ_Attachment_File ( $attachment );
  305.                    
  306.                     if (! CRONFRIENDLY)
  307.                         print ("Attachment is a " . $attached_file->type . " file. \n") ;
  308.                    
  309.                     switch ($attached_file->type) {
  310.                         case 'pdf' :
  311.                             {
  312.                                 $merge_queue [] = $attached_file->uri;
  313.                                 break;
  314.                             }
  315.                         case 'text' :
  316.                             {
  317.                                 // 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)
  318.                                 $pdf->Cell ( 0, 0, $attached_file->str, 0, 1, 'C' );
  319.                                 break;
  320.                             }
  321.                         case 'image' :
  322.                             {
  323.                                 if (strlen ( $attached_file->uri ) && is_readable ( $attached_file->uri ) && filesize ( $attached_file->uri ) > 10) { // skip empty uris.. christ.
  324.                                     @$pdf->AddPage ();
  325.                                     if (! CRONFRIENDLY)
  326.                                         print "Creating new PDF page..\n";
  327.                                         // Looks better if the image is zoomed in, centered and such.
  328.                                     centreImage ( $pdf, $attached_file->uri, $attached_file->ext );
  329.                                 }
  330.                                 break;
  331.                             }
  332.                        
  333.                         default :
  334.                             if (! CRONFRIENDLY)
  335.                                 print ("Unable to merge attachment: " . $attached_file->uri . ' As its type was not set') ;
  336.                     }
  337.                 } // end foreach
  338.             } // endif
  339.         } // endforeach
  340.     } elseif (! CRONFRIENDLY) {
  341.         print "Didn't find any attachments.\n";
  342.     }
  343.    
  344.     // Initiate the actual output of the PDF including our text-based and image-inserted additions.
  345.     $pdf->Output ( $outputfile, 'F' );
  346.    
  347.     // Begin appending existing PDF attachments to the end of the ticket PDF.
  348.     if (count ( $merge_queue ) > 0 && PDF_MERGE_ENABLED) {
  349.         if (! CRONFRIENDLY)
  350.             print ("Merging PDFs! \n") ;
  351.             // We have some PDF's that need to be appended to the generated file.
  352.         $merge_me = $outputfile;
  353.         // Create list of pdf filenames to merge together as a string
  354.         foreach ( $merge_queue as $file ) {
  355.             if (is_readable ( $file ) && filesize ( $file ) > 10)
  356.                 $merge_me .= ' ' . $file;
  357.         }
  358.         mergePDFS ( $merge_me, $outputfile );
  359.     } else {
  360.         if (! CRONFRIENDLY)
  361.             print "No mergeable pdfs found. \n";
  362.     }
  363.    
  364.     if (ACTUALLY_DELETE)
  365.         return $t->delete ();
  366.     return true;
  367. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement