diff --git a/README.md b/README.md index 6ff6cf1b40c0ecc9bb7fcd7e9259b26ae97ea71b..9946aeb3d85e6c3715b57f9347d10ccdc4edb244 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,21 @@ easy to setup and use. The best part is, it's completely free. Installation ------------ -Download the -[source](./zipball/master) from -the GitHub page. Follow the instructions for installation on the osTicket -[wiki](http://osticket.com/wiki/Installation). +osTicket now supports bleeding-edge installations. The easiest way to +install the software and track updates is to clone the public repository. +Create a folder on you web server (using whatever method makes sense for +you) and cd into it. Then clone the repository (the folder must be empty!): + + git clone https://github.com/osTicket/osTicket-1.7 . + +osTicket uses the git flow development model, so you’ll need to switch to +the develop branch in order to see the bleeding-edge feature additions. + + git checkout develop + +Follow the usual install instructions (beginning from Manual Installation +above), except, don't delete the setup/ folder. For this reason, such an +installation is not recommended for a public-facing support system. Help ---- diff --git a/include/class.canned.php b/include/class.canned.php index 00c46de3365e92fef38fe29e154c56fafacb622b..6c6c1ac1c75bcbc25b7c288c2e49909e30b4876a 100644 --- a/include/class.canned.php +++ b/include/class.canned.php @@ -143,30 +143,13 @@ class Canned { return $i; } - function deleteAttachment($fileId) { - - $sql='DELETE FROM '.CANNED_ATTACHMENT_TABLE - .' WHERE canned_id='.db_input($this->getId()) - .' AND file_id='.db_input($fileId) - .' LIMIT 1'; - - if(!db_query($sql) || !db_affected_rows()) - return false; - - - if(($file=AttachmentFile::lookup($fileId)) && !$file->isInuse()) - $file->delete(); - - return true; - } - function deleteAttachments(){ $deleted=0; - if(($attachments = $this->getAttachments())) { - foreach($attachments as $attachment) - if($attachment['id'] && $this->deleteAttachment($attachment['id'])) - $deleted++; + $sql='DELETE FROM '.CANNED_ATTACHMENT_TABLE + .' WHERE canned_id='.db_input($this->getId()); + if(db_query($sql) && db_affected_rows()) { + $deleted = AttachmentFile::deleteOrphans(); } return $deleted; diff --git a/include/class.cron.php b/include/class.cron.php index 3fd2d652cad8551ffa9852cd3d1f8984f060d542..ddc4d2973e77ff93f6f83a4a3fefd6b4b9b82814 100644 --- a/include/class.cron.php +++ b/include/class.cron.php @@ -34,10 +34,16 @@ class Cron { Sys::purgeLogs(); } + function CleanOrphanedFiles() { + require_once(INCLUDE_DIR.'class.file.php'); + AttachmentFile::deleteOrphans(); + } + function run(){ //called by outside cron NOT autocron - Cron::MailFetcher(); - Cron::TicketMonitor(); - cron::PurgeLogs(); + self::MailFetcher(); + self::TicketMonitor(); + self::PurgeLogs(); + self::CleanOrphanedFiles(); } } ?> diff --git a/include/class.faq.php b/include/class.faq.php index 63b0848e469c7b3b1df6dbb9d61e27fca2c28118..99d6def270ef2f54340dda2cea0f9929a8712f5e 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -217,29 +217,13 @@ class FAQ { return $i; } - function deleteAttachment($fileId) { - - $sql='DELETE FROM '.FAQ_ATTACHMENT_TABLE - .' WHERE faq_id='.db_input($this->getId()) - .' AND file_id='.db_input($fileId) - .' LIMIT 1'; - - if(!db_query($sql) || !db_affected_rows()) - return false; - - if(($file=AttachmentFile::lookup($fileId)) && !$file->isInuse()) - $file->delete(); - - return true; - } - function deleteAttachments(){ $deleted=0; - if(($attachments = $this->getAttachments())) { - foreach($attachments as $attachment) - if($attachment['id'] && $this->deleteAttachment($attachment['id'])) - $deleted++; + $sql='DELETE FROM '.FAQ_ATTACHMENT_TABLE + .' WHERE faq_id='.db_input($this->getId()); + if(db_query($sql) && db_affected_rows()) { + $deleted = AttachmentFile::deleteOrphans(); } return $deleted; diff --git a/include/class.file.php b/include/class.file.php index af38bfd59e43b45471dc35d8e1a256678c05588a..91bd1cddce71f1438580bdffd9bf9782ffe125e9 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -193,6 +193,24 @@ class AttachmentFile { return ($id && ($file = new AttachmentFile($id)) && $file->getId()==$id)?$file:null; } + /** + * Removes files and associated meta-data for files which no ticket, + * canned-response, or faq point to any more. + */ + /* static */ function deleteOrphans() { + $res=db_query( + 'DELETE FROM '.FILE_TABLE.' WHERE id NOT IN (' + # DISTINCT implies sort and may not be necessary + .'SELECT DISTINCT(file_id) FROM (' + .'SELECT file_id FROM '.TICKET_ATTACHMENT_TABLE + .' UNION ALL ' + .'SELECT file_id FROM '.CANNED_ATTACHMENT_TABLE + .' UNION ALL ' + .'SELECT file_id FROM '.FAQ_ATTACHMENT_TABLE + .') still_loved' + .')'); + return db_affected_rows(); + } } class AttachmentList { diff --git a/include/class.filter.php b/include/class.filter.php index 9a1caf2b1b0792c9cea14d9cbf28cf4a458ea0ce..9b9d7279052ad0e5624642cb9f9f1c2ed127a6c5 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -38,7 +38,7 @@ class Filter { return false; $this->ht=db_fetch_array($res); - $this->id=$info['id']; + $this->id=$this->ht['id']; return true; } @@ -589,16 +589,46 @@ class EmailFilter { array_push($this->filters, new Filter($id)); return $this->filters; } + /** + * Fetches the short list of filters that match the email received in the + * constructor. This function is memoized so subsequent calls will + * return immediately. + */ + function getMatchingFilterList() { + if (!isset($this->short_list)) { + $this->short_list = array(); + foreach ($this->filters as $filter) + if ($filter->matches($this->email)) + $this->short_list[] = $filter; + } + return $this->short_list; + } + /** + * Determine if the filters that match the received email indicate that + * the email should be rejected + * + * Returns FALSE if the email should be acceptable. If the email should + * be rejected, the first filter that matches and has rejectEmail set is + * returned. + */ + function shouldReject() { + foreach ($this->getMatchingFilterList() as $filter) { + # Set reject if this filter indicates that the email should + # be blocked; however, don't unset $reject, because if it + # was set by another rule that did not set stopOnMatch(), we + # should still honor its configuration + if ($filter->rejectEmail()) return $filter; + } + return false; + } /** * Determine if any filters match the received email, and if so, apply * actions defined in those filters to the ticket-to-be-created. */ function apply(&$ticket) { - foreach ($this->filters as $filter) { - if ($filter->matches($this->email)) { - $filter->apply($ticket, $this->email); - if ($filter->stopOnMatch()) break; - } + foreach ($this->getMatchingFilterList() as $filter) { + $filter->apply($ticket, $this->email); + if ($filter->stopOnMatch()) break; } } diff --git a/include/class.ticket.php b/include/class.ticket.php index edb9ac4608cd6df114bebd62c798d4b205b443aa..2c111cec2f5a733716c73d7c2a03402690f7ff10 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1424,15 +1424,10 @@ class Ticket{ global $cfg; $deleted=0; - if(($attachments = $this->getAttachments())) { - //Clear reference table - XXX: some attachments might be orphaned - db_query('DELETE FROM '.TICKET_ATTACHMENT_TABLE.' WHERE ticket_id='.db_input($this->getId())); - //Delete file from DB IF NOT inuse. - foreach($attachments as $attachment) { - if(($file=AttachmentFile::lookup($attachment['file_id'])) && !$file->isInuse() && $file->delete()) - $deleted++; - } - } + // Clear reference table + $res=db_query('DELETE FROM '.TICKET_ATTACHMENT_TABLE.' WHERE ticket_id='.db_input($this->getId())); + if ($res && db_affected_rows()) + $deleted = AttachmentFile::deleteOrphans(); return $deleted; } @@ -1627,12 +1622,21 @@ class Ticket{ function create($vars,&$errors, $origin, $autorespond=true, $alertstaff=true) { global $cfg,$thisclient,$_FILES; - //Make sure the email is not banned + //Make sure the email address is not banned if ($vars['email'] && EmailFilter::isBanned($vars['email'])) { $errors['err']='Ticket denied. Error #403'; Sys::log(LOG_WARNING,'Ticket denied','Banned email - '.$vars['email']); return 0; - } + } + // Make sure email contents should not be rejected + if (($email_filter=new EmailFilter($vars)) + && ($filter=$email_filter->shouldReject())) { + $errors['err']='Ticket denied. Error #403'; + Sys::log(LOG_WARNING,'Ticket denied', + sprintf('Banned email - %s by filter "%s"', $vars['email'], + $filter->getName())); + return 0; + } $id=0; $fields=array(); @@ -1692,7 +1696,7 @@ class Ticket{ } # Perform email filter actions on the new ticket arguments XXX: Move filter to the top and check for reject... - if (!$errors && $ef = new EmailFilter($vars)) $ef->apply($vars); + if (!$errors && $email_filter) $email_filter->apply($vars); # Some things will need to be unpacked back into the scope of this # function diff --git a/setup/inc/class.setup.php b/setup/inc/class.setup.php index 2660da6a260cc8afd6360f3f6aec4ea5f4131bf7..c4d986c655ad6041591f879f7e4c077ef24ec6b7 100644 --- a/setup/inc/class.setup.php +++ b/setup/inc/class.setup.php @@ -253,7 +253,7 @@ class Installer extends SetupWizard { //Staff's email can't be same as system emails. - if($vars['admin_email'] && $vars['email'] && !strcasecmp($vars['sysemail'],$vars['email'])) + if($vars['admin_email'] && $vars['email'] && !strcasecmp($vars['admin_email'],$vars['email'])) $this->errors['admin_email']='Conflicts with system email above'; //Admin's pass confirmation. if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2'])) @@ -303,8 +303,8 @@ class Installer extends SetupWizard { if(!$this->errors) { //Create admin user. $sql='INSERT INTO '.PREFIX.'staff SET created=NOW() ' - .', isactive=1, isadmin=1, group_id=1, dept_id=1, timezone_id=1 ' - .', email='.db_input($_POST['email']) + .', isactive=1, isadmin=1, group_id=1, dept_id=1, timezone_id=8 ' + .', email='.db_input($_POST['admin_email']) .', firstname='.db_input($vars['fname']) .', lastname='.db_input($vars['lname']) .', username='.db_input($vars['username']) @@ -318,7 +318,7 @@ class Installer extends SetupWizard { //XXX: rename ostversion helpdesk_* ?? $sql='INSERT INTO '.PREFIX.'config SET updated=NOW(), isonline=0 ' .', default_email_id=1, alert_email_id=2, default_dept_id=1 ' - .', default_sla_id=1, default_timezone_id=1, default_template_id=1 ' + .', default_sla_id=1, default_timezone_id=8, default_template_id=1 ' .', admin_email='.db_input($vars['admin_email']) .', schema_signature='.db_input(md5_file($schemaFile)) .', helpdesk_url='.db_input(URL) @@ -356,7 +356,8 @@ class Installer extends SetupWizard { @mysql_query($sql); //Create a ticket to make the system warm and happy. - $sql='INSERT INTO '.PREFIX.'ticket SET created=NOW(), status="open", source="Web", priority_id=2, dept_id=1 ' + $sql='INSERT INTO '.PREFIX.'ticket SET created=NOW(), status="open", source="Web" ' + .' ,priority_id=2, dept_id=1, topic_id=1 ' .' ,ticketID='.db_input(Misc::randNumber(6)) .' ,email="support@osticket.com" ' .' ,name="osTicket Support" ' diff --git a/setup/install.php b/setup/install.php index e98c7aa7bf4791a3c2dd536b4722e3d4206a4a80..e7f68e9d5f203e3a6960fdb229449a48682021ea 100644 --- a/setup/install.php +++ b/setup/install.php @@ -39,7 +39,7 @@ if($_POST && $_POST['s']) { break; case 'config': if(!$installer->config_exists()) - $errors['err']='Configuratin file does NOT exist. Follow steps below'.$installer->getConfigFile(); + $errors['err']='Configuration file does NOT exist. Follow steps below to add one.'; elseif(!$installer->config_writable()) $errors['err']='Write access required to continue'; else diff --git a/setup/js/tips.js b/setup/js/tips.js index f24d82512d6b0bd6258ae8c674bce77bfaed0826..76c20421a28c9301a92497a778de881e1e4f91fe 100644 --- a/setup/js/tips.js +++ b/setup/js/tips.js @@ -1,60 +1,47 @@ -jQuery(function($) { - var tips = $('.tip'); - for(i=0;i<tips.length;i++) { - tips[i].rel = 'tip-' + i; - } - - $('.tip').live('mouseover click', function(e) { - e.preventDefault(); - var tip_num = this.rel; - - if($('.' + tip_num).length == 0) { - - var elem = $(this); - var pos = elem.offset(); - - var y_pos = pos.top - 12; - var x_pos = pos.left + elem.width() + 20; - - var tip_arrow = $('<img>').attr('src', './images/tip_arrow.png').addClass('tip_arrow'); - var tip_box = $('<div>').addClass('tip_box'); - var tip_content = $('<div>').addClass('tip_content').load('tips.html '+elem.attr('href'), function() { - tip_content.prepend('<a href="#" class="tip_close">x</a>'); - }); - - var the_tip = tip_box.append(tip_arrow).append(tip_content); - the_tip.css({ - "top":y_pos + "px", - "left":x_pos + "px" - }).addClass(tip_num); - - tip_timer = setTimeout(function() { - $('.tip_box').remove(); - $('body').append(the_tip.hide().fadeIn()); - }, 500); - - $('.' + tip_num + ' .tip_shadow').css({ - "height":$('.' + tip_num).height() + 5 - }); - } - }).live('mouseout', function() { - clearTimeout(tip_timer); - }); - $('body').delegate('.tip_close', 'click', function(e) { - e.preventDefault(); - $(this).parent().parent().remove(); - }).delegate('.tip_menu .assign', 'click', function(e) { - e.preventDefault(); - elem = $(this).parent().parent().parent().parent(); - $('.tip_body', elem).slideToggle(function() { - $('.assign_panel', elem).slideToggle(); - }); - }).delegate('.assign_panel .cancel', 'click', function(e) { - e.preventDefault(); - elem = $(this).parent().parent().parent(); - $('.assign_panel', elem).slideToggle(function() { - $('.tip_body', elem).slideToggle(); - }); - }); - -}); +jQuery(function($) { + var tips = $('.tip'); + for(i=0;i<tips.length;i++) { + tips[i].rel = 'tip-' + i; + } + + $('.tip').live('mouseover click', function(e) { + e.preventDefault(); + var tip_num = this.rel; + + if($('.' + tip_num).length == 0) { + + var elem = $(this); + var pos = elem.offset(); + + var y_pos = pos.top - 12; + var x_pos = pos.left + elem.width() + 20; + + var tip_arrow = $('<img>').attr('src', './images/tip_arrow.png').addClass('tip_arrow'); + var tip_box = $('<div>').addClass('tip_box'); + var tip_content = $('<div>').addClass('tip_content').load('tips.html '+elem.attr('href'), function() { + tip_content.prepend('<a href="#" class="tip_close">x</a>'); + }); + + var the_tip = tip_box.append(tip_arrow).append(tip_content); + the_tip.css({ + "top":y_pos + "px", + "left":x_pos + "px" + }).addClass(tip_num); + + tip_timer = setTimeout(function() { + $('.tip_box').remove(); + $('body').append(the_tip.hide().fadeIn()); + }, 500); + + $('.' + tip_num + ' .tip_shadow').css({ + "height":$('.' + tip_num).height() + 5 + }); + } + }).live('mouseout', function() { + clearTimeout(tip_timer); + }); + $('body').delegate('.tip_close', 'click', function(e) { + e.preventDefault(); + $(this).parent().parent().remove(); + }); +});