diff --git a/WHATSNEW.md b/WHATSNEW.md index 653f8a5c0462d8d7630c5506b4b4099a29fe27c1..5034732f4c55257eab64844adb679498e7d80c05 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,3 +1,38 @@ +osTicket v1.9.8 +=============== +### Enhancements + * Update user information for existing users when importing CSV (#1993) + * Agent names are consistently formatted and sorted throughout the system (#1972) + * Memcache session backend support. (See `include/ost-sampleconfig.php`) (#2031) + * Email domain validation includes DNS record verification (#2042) + * Make ticket queue selection sticky (aa2dc85) + +### Improvements + * Fix incorrect mapping of ISO charsets to ISO-8859-1, thanks @nerull7 + * Fix unnecessary drop of ticket CDATA table because of update to deleted + field (#1932) + * Fix inability to create or update organization custom data (#1942) + * Fix inability to update some fields of user custom data (#1942) + * Fix filtering user custom data for email tickets (#1943) + * Fix missing email headers resulting in incorrectly threaded emails when + delivered (#1947) + * Cleanup file data when removing custom file uploads (#1942) + * Fix crash when exporting PDF and PHAR extension is not enabled + * Fix crash processing some TNEF documents (89f3ed7, #1956) + * Fix handling of GBK charset when gb2312 is advertised (#2000) + * Fix link to client ticket listing when logged in, thanks @neewy (#1952) + * Disambiguate staff and collaborators when processing a some emails (#1983) + * Fix several i18n phrase and layout issues (#1958, #1962, #2039) + * Improve detection of some bounce notices with alternative content (#1994) + * Fix image URL rewrite when pasting existing images, from a KB article for + instance (#1960) + * Preserve internal note formatting on new ticket by staff if HTML is + disabled (#2001) + * Touch organization `updated` timestamp on custom data update (#2007) + * Fix deployment on Windows® platforms, thanks @yadimon (#2033) + * Fix upgrade crash if retrying an old, failed upgrade from v1.6 (#1995) + * Fix corruption of some html content (9ae01bf) + osTicket v1.9.7 =============== ### Enhancements diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index c2c90a3a78510b85886801992bcaf6f43af2321d..ca2b3f2c8fa2a649f5b9cfa2e55a786737339f38 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -1219,7 +1219,13 @@ class DynamicFormEntry extends VerySimpleModel { // Set the entry ID here so that $field->getClean() can use the // entry-id if necessary $a->entry = $this; - $val = $field->to_database($field->getClean()); + try { + $val = $field->to_database($field->getClean()); + } + catch (FieldUnchanged $e) { + // Don't update the answer. + continue; + } if (is_array($val)) { $a->set('value', $val[0]); $a->set('value_id', $val[1]); diff --git a/include/class.format.php b/include/class.format.php index 52b2bcff56f6c1612b9b0dd100c30c54ec4af3dd..06a07f5065e5ad5bcc5357833817eb9d37544fdd 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -212,7 +212,7 @@ class Format { $html); $config = array( 'safe' => 1, //Exclude applet, embed, iframe, object and script tags. - 'balance' => 1, //balance and close unclosed tags. + 'balance' => 0, // No balance — corrupts poorly formatted Outlook html 'comment' => 1, //Remove html comments (OUTLOOK LOVE THEM) 'tidy' => -1, 'deny_attribute' => 'id', diff --git a/include/class.forms.php b/include/class.forms.php index 75acdbd92a89fca54b6c751fad420b7384f0d32f..9b8de17b2bee8cf3f49853c3823cd36a1adcaa63 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -1027,7 +1027,7 @@ class TextboxField extends FormField { $config = $this->getConfiguration(); $validators = array( '' => null, - 'email' => array(array('Validator', 'is_email'), + 'email' => array(array('Validator', 'is_valid_email'), __('Enter a valid email address')), 'phone' => array(array('Validator', 'is_phone'), __('Enter a valid phone number')), @@ -1061,12 +1061,20 @@ class TextboxField extends FormField { class PasswordField extends TextboxField { static $widget = 'PasswordWidget'; + function parse($value) { + // Don't trim the value + return $value; + } + function to_database($value) { - return Crypto::encrypt($value, SECRET_SALT, $this->getFormName()); + // If not set in UI, don't save the empty value + if (!$value) + throw new FieldUnchanged(); + return Crypto::encrypt($value, SECRET_SALT, 'pwfield'); } function to_php($value) { - return Crypto::decrypt($value, SECRET_SALT, $this->getFormName()); + return Crypto::decrypt($value, SECRET_SALT, 'pwfield'); } } @@ -2549,8 +2557,13 @@ class Widget { class TextboxWidget extends Widget { static $input_type = 'text'; - function render($options=array()) { + function render($options=array(), $extraConfig=false) { $config = $this->field->getConfiguration(); + if (is_array($extraConfig)) { + foreach ($extraConfig as $k=>$v) + if (!isset($config[$k]) || !$config[$k]) + $config[$k] = $v; + } if (isset($config['size'])) $size = "size=\"{$config['size']}\""; if (isset($config['length']) && $config['length']) @@ -2601,12 +2614,19 @@ class TextboxSelectionWidget extends TextboxWidget { class PasswordWidget extends TextboxWidget { static $input_type = 'password'; + function render($mode=false, $extra=false) { + $extra = array(); + if ($this->field->value) { + $extra['placeholder'] = '••••••••••••'; + } + return parent::render($mode, $extra); + } + function parseValue() { + parent::parseValue(); // Show empty box unless failed POST - if ($_SERVER['REQUEST_METHOD'] == 'POST' - && $this->field->getForm()->isValid()) - parent::parseValue(); - else + if ($_SERVER['REQUEST_METHOD'] != 'POST' + || $this->field->getForm()->isValid()) $this->value = ''; } } @@ -3445,5 +3465,11 @@ class TransferForm extends Form { } } - +/** + * FieldUnchanged + * + * Thrown in the to_database() method to indicate the value should not be + * saved in the database (it wasn't changed in the request) + */ +class FieldUnchanged extends Exception {} ?> diff --git a/include/class.organization.php b/include/class.organization.php index cc011495fa3e13114ffd8af3e2863946ca86d3f8..e79f18af0316abbc6d2c971a938d2da44c987608 100644 --- a/include/class.organization.php +++ b/include/class.organization.php @@ -374,7 +374,8 @@ implements TemplateVariable { $this->save(); } $entry->setSource($vars); - $entry->save(); + if ($entry->save()) + $this->updated = SqlFunction::NOW(); } // Set flags diff --git a/include/class.ostsession.php b/include/class.ostsession.php index b300f16ec7a826443e3e1fb52530a7c88bd6486b..d93de839a9770e422dcde63205060af27ff95fa7 100644 --- a/include/class.ostsession.php +++ b/include/class.ostsession.php @@ -15,11 +15,17 @@ **********************************************************************/ class osTicketSession { + static $backends = array( + 'db' => 'DbSessionBackend', + 'memcache' => 'MemcacheSessionBackend', + 'system' => 'FallbackSessionBackend', + ); var $ttl = SESSION_TTL; var $data = ''; var $data_hash = ''; var $id = ''; + var $backend; function osTicketSession($ttl=0){ $this->ttl = $ttl ?: ini_get('session.gc_maxlifetime') ?: SESSION_TTL; @@ -49,24 +55,41 @@ class osTicketSession { session_set_cookie_params($ttl, ROOT_PATH, $domain, osTicket::is_https()); - //Set handlers. - session_set_save_handler( - array(&$this, 'open'), - array(&$this, 'close'), - array(&$this, 'read'), - array(&$this, 'write'), - array(&$this, 'destroy'), - array(&$this, 'gc') - ); - - //Start the session. + if (!defined('SESSION_BACKEND')) + define('SESSION_BACKEND', 'db'); + + try { + $bk = SESSION_BACKEND; + if (!class_exists(self::$backends[$bk])) + $bk = 'db'; + $this->backend = new self::$backends[$bk]($this->ttl); + } + catch (Exception $x) { + // Use the database for sessions + trigger_error($x->getMessage(), E_USER_WARNING); + $this->backend = new self::$backends['db']($this->ttl); + } + + if ($this->backend instanceof SessionBackend) { + // Set handlers. + session_set_save_handler( + array($this->backend, 'open'), + array($this->backend, 'close'), + array($this->backend, 'read'), + array($this->backend, 'write'), + array($this->backend, 'destroy'), + array($this->backend, 'gc') + ); + } + + // Start the session. session_start(); } function regenerate_id(){ $oldId = session_id(); session_regenerate_id(); - $this->destroy($oldId); + $this->backend->destroy($oldId); } static function destroyCookie() { @@ -86,14 +109,57 @@ class osTicketSession { ini_get('session.cookie_httponly')); } - function open($save_path, $session_name){ - return (true); + /* helper functions */ + + function get_online_users($sec=0){ + $sql='SELECT user_id FROM '.SESSION_TABLE.' WHERE user_id>0 AND session_expire>NOW()'; + if($sec) + $sql.=" AND TIME_TO_SEC(TIMEDIFF(NOW(),session_updated))<$sec"; + + $users=array(); + if(($res=db_query($sql)) && db_num_rows($res)) { + while(list($uid)=db_fetch_row($res)) + $users[] = $uid; + } + + return $users; + } + + /* ---------- static function ---------- */ + static function start($ttl=0) { + return new static($ttl); + } +} + +abstract class SessionBackend { + var $isnew = false; + var $ttl; + + function __construct($ttl) { + $this->ttl = $ttl; } - function close(){ - return (true); + function open($save_path, $session_name) { + return true; } + function close() { + return true; + } + + function getTTL() { + return $this->ttl; + } + + abstract function read($id); + abstract function write($id, $data); + abstract function destroy($id); + abstract function gc($maxlife); +} + +class DbSessionBackend +extends SessionBackend { + function read($id){ $this->isnew = false; if (!$this->data || $this->id != $id) { @@ -146,30 +212,100 @@ class osTicketSession { $sql='DELETE FROM '.SESSION_TABLE.' WHERE session_expire<NOW()'; db_query($sql); } +} - /* helper functions */ +class MemcacheSessionBackend +extends SessionBackend { + var $memcache; + var $servers = array(); - function getTTL(){ - return $this->ttl; + function __construct($ttl) { + parent::__construct($ttl); + + if (!extension_loaded('memcache')) + throw new Exception('Memcached extension is missing'); + if (!defined('MEMCACHE_SERVERS')) + throw new Exception('MEMCACHE_SERVERS must be defined'); + + $servers = explode(',', MEMCACHE_SERVERS); + $this->memcache = new Memcache(); + + foreach ($servers as $S) { + @list($host, $port) = explode(':', $S); + if (strpos($host, '/') !== false) + // Use port '0' for unix sockets + $port = 0; + else + $port = $port ?: ini_get('memcache.default_port') ?: 11211; + $this->servers[] = array(trim($host), (int) trim($port)); + // FIXME: Crash or warn if invalid $host or $port + } } - function get_online_users($sec=0){ - $sql='SELECT user_id FROM '.SESSION_TABLE.' WHERE user_id>0 AND session_expire>NOW()'; - if($sec) - $sql.=" AND TIME_TO_SEC(TIMEDIFF(NOW(),session_updated))<$sec"; + function getKey($id) { + return sha1($id.SECRET_SALT); + } - $users=array(); - if(($res=db_query($sql)) && db_num_rows($res)) { - while(list($uid)=db_fetch_row($res)) - $users[] = $uid; + function read($id) { + $key = $this->getKey($id); + + // Try distributed read first + foreach ($this->servers as $S) { + list($host, $port) = $S; + $this->memcache->addServer($host, $port); + } + $data = $this->memcache->get($key); + + // Read from other servers on failure + if ($data === false && count($this->servers) > 1) { + foreach ($this->servers as $S) { + list($host, $port) = $S; + $this->memcache->pconnect($host, $port); + if ($data = $this->memcache->get($key)) + break; + } } - return $users; + // No session data on record -- new session + $this->isnew = $data === false; + + return $data; } - /* ---------- static function ---------- */ - function start($ttl=0) { - return New osTicketSession($ttl); + function write($id, $data) { + if (defined('DISABLE_SESSION') && $this->isnew) + return; + + $key = $this->getKey($id); + foreach ($this->servers as $S) { + list($host, $port) = $S; + $this->memcache->pconnect($host, $port); + if (!$this->memcache->replace($key, $data, 0, $this->getTTL())); + $this->memcache->set($key, $data, 0, $this->getTTL()); + } + } + + function destroy($id) { + $key = $this->getKey($id); + foreach ($this->servers as $S) { + list($host, $port) = $S; + $this->memcache->pconnect($host, $port); + $this->memcache->replace($key, '', 0, 1); + $this->memcache->delete($key, 0); + } + } + + function gc($maxlife) { + // Memcache does this automatically } } + +class FallbackSessionBackend { + // Use default PHP settings, with some edits for best experience + function __construct() { + // FIXME: Consider extra possible security tweaks such as adjusting + // the session.save_path + } +} + ?> diff --git a/include/class.plugin.php b/include/class.plugin.php index c71d86524df95d44be3ebd3f598029bc17eb7074..edb22710c52cab3755de2f5c40fd651e42bf9e08 100644 --- a/include/class.plugin.php +++ b/include/class.plugin.php @@ -81,7 +81,13 @@ class PluginConfig extends Config { $dbready = array(); foreach ($config as $name => $val) { $field = $f->getField($name); - $dbready[$name] = $field->to_database($val); + try { + $dbready[$name] = $field->to_database($val); + } + catch (FieldUnchanged $e) { + // Don't save the field value + continue; + } } if ($this->updateAll($dbready)) { if (!$msg) diff --git a/include/class.staff.php b/include/class.staff.php index b295a15e194272d0e23ca1284f7bf4eec60ef4c5..8362656a1370ba407e864baa49b0510cc995b25d 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -549,7 +549,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if(!$vars['lastname']) $errors['lastname']=__('Last name is required'); - if(!$vars['email'] || !Validator::is_email($vars['email'])) + if(!$vars['email'] || !Validator::is_valid_email($vars['email'])) $errors['email']=__('Valid email is required'); elseif(Email::getIdByEmail($vars['email'])) $errors['email']=__('Already in-use as system email'); @@ -761,7 +761,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { unset($_SESSION['_staff']['reset-token']); } - function sendResetEmail($template='pwreset-staff') { + function sendResetEmail($template='pwreset-staff', $log=true) { global $ost, $cfg; $content = Page::lookupByType($template); @@ -785,7 +785,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if (!($email = $cfg->getAlertEmail())) $email = $cfg->getDefaultEmail(); - $info = array('email' => $email, 'vars' => &$vars, 'log'=>true); + $info = array('email' => $email, 'vars' => &$vars, 'log'=>$log); Signal::send('auth.pwreset.email', $this, $info); if ($info['log']) @@ -927,7 +927,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if ($this->save() && $this->updateTeams($vars['teams'])) { if ($vars['welcome_email']) - $this->sendResetEmail('registration-staff'); + $this->sendResetEmail('registration-staff', false); return true; } diff --git a/include/class.ticket.php b/include/class.ticket.php index cbb51b4143eca41bdfdf9570835237d5e51db4be..d114d1664665c5444e6ddd3c41c692f88f25b83e 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1491,7 +1491,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { // Skip all the other recipients of the message foreach ($entry->getAllEmailRecipients() as $R) { foreach ($recipients as $R2) { - if ($R2->getEmail() == ($R->mailbox.'@'.$R->hostname)) { + if (0 === strcasecmp($R2->getEmail(), $R->mailbox.'@'.$R->host)) { $skip[$R2->getUserId()] = true; break; } diff --git a/include/class.validator.php b/include/class.validator.php index c14f910743f85b72be0ab65b97af64c48354ffb3..fcb350126a22191da7572719617d42a0cfab7be8 100644 --- a/include/class.validator.php +++ b/include/class.validator.php @@ -140,7 +140,7 @@ class Validator { /*** Functions below can be called directly without class instance. Validator::func(var..); (nolint) ***/ - function is_email($email, $list=false) { + function is_email($email, $list=false, $verify=false) { require_once PEAR_DIR . 'Mail/RFC822.php'; require_once PEAR_DIR . 'PEAR.php'; if (!($mails = Mail_RFC822::parseAddressList($email)) || PEAR::isError($mails)) @@ -156,8 +156,16 @@ class Validator { return false; } + if ($verify && !checkdnsrr($m->host, 'MX')) + return false; + return true; } + + function is_valid_email($email) { + return self::is_email($email, false, true); + } + function is_phone($phone) { /* We're not really validating the phone number but just making sure it doesn't contain illegal chars and of acceptable len */ $stripped=preg_replace("(\(|\)|\-|\.|\+|[ ]+)","",$phone); diff --git a/include/ost-sampleconfig.php b/include/ost-sampleconfig.php index a4624d1e706ce75f36c2e23a6ac1c0ec3b916eb3..0b26400698271c1d89ab46f55a209d501b5b1dae 100644 --- a/include/ost-sampleconfig.php +++ b/include/ost-sampleconfig.php @@ -106,4 +106,24 @@ define('TABLE_PREFIX','%CONFIG-PREFIX'); # ROOT_PATH *must* end with a forward-slash! # define('ROOT_PATH', '/support/'); + +# +# Session Storage Options +# --------------------------------------------------- +# Option: SESSION_BACKEND (default: db) +# +# osTicket supports Memcache as a session storage backend if the `memcache` +# pecl extesion is installed. This also requires MEMCACHE_SERVERS to be +# configured as well. +# +# MEMCACHE_SERVERS can be defined as a comma-separated list of host:port +# specifications. If more than one server is listed, the session is written +# to all of the servers for redundancy. +# +# Values: 'db' (default) +# 'memcache' (Use Memcache servers) +# 'system' (use PHP settings as configured (not recommended!)) +# +# define('SESSION_BACKEND', 'memcache'); +# define('MEMCACHE_SERVERS', 'server1:11211,server2:11211'); ?> diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index 5f6288db738b1cd3cd11fcbf7a1e389e70b2bcd6..5ef77f8707d4573e7aca06e4e22cefcfef5909d7 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -189,7 +189,7 @@ $info['id']=$staff->getId(); } ?> </select> - <em><?php echo __('(This can be selectected when replying to a ticket)');?></em> + <em><?php echo __('(This can be selected when replying to a ticket)');?></em> <span class="error"> <?php echo $errors['default_signature_type']; ?></span> </td> </tr> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 78844c8aa7a0ee3a6a2e3f3c457be3b514bd28cf..35fb34acacdc3334b36e1ffd7381ad9d82b72bd3 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -46,6 +46,8 @@ if (!$errors['err']) { $lock->getStaffName()); elseif (($emailBanned=Banlist::isBanned($ticket->getEmail()))) $errors['err'] = __('Email is in banlist! Must be removed before any reply/response'); + elseif (!Validator::is_valid_email($ticket->getEmail())) + $errors['err'] = __('EndUser email address is not valid! Consider updating it before responding'); } $unbannable=($emailBanned) ? BanList::includes($ticket->getEmail()) : false; diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 7a606522abd4dcec007a97bd5601a6cd654059d3..612f76f1759a62230fc2fc9a87410b7b9ed17ed7 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -31,6 +31,9 @@ $sort_options = array( $use_subquery = true; $queue_name = strtolower($_GET['status'] ?: $_GET['a']); //Status is overloaded +// Stash current queue view +$_SESSION['::Q'] = $queue_name; + switch ($queue_name) { case 'closed': $status='closed'; diff --git a/include/staff/user-view.inc.php b/include/staff/user-view.inc.php index 07085d4b9a44fe0440217bce5d68915d9579d792..29c573a608649af3242abe7bee5a972615eac07a 100644 --- a/include/staff/user-view.inc.php +++ b/include/staff/user-view.inc.php @@ -212,6 +212,7 @@ $(function() { window.location.href = 'users.php'; else window.location.href = window.location.href; + return false; }, { onshow: function() { $('#user-search').focus(); } }); diff --git a/include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql b/include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql index fd75833c30ca760b971aad04f7c0ef47bf936426..18d54b03313828b73b5344ef9816f175757c57eb 100644 --- a/include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql +++ b/include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql @@ -5,6 +5,7 @@ */ -- Group department access table +DROP TABLE IF EXISTS `%TABLE_PREFIX%group_dept_access`; CREATE TABLE `%TABLE_PREFIX%group_dept_access` ( `group_id` int(10) unsigned NOT NULL default '0', `dept_id` int(10) unsigned NOT NULL default '0', diff --git a/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql index 97c2bf97b45d6e5fa1368e8011d05e07d48c545e..96d97a041e64677848a90a0b67c116cfdfa47422 100644 --- a/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql +++ b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql @@ -96,6 +96,7 @@ INSERT INTO `%TABLE_PREFIX%sla` (`isactive`, `enable_priority_escalation`, VALUES (1, 1, 0, 48, 'Default SLA', NULL, NOW(), NOW()); -- Create a TEAM table +DROP TABLE IF EXISTS `%TABLE_PREFIX%team`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%team` ( `team_id` int(10) unsigned NOT NULL auto_increment, `lead_id` int(10) unsigned NOT NULL default '0', @@ -282,6 +283,7 @@ ALTER TABLE `%TABLE_PREFIX%kb_premade` ADD `notes` TEXT NOT NULL AFTER `response`, DROP INDEX `title`; +DROP TABLE IF EXISTS `%TABLE_PREFIX%canned_response`; ALTER TABLE `%TABLE_PREFIX%kb_premade` RENAME TO `%TABLE_PREFIX%canned_response`; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_category`; diff --git a/include/upgrader/streams/core/d0e37dca-1da1bcba.patch.sql b/include/upgrader/streams/core/d0e37dca-1da1bcba.patch.sql index 3d3bda68e2fd49293e1a73545506416c7a1cc126..2519a5bd32330f5df7df5b2389fd7b2d7aa99088 100644 --- a/include/upgrader/streams/core/d0e37dca-1da1bcba.patch.sql +++ b/include/upgrader/streams/core/d0e37dca-1da1bcba.patch.sql @@ -3,16 +3,18 @@ * @signature 1da1bcbafcedc65efef58f142a48ac91 * * Upgrade from 1.6 RC3 + filters - * + * */ +DROP TABLE IF EXISTS `%TABLE_PREFIX%filter`; RENAME TABLE `%TABLE_PREFIX%email_filter` TO `%TABLE_PREFIX%filter`; +DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_rule`; RENAME TABLE `%TABLE_PREFIX%email_filter_rule` TO `%TABLE_PREFIX%filter_rule`; ALTER TABLE `%TABLE_PREFIX%filter` CHANGE `reject_email` `reject_ticket` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0'; -ALTER TABLE `%TABLE_PREFIX%filter` +ALTER TABLE `%TABLE_PREFIX%filter` ADD `target` ENUM( 'Any', 'Web', 'Email', 'API' ) NOT NULL DEFAULT 'Any' AFTER `sla_id` , ADD INDEX ( `target` ); diff --git a/scp/autocron.php b/scp/autocron.php index eabc67151374c7d28eaf4895efe409f21652e5d9..d6ff919b359791e87caedd2f9b2c04dac7a397c0 100644 --- a/scp/autocron.php +++ b/scp/autocron.php @@ -39,14 +39,25 @@ ob_start(); //Keep the image output clean. Hide our dirt. $sec=time()-$_SESSION['lastcroncall']; $caller = $thisstaff->getUserName(); -if($sec>180 && $ost && !$ost->isUpgradePending()): //user can call cron once every 3 minutes. +// Agent can call cron once every 3 minutes. +if ($sec < 180 || !$ost || $ost->isUpgradePending()) + ob_end_clean(); + require_once(INCLUDE_DIR.'class.cron.php'); -$thisstaff = null; //Clear staff obj to avoid false credit internal notes & auto-assignment -Cron::TicketMonitor(); //Age tickets: We're going to age tickets regardless of cron settings. +// Clear staff obj to avoid false credit internal notes & auto-assignment +$thisstaff = null; + +// Release the session to prevent locking a future request while this is +// running +$_SESSION['lastcroncall'] = time(); +session_write_close(); + +// Age tickets: We're going to age tickets regardless of cron settings. +Cron::TicketMonitor(); -// Run file purging about every 30 minutes -if (mt_rand(1, 9) == 4) +// Run file purging about every 20 cron runs (1h40 on a five minute cron) +if (mt_rand(1, 20) == 4) Cron::CleanOrphanedFiles(); if($cfg && $cfg->isAutoCronEnabled()) { //ONLY fetch tickets if autocron is enabled! @@ -57,7 +68,5 @@ if($cfg && $cfg->isAutoCronEnabled()) { //ONLY fetch tickets if autocron is enab $data = array('autocron'=>true); Signal::send('cron', $data); -$_SESSION['lastcroncall']=time(); -endif; ob_end_clean(); ?> diff --git a/scp/emailtest.php b/scp/emailtest.php index 1246dff088799736f62941acf470f2901b335e1c..9e15baf36b1ccb10d0cd3cc93e87b94edf9d88c3 100644 --- a/scp/emailtest.php +++ b/scp/emailtest.php @@ -25,8 +25,8 @@ if($_POST){ if(!$_POST['email_id'] || !($email=Email::lookup($_POST['email_id']))) $errors['email_id']=__('Select from email address'); - if(!$_POST['email'] || !Validator::is_email($_POST['email'])) - $errors['email']=__('To email address required'); + if(!$_POST['email'] || !Validator::is_valid_email($_POST['email'])) + $errors['email']=__('Valid recipient email address required'); if(!$_POST['subj']) $errors['subj']=__('Subject required'); @@ -48,11 +48,12 @@ if($_POST){ $errors['err']=__('Error sending email - try again.'); } } -$info=Format::htmlchars(($errors && $_POST)?$_POST:$info); $nav->setTabActive('emails'); $ost->addExtraHeader('<meta name="tip-namespace" content="emails.diagnostic" />', "$('#content').data('tipNamespace', '".$tip_namespace."');"); require(STAFFINC_DIR.'header.inc.php'); + +$info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> <form action="emailtest.php" method="post" id="save"> <?php csrf_token(); ?> diff --git a/scp/tickets.php b/scp/tickets.php index c4a9591e25049e1a9c7292d089ce5a8c2a695ee4..4f0dcec4e3642c8628ed5bc0f743926d746becc2 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -36,9 +36,12 @@ if($_REQUEST['id']) { } //Lookup user if id is available. -if ($_REQUEST['uid']) +if ($_REQUEST['uid']) { $user = User::lookup($_REQUEST['uid']); - +} +elseif (!isset($_GET['status']) && isset($_SESSION['::Q'])) { + $_GET['status'] = $_REQUEST['status'] = $_SESSION['::Q']; +} // Configure form for file uploads $response_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php index f402e99e2fb355159983473232f6e6f8b710e8b8..7d0e87ecc21b86cb596e2b20067028a9f8ba7171 100644 --- a/setup/cli/modules/deploy.php +++ b/setup/cli/modules/deploy.php @@ -151,11 +151,12 @@ class Deployment extends Unpacker { # Locate the upload folder $root = $this->find_root_folder(); + $rootPattern = str_replace("\\","\\\\", $root); //need for windows case - $exclusions = array("$root/include", "$root/.git*", + $exclusions = array("$rootPattern/include", "$rootPattern/.git*", "*.sw[a-z]","*.md", "*.txt"); if (!$options['setup']) - $exclusions[] = "$root/setup"; + $exclusions[] = "$rootPattern/setup"; # Unpack everything but the include/ folder $this->unpackage("$root/{,.}*", $this->destination, -1, diff --git a/setup/install.php b/setup/install.php index 4a348cf632d24b3c7c62a406d80fc1bf86ca898c..1420f2dfab36db7ee48d874b9e127308b45dd32d 100644 --- a/setup/install.php +++ b/setup/install.php @@ -65,7 +65,7 @@ if($_POST && $_POST['s']) { if(!$_POST['email']) $errors['email'] = __('Required'); - elseif(!Validator::is_email($_POST['email'])) + elseif(!Validator::is_valid_email($_POST['email'])) $errors['email'] = __('Invalid'); if(!$_POST['alerts'] && !$_POST['news']) diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php index 83a6cdd297a4688f96528aeee46a9360fa0ae9f1..620f763a55090b6bb423e2b36a6b378f44255a8e 100644 --- a/setup/test/tests/stubs.php +++ b/setup/test/tests/stubs.php @@ -163,4 +163,12 @@ class NumberFormatter { class Aws_Route53_Client { function changeResourceRecordSets() {} } + +class Memcache { + function addServer() {} + function pconnect() {} + function replace() {} + function set() {} + function get() {} +} ?>