diff --git a/WHATSNEW.md b/WHATSNEW.md index cd03e65ce837b16d3fb34b45f30fbfe8c3592237..ce06e6a8acee174be1ff20dd5a6822deb904a771 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,3 +1,71 @@ +osTicket v1.9.4 +=============== +### Major New Features + * New ticket states (resolved, archived, and deleted) (#1094, #1159) + * Custom ticket statuses (#1159) + * Custom ticket number formats (#1128) + * Full text search capabilities (*beta*) + * Multiselect for choice fields and custom list selections + * Phase II Multi-Lingual Support (User Interface) (see + http://i18n.osticket.com and http://jipt.i18n.osticket.com) (#1096) + * Active interface translations of 46 languages currently + * Popup help tip documentation in all languages + * Flags displayed on client portal for manual switch of UI language by + EndUsers + * Automatic detection of enduser and agent language preference as + advertised by the browser + * Improved PDF ticket printing support, including greater support for + eastern characters such as Thai, Korean, Chinese, and Japanese + * Proper support for searching, including breaking words for languages + which do not use word breaks, such as Japanese + * Proper user interface layout for right-to-left languages such as Hebrew, + Arabic, and Farsi + * Right-to-Left support for the HTML text editor, regardless of the viewing + user’s current language setting + * Proper handling of bidirectional text in PDF output and in the ticket + view + +### Enhancements + * Plugins can have custom configurations (#1156) + * Upgrade to mPDF to v5.7.3 (#1356) + * Add support for PDF fonts in language packs (#1356) + * Advanced search improved to support multiple selections, custom status and flags + +### Improvements + * Fix display of text thread entries with HTML characters (`<`) (#1360) + * Fix crash creating new ticket if organization custom data has a selection field (#1361) + * Fix footer disappearance on PJAX navigation (#1366) + * Fix User Directory not sortable by user status (#1375) + * Fix loss of enduser or agent priority selection on new ticket (#1365) + * Add validation error if setting EndUser username to an email address (#1368) + * Fix skipped validation of some fields (#1369) (*regression from rc4*) + * Fix detection of inline attachments from rich text inputs (#1357) + * Fix dropping attachments when updating canned responses (#1357) + * Fix PJAX navigation crash in some browsers (#1378) + * Fix searching for tickets in the client portal (#1379) (*regression from rc4*) + * Fix crash submitting new ticket as agent with validation errors (#1380) + * Fix display of unanswered tickets in open queue (#1384) + * Fix incorrect statistics on dashboard page (#1345) + * Fix sorting by ticket number if using sequential numbers + * Fix threading if HTML is enabled and QR is disabled (#1197) + * Export ticket `created` date (#1201) + * Fix duplicate email where a collaborator would receive a confirmation + for his own message (#1235) + * Fix multi-line display of checkbox descriptions (#1160) + * Fix API validation failure for custom list selections (#1238) + * Fix crash adding a new user with a selection field custom data + * Fix failed user identification from email headers if `References` header + is sorted differently be mail client (#1263) + * Fix deletion of inline images on pages if draft was not saved (#1288) + * Fix corruption of custom date time fields on client portal if using non + US date format (#1320) + * Fix corruption of email mailbox if improperly encoded as ISO-8859-1 + without RFC 2047 charset hint (#1332) + * Fix occasional MySQL Commands OOS error from ORM (#1334) + +### Performance and Security + * Fix possible XSS vulnerability in email template management (#1163) + osTicket v1.9.3 =============== ### Enhancements diff --git a/ajax.php b/ajax.php index bfa481a20aed7cf8c1d6134ff835a46584dcbb0e..8ea5226439f971b9cd917a13f53c099579394ed1 100644 --- a/ajax.php +++ b/ajax.php @@ -31,6 +31,7 @@ $dispatcher = patterns('', )), url('^/draft/', patterns('ajax.draft.php:DraftAjaxAPI', url_post('^(?P<id>\d+)$', 'updateDraftClient'), + url_delete('^(?P<id>\d+)$', 'deleteDraftClient'), url_post('^(?P<id>\d+)/attach$', 'uploadInlineImageClient'), url_get('^(?P<namespace>[\w.]+)$', 'getDraftClient'), url_post('^(?P<namespace>[\w.]+)$', 'createDraftClient') diff --git a/include/ajax.draft.php b/include/ajax.draft.php index 41fde2be24ff955b793f1fbf773b9d8372616c0b..f727e2cc9a10040786f6abb3502a8a21f7e04806 100644 --- a/include/ajax.draft.php +++ b/include/ajax.draft.php @@ -186,6 +186,23 @@ class DraftAjaxAPI extends AjaxController { return self::_updateDraft($draft); } + function deleteDraftClient($id) { + global $thisclient; + + if (!($draft = Draft::lookup($id))) + Http::response(205, "Draft not found. Create one first"); + elseif ($thisclient) { + if ($draft->getStaffId() != $thisclient->getId()) + Http::response(404, "Draft not found"); + } + else { + if (substr(session_id(), -12) != substr($draft->getNamespace(), -12)) + Http::response(404, "Draft not found"); + } + + $draft->delete(); + } + function uploadInlineImageClient($id) { global $thisclient; diff --git a/include/class.config.php b/include/class.config.php index 8a7d5eebdb6d53a6d23d5667d543ded34ad8b5ca..04a7cea4546bdf65430034564fd3d2812a5fc50a 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -523,15 +523,6 @@ class OsticketConfig extends Config { return $this->get('max_file_size'); } - function getStaffMaxFileUploads() { - return $this->get('max_staff_file_uploads'); - } - - function getClientMaxFileUploads() { - //TODO: change max_user_file_uploads to max_client_file_uploads - return $this->get('max_user_file_uploads'); - } - function getLogLevel() { return $this->get('log_level'); } @@ -971,7 +962,6 @@ class OsticketConfig extends Config { 'enable_html_thread'=>isset($vars['enable_html_thread'])?1:0, 'allow_client_updates'=>isset($vars['allow_client_updates'])?1:0, 'max_file_size'=>$vars['max_file_size'], - 'email_attachments'=>isset($vars['email_attachments'])?1:0, )); } @@ -1005,6 +995,7 @@ class OsticketConfig extends Config { 'accept_unregistered_email'=>isset($vars['accept_unregistered_email'])?1:0, 'add_email_collabs'=>isset($vars['add_email_collabs'])?1:0, 'reply_separator'=>$vars['reply_separator'], + 'email_attachments'=>isset($vars['email_attachments'])?1:0, )); } diff --git a/include/class.cron.php b/include/class.cron.php index af5a1476865f7cf11f40f1b39f195f11a1660f63..f231ac5b3db00695cfa0a0a1045ff62d0bec9745 100644 --- a/include/class.cron.php +++ b/include/class.cron.php @@ -98,7 +98,9 @@ class Cron { self::MailFetcher(); self::TicketMonitor(); self::PurgeLogs(); - self::CleanOrphanedFiles(); + // Run file purging about every 10 cron runs + if (mt_rand(1, 9) == 4) + self::CleanOrphanedFiles(); self::PurgeDrafts(); self::MaybeOptimizeTables(); diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 1b31e882d75025c248c48969627a2af75d617471..94b9b786e76c6cff6afede8f44f12ba509939cdd 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -690,7 +690,7 @@ class DynamicFormEntry extends VerySimpleModel { function getForm() { if (!isset($this->_form)) { $this->_form = DynamicForm::lookup($this->get('form_id')); - if (isset($this->id)) + if ($this->_form && isset($this->id)) $this->_form->data($this); } return $this->_form; @@ -1122,12 +1122,20 @@ class SelectionField extends FormField { } function to_php($value, $id=false) { - $value = ($value && !is_array($value)) - ? JsonDataParser::parse($value) : $value; + if (is_string($value)) + $value = JsonDataParser::parse($value) ?: $value; + if (!is_array($value)) { + $config = $this->getConfiguration(); + if (!$config['multiselect']) { + // CDATA may be built with comma-list + list($value,) = explode(',', $value, 2); + } $choices = $this->getChoices(); if (isset($choices[$value])) - $value = $choices[$value]; + $value = array($value => $choices[$value]); + elseif ($id && isset($choices[$id])) + $value = array($id => $choices[$id]); } // Don't set the ID here as multiselect prevents using exactly one // ID value. Instead, stick with the JSON value only. diff --git a/include/class.error.php b/include/class.error.php index 7e9ecb8a9cc15cc45d422fd7a13d39278af95091..72909a615e2a61cde374ccdcba9213a80f4e2515 100644 --- a/include/class.error.php +++ b/include/class.error.php @@ -27,7 +27,7 @@ class Error extends Exception { parent::__construct(__($message)); $message = str_replace(ROOT_DIR, '(root)/', _S($message)); - if ($ost->getConfig()->getLogLevel() == 3) + if ($ost && $ost->getConfig()->getLogLevel() == 3) $message .= "\n\n" . $this->getBacktrace(); $ost->logError($this->getTitle(), $message, static::$sendAlert); diff --git a/include/class.format.php b/include/class.format.php index 55635f5aab7d6cd57dc6ad9b90c3cc9ff37b6ce3..2357e45e5583990767b37298d5dd6ced66517fed 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -30,6 +30,16 @@ class Format { return round(($bytes/1048576),1).' mb'; } + function filesize2bytes($size) { + switch (substr($size, -1)) { + case 'M': case 'm': return (int)$size <<= 20; + case 'K': case 'k': return (int)$size <<= 10; + case 'G': case 'g': return (int)$size <<= 30; + } + + return $size; + } + /* encode text into desired encoding - taking into accout charset when available. */ function encode($text, $charset=null, $encoding='utf-8') { diff --git a/include/class.forms.php b/include/class.forms.php index 54ef6e9003821df2da2091adcf0b0125b01f61e1..c80c34ba1567cea62f7cb38150e11b020ca39228 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -676,7 +676,7 @@ class TextboxField extends FormField { $wrapped = "/".$value."/iu"; if (false === @preg_match($value, ' ') && false !== @preg_match($wrapped, ' ')) { - return $wrapped; + $value = $wrapped; } if ($value == '//iu') return ''; @@ -950,16 +950,22 @@ class ChoiceField extends FormField { else $array = $value; $config = $this->getConfiguration(); - if (is_array($array) && !$config['multiselect'] && count($array) < 2) { - reset($array); - return key($array); + if (!$config['multiselect']) { + if (is_array($array) && count($array) < 2) { + reset($array); + return key($array); + } + if (is_string($array) && strpos($array, ',') !== false) { + list($array,) = explode(',', $array, 2); + } } return $array; } function toString($value) { $selection = $this->getChoice($value); - return is_array($selection) ? implode(', ', array_filter($selection)) + return is_array($selection) + ? (implode(', ', array_filter($selection)) ?: $value) : (string) $selection; } @@ -1128,7 +1134,9 @@ class ThreadEntryField extends FormField { $attachments = new FileUploadField(); $fileupload_config = $attachments->getConfigurationOptions(); - $fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes()); + if ($cfg->getAllowedFileTypes()) + $fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes()); + return array( 'attachments' => new BooleanField(array( 'label'=>__('Enable Attachments'), @@ -1831,7 +1839,7 @@ class ChoicesWidget extends Widget { $values = array($values => $this->field->getChoice($values)); } - if ($values === null) + if (!is_array($values)) $values = $have_def ? array($def_key => $choices[$def_key]) : array(); ?> diff --git a/include/class.i18n.php b/include/class.i18n.php index 4a9540520c26816b0c405ccfe318402ecf6ef840..4a0d138f295bec3586369127a16d255d3d51e61a 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -104,8 +104,18 @@ class Internationalization { } } - // Pages and content + // Load core config $_config = new OsticketConfig(); + + // Determine reasonable default max_file_size + $max_size = Format::filesize2bytes(strtoupper(ini_get('upload_max_filesize'))); + $val = ((int) $max_size/2); + $po2 = 1; + while( $po2 < $val ) $po2 <<= 1; + + $_config->set('max_file_size', $po2); + + // Pages and content foreach (array('landing','thank-you','offline', 'registration-staff', 'pwreset-staff', 'banner-staff', 'registration-client', 'pwreset-client', 'banner-client', diff --git a/include/class.list.php b/include/class.list.php index dfee67d558d3ef2a56ac81f16e14fa8a090c82a7..f5f9c4bd2551f5605dc4228da9b827e6c62ac010 100644 --- a/include/class.list.php +++ b/include/class.list.php @@ -109,10 +109,15 @@ abstract class CustomListHandler { return $rv; } + function __get($field) { + return $this->_list->{$field}; + } + function update($vars, &$errors) { return $this->_list->update($vars, $errors); } + abstract function getListOrderBy(); abstract function getNumItems(); abstract function getAllItems(); abstract function getItems($criteria); @@ -334,6 +339,7 @@ class DynamicList extends VerySimpleModel implements CustomList { $this->set('updated', new SqlFunction('NOW')); if (isset($this->dirty['notes'])) $this->notes = Format::sanitize($this->notes); + return parent::save($refetch); } @@ -656,6 +662,14 @@ class TicketStatusList extends CustomListHandler { var $_items; var $_form; + function getListOrderBy() { + switch ($this->getSortMode()) { + case 'Alpha': return 'name'; + case '-Alpha': return '-name'; + case 'SortCol': return 'sort'; + } + } + function getNumItems() { return TicketStatus::objects()->count(); } @@ -774,8 +788,6 @@ class TicketStatus extends VerySimpleModel implements CustomListItem { const ENABLED = 0x0001; const INTERNAL = 0x0002; // Forbid deletion or name and status change. - - function __construct() { call_user_func_array(array('parent', '__construct'), func_get_args()); } diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 4e6546549f44d7ad520670bb01af8e946534aaea..1210bffa3f3bfd924169e87939d369c229a4a9ce 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -138,7 +138,7 @@ class MailFetcher { $args += array(NULL, 0, array( 'DISABLE_AUTHENTICATOR' => array('GSSAPI', 'NTLM'))); - $this->mbox = call_user_func_array('imap_open', $args); + $this->mbox = @call_user_func_array('imap_open', $args); return $this->mbox; } diff --git a/include/class.search.php b/include/class.search.php index c3b2b2c1e0cb2ef30aef3ddd43cadc53d2bf15d1..fd8c7e2cff06868b63439c980252d4b4719a71a0 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -257,12 +257,34 @@ class MysqlSearchBackend extends SearchBackend { return db_query($sql); } + // Quote things like email addresses + function quote($query) { + $parts = array(); + if (!preg_match_all('`([^\s"\']+)|"[^"]*"|\'[^\']*\'`', $query, $parts, + PREG_SET_ORDER)) + return $query; + + $results = array(); + foreach ($parts as $m) { + // Check for quoting + if ($m[1] // Already quoted? + && preg_match('`@`u', $m[0]) + ) { + $char = strpos($m[1], '"') ? "'" : '"'; + $m[0] = $char . $m[0] . $char; + } + $results[] = $m[0]; + } + return implode(' ', $results); + } + function find($query, $criteria=array(), $model=false, $sort=array()) { global $thisstaff; $mode = ' IN BOOLEAN MODE'; #if (count(explode(' ', $query)) == 1) # $mode = ' WITH QUERY EXPANSION'; + $query = $this->quote($query); $search = 'MATCH (search.title, search.content) AGAINST (' .db_input($query) .$mode.')'; @@ -403,14 +425,30 @@ class MysqlSearchBackend extends SearchBackend { } static function createSearchTable() { - $sql = 'CREATE TABLE IF NOT EXISTS '.TABLE_PREFIX.'_search ( + // Use InnoDB with Galera, MyISAM with v5.5, and the database + // default otherwise + $sql = "select count(*) from information_schema.tables where + table_schema='information_schema' and table_name = + 'INNODB_FT_CONFIG'"; + $mysql56 = db_result(db_query($sql)); + + $sql = "show status like 'wsrep_local_state'"; + $galera = db_result(db_query($sql)); + + if ($galera && !$mysql56) + throw new Exception('Galera cannot be used with MyISAM tables'); + $engine = $galera ? 'InnodB' : ($mysql56 ? '' : 'MyISAM'); + if ($engine) + $engine = 'ENGINE='.$engine; + + $sql = 'CREATE TABLE IF NOT EXISTS '.TABLE_PREFIX."_search ( `object_type` varchar(8) not null, `object_id` int(11) unsigned not null, `title` text collate utf8_general_ci, `content` text collate utf8_general_ci, primary key `object` (`object_type`, `object_id`), fulltext key `search` (`title`, `content`) - ) ENGINE=MyISAM CHARSET=utf8'; + ) $engine CHARSET=utf8"; return db_query($sql); } diff --git a/include/class.sequence.php b/include/class.sequence.php index 1b3fc3182ec1e890d8517d3c2969818d862fb40a..cc27801c596a8dd17acac088286e15bc7de6b4aa 100644 --- a/include/class.sequence.php +++ b/include/class.sequence.php @@ -151,6 +151,9 @@ class Sequence extends VerySimpleModel { * and assured to be session-wise atomic before the value is returned. */ function __next($digits=false) { + // Ensure this block is executed in a single transaction + db_autocommit(false); + // Lock the database object -- this is important to handle concurrent // requests for new numbers static::objects()->filter(array('id'=>$this->id))->lock()->one(); @@ -161,6 +164,8 @@ class Sequence extends VerySimpleModel { $this->updated = SqlFunction::NOW(); $this->save(); + db_autocommit(true); + return $next; } diff --git a/include/class.thread.php b/include/class.thread.php index c009ed2fbe4c4323a1138b0f6cdb2ac665a62aa0..b57da97133ac53e51f11bb8fd87a3ed6370ef1a6 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -1427,7 +1427,7 @@ class TextThreadBody extends ThreadBody { } class HtmlThreadBody extends ThreadBody { function __construct($body, $options=array()) { - if ($options['strip-embedded']) + if (!isset($options['strip-embedded']) || $options['strip-embedded']) $body = $this->extractEmbeddedHtmlImages($body); parent::__construct($body, 'html', $options); } diff --git a/include/class.ticket.php b/include/class.ticket.php index c4b6d3e94a36cb9c38b83ad0a411f4779151c9d9..e65347a03663ef74276f52e78f054a175856d7bc 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -54,12 +54,6 @@ class Ticket { var $thread; //Thread obj. - // Status -- listed here until we have a formal status class - static $STATUSES = array( - /* @trans */ 'open', - /* @trans */ 'closed', - ); - function Ticket($id) { $this->id = 0; $this->load($id); @@ -2368,11 +2362,14 @@ class Ticket { // Create and verify the dynamic form entry for the new ticket $form = TicketForm::getNewInstance(); $form->setSource($vars); - // If submitting via email, ensure we have a subject and such - foreach ($form->getFields() as $field) { - $fname = $field->get('name'); - if ($fname && isset($vars[$fname]) && !$field->value) - $field->value = $field->parse($vars[$fname]); + + // If submitting via email or api, ensure we have a subject and such + if (!in_array(strtolower($origin), array('web', 'staff'))) { + foreach ($form->getFields() as $field) { + $fname = $field->get('name'); + if ($fname && isset($vars[$fname]) && !$field->value) + $field->value = $field->parse($vars[$fname]); + } } if (!$form->isValid($field_filter('ticket'))) diff --git a/include/class.topic.php b/include/class.topic.php index 1dd6d0b48f67a6fa9bb3e2c5bbf30e463337990f..9139f9e6d0926efe6d27bc2dc9145ef02f17b151 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -256,7 +256,7 @@ class Topic { static function getHelpTopics($publicOnly=false, $disabled=false) { global $cfg; - static $topics, $names; + static $topics, $names = array(); if (!$names) { $sql = 'SELECT topic_id, topic_pid, ispublic, isactive, topic FROM '.TOPIC_TABLE diff --git a/include/client/header.inc.php b/include/client/header.inc.php index 4cb8b22ca788748a2e928abbafbd2308cbbf5fb5..92e9dfe13bdda7cc9ad08fc6cedb9b6f7c0d0b7b 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -63,7 +63,7 @@ if (($lang = Internationalization::getCurrentLanguage()) && !$thisclient->isGuest()) { echo Format::htmlchars($thisclient->getName()).' |'; ?> - <a href="<?php echo ROOT_PATH; ?>account.php"><?php echo __('Profile'); ?></a> | + <a href="<?php echo ROOT_PATH; ?>profile.php"><?php echo __('Profile'); ?></a> | <a href="<?php echo ROOT_PATH; ?>tickets.php"><?php echo sprintf(__('Tickets <b>(%d)</b>'), $thisclient->getNumTickets()); ?></a> - <a href="<?php echo $signout_url; ?>"><?php echo __('Sign Out'); ?></a> <?php diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index 2c1a04a97c208e2bdadc6281cca2ee0cd093e9c0..6b8e9df1af38ee40b56dcc831ad260c156d5e235 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -157,11 +157,14 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting </thead> <tbody> <?php + $subject_field = TicketForm::objects()->one()->getField('subject'); if($res && ($num=db_num_rows($res))) { $defaultDept=Dept::getDefaultDeptName(); //Default public dept. while ($row = db_fetch_array($res)) { $dept= $row['ispublic']? $row['dept_name'] : $defaultDept; - $subject=Format::htmlchars(Format::truncate($row['subject'],40)); + $subject = Format::truncate($subject_field->display( + $subject_field->to_php($row['subject']) ?: $row['subject'] + ), 40); if($row['attachments']) $subject.=' <span class="Icon file"></span>'; diff --git a/include/i18n/en_US/config.yaml b/include/i18n/en_US/config.yaml index a63764ba9c1b3b1aeb8b56488c0cbc6c4aa4409e..b29df10ace86324407ba25ab5605ed7d569cdf04 100644 --- a/include/i18n/en_US/config.yaml +++ b/include/i18n/en_US/config.yaml @@ -15,7 +15,6 @@ core: reply_separator: '-- reply above this line --' # Do not translate below here - allowed_filetypes: '.doc, .pdf, .jpg, .jpeg, .gif, .png, .xls, .docx, .xlsx, .txt' isonline: 1 staff_ip_binding: 0 staff_max_logins: 4 @@ -27,9 +26,6 @@ core: client_session_timeout: 30 max_page_size: 25 max_open_tickets: 0 - max_file_size: 1048576 - max_user_file_uploads: 1 - max_staff_file_uploads: 1 autolock_minutes: 3 default_priority_id: 2 default_smtp_id: 0 diff --git a/include/mysqli.php b/include/mysqli.php index 71681975cd8bdd8e1281a3290eac9234690fa6af..ed70cd82ef3c5fc8ae0f209759ed9c969f2ceb61 100644 --- a/include/mysqli.php +++ b/include/mysqli.php @@ -76,18 +76,7 @@ function db_connect($host, $user, $passwd, $options = array()) { @db_set_variable('sql_mode', ''); - // Start a new transaction -- for performance. Transactions are always - // committed at shutdown (below) - $__db->autocommit(false); - - // Auto commit the transaction at shutdown and re-enable statement-level - // autocommit - register_shutdown_function(function() { - global $__db, $err; - if (!$__db->commit()) - $err = 'Unable to save changes to database'; - $__db->autocommit(true); - }); + $__db->autocommit(true); // Use connection timing to seed the random number generator Misc::__rand_seed((microtime(true) - $start) * 1000000); @@ -95,6 +84,12 @@ function db_connect($host, $user, $passwd, $options = array()) { return $__db; } +function db_autocommit($enable=true) { + global $__db; + + return $__db->autocommit($enable); +} + function db_close() { global $__db; return @$__db->close(); diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php index f42abef7c27a9b035c5d4927147219bed01a5ad4..0b4ed80996b418ff19d6bf020dc7bc190e08ef37 100644 --- a/include/staff/dynamic-list.inc.php +++ b/include/staff/dynamic-list.inc.php @@ -8,7 +8,7 @@ if ($list) { $info = $list->getInfo(); $newcount=2; } else { - $title = __('Add new custom list'); + $title = __('Add New Custom List'); $action = 'add'; $submit_text = __('Add List'); $newcount=4; diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php index 228791074b65c01b5afd8d3ca254a71ee12d69e9..b10facabe98d61a05eccf10736e37b4269721c20 100644 --- a/include/staff/settings-emails.inc.php +++ b/include/staff/settings-emails.inc.php @@ -153,6 +153,14 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) <i class="help-tip icon-question-sign" href="#default_mta"></i> </td> </tr> + <tr> + <td width="180"><?php echo __('Attachments');?>:</td> + <td> + <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?>> + <?php echo __('Email attachments to the user'); ?> + <i class="help-tip icon-question-sign" href="#ticket_response_files"></i> + </td> + </tr> </tbody> </table> <p style="padding-left:250px;"> diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index 2e23da95068b404e91ace8b02749690fb2b9a802..d64c52ed12a4f7e4148598e75d292a93430cbe59 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -268,14 +268,6 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) <div class="error"><?php echo $errors['max_file_size']; ?></div> </td> </tr> - <tr> - <td width="180"><?php echo __('Ticket Response Files');?>:</td> - <td> - <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?>> - <?php echo __('Email attachments to the user'); ?> - <i class="help-tip icon-question-sign" href="#ticket_response_files"></i> - </td> - </tr> <?php if (($bks = FileStorageBackend::allRegistered()) && count($bks) > 1) { ?> <tr> diff --git a/include/staff/templates/ticket-status.tmpl.php b/include/staff/templates/ticket-status.tmpl.php index c033e0caa8d541b2a9bbed596fb3e8ba2d8c7c80..5e0d84ae3c82be28c5880b5444ffc65ea513a279 100644 --- a/include/staff/templates/ticket-status.tmpl.php +++ b/include/staff/templates/ticket-status.tmpl.php @@ -110,9 +110,12 @@ $action = $info['action'] ?: ('#tickets/status/'. $state); $(function() { // Copy checked tickets to status form. $('form#tickets input[name="tids[]"]:checkbox:checked') - .clone() - .prop('type', 'hidden') - .removeAttr('class') - .appendTo('form#status'); - }); + .each(function() { + $('<input>') + .prop('type', 'hidden') + .attr('name', 'tids[]') + .val($(this).val()) + .appendTo('form#status'); + }); +}); </script> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index f6c17982c7aa39e88027b0b3f46a0c171e045360..70acec9195642decac3530a7be2267fb00a176b2 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -406,6 +406,8 @@ if ($results) { </thead> <tbody> <?php + // Setup Subject field for display + $subject_field = TicketForm::objects()->one()->getField('subject'); $class = "row1"; $total=0; if($res && ($num=count($results))): @@ -430,7 +432,10 @@ if ($results) { $lc=Format::truncate($row['dept_name'],40); } $tid=$row['number']; - $subject = Format::htmlchars(Format::truncate($row['subject'],40)); + + $subject = Format::truncate($subject_field->display( + $subject_field->to_php($row['subject']) ?: $row['subject'] + ), 40); $threadcount=$row['thread_count']; if(!strcasecmp($row['state'],'open') && !$row['isanswered'] && !$row['lock_id']) { $tid=sprintf('<b>%s</b>',$tid); diff --git a/js/filedrop.field.js b/js/filedrop.field.js index d07a2a675455cf3c5af0f8046a82aa5822c4497b..4eb5d27e91ebdb78033b0e6cdab7e4d20b57f0f8 100644 --- a/js/filedrop.field.js +++ b/js/filedrop.field.js @@ -554,7 +554,7 @@ if (fileIndex === files_count) { return; } - var reader = new FileReader(), + var reader = new window.FileReader(), max_file_size = 1048576 * opts.maxfilesize; reader.index = fileIndex; @@ -807,26 +807,4 @@ function empty() {} - try { - if (XMLHttpRequest.prototype.sendAsBinary) { - return; - } - XMLHttpRequest.prototype.sendAsBinary = function(datastr) { - function byteValue(x) { - return x.charCodeAt(0) & 0xff; - } - var ords = Array.prototype.map.call(datastr, byteValue); - var ui8a = new Uint8Array(ords); - - // Not pretty: Chrome 22 deprecated sending ArrayBuffer, moving instead - // to sending ArrayBufferView. Sadly, no proper way to detect this - // functionality has been discovered. Happily, Chrome 22 also introduced - // the base ArrayBufferView class, not present in Chrome 21. - if ('ArrayBufferView' in window) - this.send(ui8a); - else - this.send(ui8a.buffer); - }; - } catch (e) {} - })(jQuery); diff --git a/logout.php b/logout.php index 4b9ea91b133fae4adf96823b8037428d1ed5acd0..74d73cc377b58049f286551771ff880dfc800fab 100644 --- a/logout.php +++ b/logout.php @@ -16,10 +16,9 @@ require('client.inc.php'); //Check token: Make sure the user actually clicked on the link to logout. -if(!$thisclient || !$_GET['auth'] || !$ost->validateLinkToken($_GET['auth'])) - @header('Location: index.php'); +if ($thisclient && $_GET['auth'] && $ost->validateLinkToken($_GET['auth'])) + $thisclient->logOut(); -$thisclient->logOut(); -header('Location: index.php'); -require('index.php'); + +Http::redirect('index.php'); ?> diff --git a/scp/autocron.php b/scp/autocron.php index b6016399e0b4745c62891dd7cce4b3f77df6fcca..eabc67151374c7d28eaf4895efe409f21652e5d9 100644 --- a/scp/autocron.php +++ b/scp/autocron.php @@ -44,6 +44,11 @@ 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. + +// Run file purging about every 30 minutes +if (mt_rand(1, 9) == 4) + Cron::CleanOrphanedFiles(); + if($cfg && $cfg->isAutoCronEnabled()) { //ONLY fetch tickets if autocron is enabled! Cron::MailFetcher(); //Fetch mail. $ost->logDebug(_S('Auto Cron'), sprintf(_S('Mail fetcher cron call [%s]'), $caller)); diff --git a/scp/js/dashboard.inc.js b/scp/js/dashboard.inc.js index b16aebf103a94e8c0f22e873e28681227e797da1..c902e4be2d77352909602d2d72b31758909136a8 100644 --- a/scp/js/dashboard.inc.js +++ b/scp/js/dashboard.inc.js @@ -140,7 +140,7 @@ method: 'GET', dataType: 'json', url: 'ajax.php/report/overview/table', - data: {group: group, start: start, stop: stop}, + data: {group: group, start: start, period: stop}, success: function(json) { var q = $('<table>').attr({'class':'table table-condensed table-striped'}), h = $('<tr>').appendTo($('<thead>').appendTo(q)), diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php index 19aa38cee7e62b6b24cdde477c24173740052ca9..f402e99e2fb355159983473232f6e6f8b710e8b8 100644 --- a/setup/cli/modules/deploy.php +++ b/setup/cli/modules/deploy.php @@ -106,10 +106,10 @@ class Deployment extends Unpacker { return parent::copyFile($src, $dest); $source = file_get_contents($src); - $source = preg_replace(':<script(.*) src="(.*).js"></script>:', + $source = preg_replace(':<script(.*) src="([^"]+)\.js"></script>:', '<script$1 src="$2.js?'.$short.'"></script>', $source); - $source = preg_replace(':<link(.*) href="(.*).css"([^/>]*)/?>:', # <?php + $source = preg_replace(':<link(.*) href="([^"]+)\.css"([^/>]*)/?>:', # <?php '<link$1 href="$2.css?'.$short.'"$3/>', $source); // Set THIS_VERSION diff --git a/setup/cli/modules/i18n.php b/setup/cli/modules/i18n.php index 57a8bd0abf91f0d5fc6d016c22c56928bfeb9199..fca97e411f50560c9e8a4e3d0b5b1d18f026edb5 100644 --- a/setup/cli/modules/i18n.php +++ b/setup/cli/modules/i18n.php @@ -253,8 +253,6 @@ class i18n_Compiler extends Module { ); } - list($code, $zip) = $this->_request("download/$lang.zip"); - // Include a manifest include_once INCLUDE_DIR . 'class.mailfetch.php';