diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..8246670fb05e453860e1a84426c4fb8e8a49cde5 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ +### Prerequisites + +* [ ] Can you reproduce the problem in a fresh installation of the "develop" branch? +* [ ] Do you have any errors in the PHP error log, or javascript console? +* [ ] Did you check the [osTicket forums](http://osticket.com/forum/categories)? +* [ ] Did you [perform a cursory search](https://github.com/osTicket/osTicket/issues) to see if your bug or enhancement is already reported? + +For more information on how to write a good [bug report](https://github.com/osTicket/osTicket/wiki/Github-Issues-Guidelines) + +### Description + +[Description of the bug or feature] + +### Steps to Reproduce + +1. [First Step] +2. [Second Step] +3. [and so on...] + +**Expected behavior:** [What you expected to happen] + +**Actual behavior:** [What actually happened] + +### Versions + +Admin panel -> Dashboard -> Information which also additionally gives you information about you server. + +Also, please include the OS and what version of the OS you're running. As well as your browser and browser version. diff --git a/WHATSNEW.md b/WHATSNEW.md index b7162b2285383954e7fa8bae4159b4a83f0d3772..f10997f753c6ff10ac5022112dfef7cacb76ddca 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,3 +1,28 @@ +osTicket v1.10 +============== +### Enhancements + * Support Passive Email Threading (#3276) + * Account for agents name format setting when sorting agents (#3274, 5c548c7) + * Ticket Filters: Support Lookup By Name (#3274, ef9b743) + * Enable preloaded canned responses by default (#3274, 7267531) + +### Improvements + * Task: Missing Description on create (#3274, 865db9) + * Save task due date on create (#3438) + * Show overlay on forms submit (#3426, #3391) + * upgrader: Fix crash on SequenceLoader (#3421) + * upgrader: Fix undefined js function when upgrading due to stale JS file (#3424) + * Use help topic as the subject line when issue summary is disabled (#3274, 74bdc02) + * PEAR: Turn off peer name verification by default (SMTP) (#3274, 4f68aeb) + * Cast orm objects to string when doing db_real_escape (#3274, e63ba58) + * Save department on __create (#3274, c664c93) + * Limit records to be indexed per cron run to 500 (#3274, 9174bab) + +### Performance and Security + * Fix memory leak when applying 'Use Reply-To Email' ticket filter action (#3437, 84f085d) + * XSS: Sanitize and validate HTTP_X_FORWARDED_FOR header (#3439, b794c599) + * XSS: Encode html chars on help desk title/name (#3439, a57de770) + osTicket v1.10-rc.3 =================== ### Enhancements diff --git a/bootstrap.php b/bootstrap.php index e70c79a6bb9801b06c49e4aaa5349a93e84b6de6..fe3dd2155b8fdd0e2dbb21d249d5eeba247fadc0 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -50,11 +50,7 @@ class Bootstrap { } function https() { - return - (isset($_SERVER['HTTPS']) - && strtolower($_SERVER['HTTPS']) == 'on') - || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'); + return osTicket::is_https(); } static function defineTables($prefix) { @@ -335,6 +331,7 @@ ini_set('include_path', './'.PATH_SEPARATOR.INCLUDE_DIR.PATH_SEPARATOR.PEAR_DIR) require(INCLUDE_DIR.'class.osticket.php'); require(INCLUDE_DIR.'class.misc.php'); require(INCLUDE_DIR.'class.http.php'); +require(INCLUDE_DIR.'class.validator.php'); // Determine the path in the URI used as the base of the osTicket // installation @@ -346,12 +343,7 @@ Bootstrap::init(); #CURRENT EXECUTING SCRIPT. define('THISPAGE', Misc::currentURL()); -define('DEFAULT_MAX_FILE_UPLOADS',ini_get('max_file_uploads')?ini_get('max_file_uploads'):5); -define('DEFAULT_PRIORITY_ID',1); +define('DEFAULT_MAX_FILE_UPLOADS', ini_get('max_file_uploads') ?: 5); +define('DEFAULT_PRIORITY_ID', 1); -#Global override -if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) - // Take the left-most item for X-Forwarded-For - $_SERVER['REMOTE_ADDR'] = trim(array_pop( - explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']))); ?> diff --git a/css/flags.css b/css/flags.css index 8e67320909401edbc33f4d4495ebd4d463cb9506..b517a8eaf28154b0ef4b18b0cd856f4e30e84759 100644 --- a/css/flags.css +++ b/css/flags.css @@ -70,7 +70,7 @@ .flag.flag-england {background-position: -240px -33px} .flag.flag-er {background-position: 0 -44px} .flag.flag-es {background-position: -16px -44px} -.flag.flag-et {background-position: -32px -44px} +.flag.flag-et {background-position: -192px -33px} .flag.flag-eu {background-position: -48px -44px} .flag.flag-fi {background-position: -64px -44px} .flag.flag-fj {background-position: -80px -44px} diff --git a/file.php b/file.php index d62d588eb5942b0c0c1f5639ba22cf511f3a8884..ed0a4465e845f90377e05905c53d9b1b37962f58 100644 --- a/file.php +++ b/file.php @@ -51,7 +51,7 @@ if ($file->verifySignature($_GET['signature'], $_GET['expires'])) { // Download the file.. $file->download(@$_GET['disposition'] ?: false, $_GET['expires']); } - catch (Exception $x) { + catch (Exception $ex) { Http::response(500, 'Unable to find that file: '.$ex->getMessage()); } } diff --git a/include/ajax.forms.php b/include/ajax.forms.php index 520e8584a80b6107d4b63af9f7bd5b309694ac54..77b4638bf43560075ad56341de2ae693ab18f2c6 100644 --- a/include/ajax.forms.php +++ b/include/ajax.forms.php @@ -149,22 +149,32 @@ class DynamicFormsAjaxAPI extends AjaxController { Http::response(403, 'Login required'); $list = DynamicList::lookup($list_id); + if (!$list) + Http::response(404, 'No such list item'); + + $list = CustomListHandler::forList($list); if (!$list || !($item = $list->getItem( (int) $item_id))) Http::response(404, 'No such list item'); $item_form = $list->getListItemBasicForm($_POST, $item); if ($valid = $item_form->isValid()) { - // Update basic information - $basic = $item_form->getClean(); - $item->extra = $basic['extra']; - $item->value = $basic['value']; - if ($_item = DynamicListItem::lookup(array( - 'list_id' => $list->getId(), 'value'=>$item->value))) + 'list_id' => $list->getId(), 'value'=>$item->getValue())) + ) { if ($_item && $_item->id != $item->id) $item_form->getField('value')->addError( __('Value already in use')); + } + if ($item_form->isValid()) { + // Update basic information + $basic = $item_form->getClean(); + $item->update([ + 'name' => $basic['name'], + 'value' => $basic['value'], + 'abbrev' => $basic['extra'], + ]); + } } // Context diff --git a/include/class.canned.php b/include/class.canned.php index c0232099178cab1b4e43076df402ec6daffc33cc..21cd9aa6367f2b4ec81959d1790177fbb751ad65 100644 --- a/include/class.canned.php +++ b/include/class.canned.php @@ -21,6 +21,10 @@ extends VerySimpleModel { 'table' => CANNED_TABLE, 'pk' => array('canned_id'), 'joins' => array( + 'dept' => array( + 'constraint' => array('dept_id' => 'Dept.id'), + 'null' => true, + ), 'attachments' => array( 'constraint' => array( "'C'" => 'Attachment.type', diff --git a/include/class.dept.php b/include/class.dept.php index 5c348237da6bfa9d6e97e6537dcb6fc26e5adc81..ceff70a47084409a9b5b02eebcff703db808b139 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -320,7 +320,7 @@ implements TemplateVariable { if (is_object($staff)) $staff = $staff->getId(); - return $members->getIterator()->findFirst(array( + return $this->getMembers()->findFirst(array( 'staff_id' => $staff )); } @@ -469,7 +469,7 @@ implements TemplateVariable { } /*----Static functions-------*/ - static function getIdByName($name, $pid=null) { + static function getIdByName($name, $pid=null) { $row = static::objects() ->filter(array( 'name' => $name, @@ -579,9 +579,12 @@ implements TemplateVariable { static function __create($vars, &$errors) { $dept = self::create($vars); - $dept->update($vars, $errors); + if (!$dept->update($vars, $errors)) + return false; + + $dept->save(); - return isset($dept->id) ? $dept : null; + return $dept; } function save($refetch=false) { diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 7bb040814714eeaa7b6f4400ef84d3e98391cccf..9abe6467a74f6b91d5641e679d06548ecac04ee2 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -951,15 +951,11 @@ class DynamicFormEntry extends VerySimpleModel { } function setAnswer($name, $value, $id=false) { - foreach ($this->getAnswers() as $ans) { + + if ($ans=$this->getAnswer($name)) { $f = $ans->getField(); - if ($f->isStorable() && $f->get('name') == $name) { - $f->reset(); - $ans->set('value', $value); - if ($id !== false) - $ans->set('value_id', $id); - break; - } + if ($f->isStorable()) + $ans->setValue($value, $id); } } @@ -1269,7 +1265,7 @@ class DynamicFormEntry extends VerySimpleModel { } if ($a->dirty) $dirty++; - $a->save(); + $a->save($refetch); } return $dirty; } @@ -1359,6 +1355,14 @@ class DynamicFormEntryAnswer extends VerySimpleModel { return $this->_value; } + function setValue($value, $id=false) { + $this->getField()->reset(); + $this->_value = null; + $this->set('value', $value); + if ($id !== false) + $this->set('value_id', $id); + } + function getLocal($tag) { return $this->field->getLocal($tag); } @@ -1621,8 +1625,10 @@ class SelectionField extends FormField { } } elseif ($config['typeahead'] && ($entered = $this->getWidget()->getEnteredValue()) - && !in_array($entered, $entry)) + && !in_array($entered, $entry) + && $entered != $entry) { $this->_errors[] = __('Select a value from the list'); + } } } diff --git a/include/class.filter.php b/include/class.filter.php index 20bac5ce53b856d13d439a0afedd8e8abe1ab97a..29f49d904949b2318dbd4da9d16c6ebed7f1f299 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -383,6 +383,10 @@ class Filter { } function lookup($id) { + + if ($id && !is_numeric($id)) + $id = self::getIdByName($id); + return ($id && is_numeric($id) && ($f= new Filter($id)) && $f->getId()==$id)?$f:null; } diff --git a/include/class.filter_action.php b/include/class.filter_action.php index 7d639d9d186634592d787c79e44605d8493d4554..f5dc14abbc77740d58a368e434603568b6f64fca 100644 --- a/include/class.filter_action.php +++ b/include/class.filter_action.php @@ -188,17 +188,18 @@ class FA_UseReplyTo extends TriggerAction { static $name = /* @trans */ 'Use Reply-To Email'; function apply(&$ticket, array $info) { - if (!$info['reply-to']) + if (!$info['reply-to'] + || !$ticket['email'] + || !strcasecmp($info['reply-to'], $ticket['email'])) // Nothing to do return; - $changed = $info['reply-to'] != $ticket['email'] - || ($info['reply-to-name'] && $ticket['name'] != $info['reply-to-name']); - if ($changed) { - $ticket['email'] = $info['reply-to']; - if ($info['reply-to-name']) - $ticket['name'] = $info['reply-to-name']; - throw new FilterDataChanged($ticket); - } + + // Change email and throw data changed exception + $ticket['email'] = $info['reply-to']; + if ($info['reply-to-name']) + $ticket['name'] = $info['reply-to-name']; + + throw new FilterDataChanged($ticket); } function getConfigurationOptions() { diff --git a/include/class.format.php b/include/class.format.php index d25579051f6c8b2f376ce35688bb382c925466b9..864bc6456c6e36863339f612e7e7fa9f2c91c581 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -305,8 +305,9 @@ class Format { ':<!\[[^]<]+\]>:', # <![if !mso]> and friends ':<!DOCTYPE[^>]+>:', # <!DOCTYPE ... > ':<\?[^>]+>:', # <?xml version="1.0" ... > + ':<html[^>]+:i', # drop html attributes ), - array('', '', '', ''), + array('', '', '', '', '<html'), $html); // HtmLawed specific config only diff --git a/include/class.forms.php b/include/class.forms.php index 318adbbc12b4f3053921f9b13d63f8e10cfa3718..b7bb2f93a54f4afb651b96191e8ee0b635d42e47 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -1582,15 +1582,19 @@ class ChoiceField extends FormField { $value = JsonDataParser::parse($value) ?: $value; // CDATA table may be built with comma-separated key,value,key,value - if (is_string($value)) { + if (is_string($value) && strpos($value, ',')) { $values = array(); $choices = $this->getChoices(); - foreach (explode(',', $value) as $V) { + $vals = explode(',', $value); + foreach ($vals as $V) { if (isset($choices[$V])) $values[$V] = $choices[$V]; } if (array_filter($values)) $value = $values; + elseif($vals) + list($value) = $vals; + } $config = $this->getConfiguration(); if (!$config['multiselect'] && is_array($value) && count($value) < 2) { @@ -1762,7 +1766,7 @@ class DatetimeField extends FormField { function to_database($value) { // Store time in gmt time, unix epoch format - return date('Y-m-d H:i:s', $value); + return $value ? date('Y-m-d H:i:s', $value) : $value; } function to_php($value) { diff --git a/include/class.i18n.php b/include/class.i18n.php index a7e23a8ee63d3fabbcbb2e4568a25b60ab3eb3ef..ac6db0a332a0e97da6b216b5ac56f3c262087222 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -140,6 +140,7 @@ class Internationalization { if (($tpl = $this->getTemplate('templates/premade.yaml')) && ($canned = $tpl->getData())) { foreach ($canned as $c) { + $c['isenabled'] = 1; if (!($premade = Canned::create($c)) || !$premade->save()) continue; if (isset($c['attachments'])) { diff --git a/include/class.orm.php b/include/class.orm.php index 3e7dc64dfaab83eebc7b583cf522fd48e8463ea8..1f37f679b60d64ad62dec1939563d16a2f2b405f 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -396,9 +396,10 @@ class VerySimpleModel { } function __isset($field) { - return array_key_exists($field, $this->ht) + return ($this->ht && array_key_exists($field, $this->ht)) || isset(static::$meta['joins'][$field]); } + function __unset($field) { if ($this->__isset($field)) unset($this->ht[$field]); @@ -1468,6 +1469,13 @@ implements IteratorAggregate, Countable, ArrayAccess { } } + function reset() { + $this->eoi = false; + $this->cache = array(); + // XXX: Should the inner be recreated to refetch? + $this->inner->rewind(); + } + function asArray() { $this->fillTo(PHP_INT_MAX); return $this->getCache(); @@ -1908,11 +1916,6 @@ extends ModelResultSet { $object->set($field, null); } - function reset() { - $this->cache = array(); - unset($this->resource); - } - /** * Slight edit to the standard iteration method which will skip deleted * items. @@ -3106,12 +3109,11 @@ class MySqlPreparedExecutor { case is_int($p): case is_float($p): return $p; - case $p instanceof DateTime: $p = $p->format('Y-m-d H:i:s'); default: - return db_real_escape($p, true); - } + return db_real_escape((string) $p, true); + } }, $this->sql); } } diff --git a/include/class.osticket.php b/include/class.osticket.php index 40c939c0493a30d685ea7c51fac0d808eb5b39a5..db48388e03dc852c9a9ddc06ffe9c9bf7a030211 100644 --- a/include/class.osticket.php +++ b/include/class.osticket.php @@ -444,6 +444,37 @@ class osTicket { } } + /* + * getTrustedProxies + * + * Get defined trusted proxies + */ + + static function getTrustedProxies() { + static $proxies = null; + // Parse trusted proxies from config file + if (!isset($proxies) && defined('TRUSTED_PROXIES')) + $proxies = array_filter( + array_map('trim', explode(',', TRUSTED_PROXIES))); + + return $proxies ?: array(); + } + + /* + * getLocalNetworkAddresses + * + * Get defined local network addresses + */ + static function getLocalNetworkAddresses() { + static $ips = null; + // Parse local addreses from config file + if (!isset($ips) && defined('LOCAL_NETWORKS')) + $ips = array_filter( + array_map('trim', explode(',', LOCAL_NETWORKS))); + + return $ips ?: array(); + } + static function get_root_path($dir) { /* If run from the commandline, DOCUMENT_ROOT will not be set. It is @@ -488,14 +519,96 @@ class osTicket { return null; } + /* + * get_client_ip + * + * Get client IP address from "Http_X-Forwarded-For" header by following a + * chain of trusted proxies. + * + * "Http_X-Forwarded-For" header value is a comma+space separated list of IP + * addresses, the left-most being the original client, and each successive + * proxy that passed the request all the way to the originating IP address. + * + */ + static function get_client_ip($header='HTTP_X_FORWARDED_FOR') { + + // Request IP + $ip = $_SERVER['REMOTE_ADDR']; + // Trusted proxies. + $proxies = self::getTrustedProxies(); + // Return current IP address if header is not set and + // request is not from a trusted proxy. + if (!isset($_SERVER[$header]) + || !$proxies + || !self::is_trusted_proxy($ip, $proxies)) + return $ip; + + // Get chain of proxied ip addresses + $ips = array_map('trim', explode(',', $_SERVER[$header])); + // Add request IP to the chain + $ips[] = $ip; + // Walk the chain in reverse - remove invalid IPs + $ips = array_reverse($ips); + foreach ($ips as $k => $ip) { + // Make sure the IP is valid and not a trusted proxy + if ($k && !Validator::is_ip($ip)) + unset($ips[$k]); + elseif ($k && !self::is_trusted_proxy($ip, $proxies)) + return $ip; + } + + // We trust the 400 lb hacker... return left most valid IP + return array_pop($ips); + } + + /* + * Checks if the IP is that of a trusted proxy + * + */ + static function is_trusted_proxy($ip, $proxies=array()) { + $proxies = $proxies ?: self::getTrustedProxies(); + // We don't have any proxies set. + if (!$proxies) + return false; + // Wildcard set - trust all proxies + else if ($proxies == '*') + return true; + + return ($proxies && Validator::check_ip($ip, $proxies)); + } + + /** + * is_local_ip + * + * Check if a given IP is part of defined local address blocks + * + */ + static function is_local_ip($ip, $ips=array()) { + $ips = $ips + ?: self::getLocalNetworkAddresses() + ?: array(); + + foreach ($ips as $addr) { + if (Validator::check_ip($ip, $addr)) + return true; + } + + return false; + } + /** * Returns TRUE if the request was made via HTTPS and false otherwise */ function is_https() { - return (isset($_SERVER['HTTPS']) + + // Local server flags + if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') - || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'); + return true; + + // Check if SSL was terminated by a loadbalancer + return (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) + && !strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https')); } /* returns true if script is being executed via commandline */ diff --git a/include/class.page.php b/include/class.page.php index 7219e88c8db2e1085e8affeae37c4ac516162e7a..c296e9feef025ddc7860c5fb6771d2397f8cdee4 100644 --- a/include/class.page.php +++ b/include/class.page.php @@ -319,7 +319,7 @@ class Page extends VerySimpleModel { return false; } // New translations (?) - foreach ($vars['trans'] as $lang=>$parts) { + foreach ($vars['trans'] ?: array() as $lang=>$parts) { $content = array('name' => @$parts['title'], 'body' => Format::sanitize(@$parts['body'])); if (!array_filter($content)) continue; diff --git a/include/class.search.php b/include/class.search.php index 6249498b05601fcc09afa23f753ae5c61fcb6466..61b56a41e61f6c14dd4fa44ccaa6fd914d36453b 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -478,7 +478,7 @@ class MysqlSearchBackend extends SearchBackend { LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`id` = A2.`object_id` AND A2.`object_type`='H') WHERE A2.`object_id` IS NULL AND (A1.poster <> 'SYSTEM') AND (LENGTH(A1.`title`) + LENGTH(A1.`body`) > 0) - ORDER BY A1.`id` DESC"; + ORDER BY A1.`id` DESC LIMIT 500"; if (!($res = db_query_unbuffered($sql, $auto_create))) return false; @@ -498,7 +498,7 @@ class MysqlSearchBackend extends SearchBackend { $sql = "SELECT A1.`ticket_id` FROM `".TICKET_TABLE."` A1 LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`ticket_id` = A2.`object_id` AND A2.`object_type`='T') WHERE A2.`object_id` IS NULL - ORDER BY A1.`ticket_id` DESC"; + ORDER BY A1.`ticket_id` DESC LIMIT 300"; if (!($res = db_query_unbuffered($sql, $auto_create))) return false; diff --git a/include/class.sequence.php b/include/class.sequence.php index 912e7cd28e910f2ba16c6b7345d56c70d2c94166..867aa210fd193c48731a2b6501470a9b33021eea 100644 --- a/include/class.sequence.php +++ b/include/class.sequence.php @@ -205,7 +205,7 @@ class Sequence extends VerySimpleModel { } function __create($data) { - $instance = new static($data); + $instance = new self($data); $instance->save(); return $instance; } diff --git a/include/class.task.php b/include/class.task.php index 30b6b673fbabf8d2b1a1fef294eb79d58dc14f9f..4b98ff1d4a861ff9d3cab3ed906fc3ae12db34d6 100644 --- a/include/class.task.php +++ b/include/class.task.php @@ -1004,13 +1004,15 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { case 'phone': case 'phone_number': return $this->getPhoneNumber(); - break; + case 'ticket_link': + if ($ticket = $this->ticket) { + return sprintf('%s/scp/tickets.php?id=%d#tasks', + $cfg->getBaseUrl(), $ticket->getId()); + } case 'staff_link': return sprintf('%s/scp/tasks.php?id=%d', $cfg->getBaseUrl(), $this->getId()); - break; case 'create_date': return new FormattedDate($this->getCreateDate()); - break; case 'due_date': if ($due = $this->getEstDueDate()) return new FormattedDate($due); @@ -1061,6 +1063,8 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { 'thread' => array( 'class' => 'TaskThread', 'desc' => __('Task Thread'), ), + 'staff_link' => __('Link to view the task'), + 'ticket_link' => __('Link to view the task inside the ticket'), 'last_update' => array( 'class' => 'FormattedDate', 'desc' => __('Time of last update'), ), @@ -1281,7 +1285,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable { if ($vars['internal_formdata']['dept_id']) $task->dept_id = $vars['internal_formdata']['dept_id']; if ($vars['internal_formdata']['duedate']) - $task->duedate = $vars['internal_formdata']['duedate']; + $task->duedate = date('Y-m-d G:i', Misc::dbtime($vars['internal_formdata']['duedate'])); if (!$task->save(true)) return false; @@ -1453,7 +1457,7 @@ class TaskForm extends DynamicForm { static $cdata = array( 'table' => TASK_CDATA_TABLE, 'object_id' => 'task_id', - 'object_type' => 'A', + 'object_type' => ObjectModel::OBJECT_TYPE_TASK, ); static function objects() { @@ -1516,7 +1520,7 @@ extends AbstractForm { 'configuration' => array( 'min' => Misc::gmtime(), 'time' => true, - 'gmt' => true, + 'gmt' => false, 'future' => true, ), )), @@ -1541,8 +1545,7 @@ class TaskThread extends ObjectThread { $vars['threadId'] = $this->getId(); $vars['message'] = $vars['description']; unset($vars['description']); - - return MessageThreadEntry::create($vars, $errors); + return MessageThreadEntry::add($vars, $errors); } static function create($task=false) { diff --git a/include/class.template.php b/include/class.template.php index 1f53922dd409bc9cc13861a9fd1dbc6a507974f0..53f1caf431a542d115bcfe49a5baa5d08bdf3eb7 100644 --- a/include/class.template.php +++ b/include/class.template.php @@ -616,16 +616,16 @@ class EmailTemplate { function save($id, $vars, &$errors) { if(!$vars['subject']) - $errors['subject']='Message subject is required'; + $errors['subject'] = __('Message subject is required'); if(!$vars['body']) - $errors['body']='Message body is required'; + $errors['body'] = __('Message body is required'); if (!$id) { if (!$vars['tpl_id']) - $errors['tpl_id']='Template set is required'; + $errors['tpl_id'] = __('Template set is required'); if (!$vars['code_name']) - $errors['code_name']='Code name is required'; + $errors['code_name'] = __('Code name is required'); } if ($errors) diff --git a/include/class.thread.php b/include/class.thread.php index b6fc9b5f15504d779dc7619d0e4a6fc384ab8bd2..164624d35c6b8ab34300f07cda5a8f4cd9a2b1cc 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -312,6 +312,7 @@ class Thread extends VerySimpleModel { 'reply_to' => $entry, 'recipients' => $mailinfo['recipients'], 'to-email-id' => $mailinfo['to-email-id'], + 'autorespond' => !isset($mailinfo['passive']), ); // XXX: Is this necessary? @@ -913,9 +914,18 @@ implements TemplateVariable { _S($error_descriptions[$error])); } // No need to log the missing-file error number - if ($error != UPLOAD_ERR_NO_FILE) - $this->getThread()->getObject()->logNote( - _S('File Import Error'), $error, _S('SYSTEM'), false); + if ($error != UPLOAD_ERR_NO_FILE + && ($thread = $this->getThread()) + ) { + // Log to the thread directly, since alerts should be + // suppressed and this is defintely a system message + $thread->addNote(array( + 'title' => _S('File Import Error'), + 'note' => new TextThreadEntryBody($error), + 'poster' => 'SYSTEM', + 'staffId' => 0, + )); + } continue; } @@ -1112,11 +1122,13 @@ implements TemplateVariable { function lookupByEmailHeaders(&$mailinfo, &$seen=false) { // Search for messages using the References header, then the // in-reply-to header - if ($entry = ThreadEntry::objects() - ->filter(array('email_info__mid' => $mailinfo['mid'])) - ->order_by(false) - ->first() - ) { + if ($mailinfo['mid'] && + ($entry = ThreadEntry::objects() + ->filter(array('email_info__mid' => $mailinfo['mid'])) + ->order_by(false) + ->first() + ) + ) { $seen = true; return $entry; } @@ -1178,40 +1190,19 @@ implements TemplateVariable { // ThreadEntry was positively identified return $t; } - - // Try to determine if it's a reply to a tagged email. - // (Deprecated) - $ref = null; - if (strpos($mid, '+')) { - list($left, $right) = explode('@',$mid); - list($left, $ref) = explode('+', $left); - $mid = "$left@$right"; - } - $entries = ThreadEntry::objects() - ->filter(array('email_info__mid' => $mid)) - ->order_by(false); - foreach ($entries as $t) { - // Capture the first match thread item - if (!$thread) - $thread = $t; - // We found a match - see if we can ID the user. - // XXX: Check access of ref is enough? - if ($ref && ($uid = $t->getUIDFromEmailReference($ref))) { - if ($ref[0] =='s') //staff - $mailinfo['staffId'] = $uid; - else // user or collaborator. - $mailinfo['userId'] = $uid; - - // Best possible case — found the thread and the - // user - return $t; - } - } } - // Second best case — found a thread but couldn't identify the - // user from the header. Return the first thread entry matched - if ($thread) - return $thread; + // Passive threading - listen mode + if (count($possibles) + && ($entry = ThreadEntry::objects() + ->filter(array('email_info__mid__in' => array_map( + function ($a) { return "<$a>"; }, + $possibles))) + ->first() + ) + ) { + $mailinfo['passive'] = true; + return $entry; + } // Search for ticket by the [#123456] in the subject line // This is the last resort - emails must match to avoid message @@ -2478,7 +2469,7 @@ implements TemplateVariable { )); } - function addNote($vars, &$errors) { + function addNote($vars, &$errors=array()) { //Add ticket Id. $vars['threadId'] = $this->getId(); diff --git a/include/class.ticket.php b/include/class.ticket.php index 8b75335a76874ac6e9f6b91b16b361573f8b2ffe..7cc661f9595c6a68e4dc8ee8d47b04b7fd13ddda 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1535,7 +1535,7 @@ implements RestrictedAccess, Threadable { } } - function onMessage($message, $autorespond=true) { + function onMessage($message, $autorespond=true, $reopen=true) { global $cfg; $this->isanswered = 0; @@ -1547,7 +1547,7 @@ implements RestrictedAccess, Threadable { // We're also checking autorespond flag because we don't want to // reopen closed tickets on auto-reply from end user. This is not to // confused with autorespond on new message setting - if ($autorespond && $this->isClosed() && $this->isReopenable()) { + if ($reopen && $this->isClosed() && $this->isReopenable()) { $this->reopen(); // Auto-assign to closing staff or the last respondent if the // agent is available and has access. Otherwise, put the ticket back @@ -2334,10 +2334,13 @@ implements RestrictedAccess, Threadable { $autorespond = isset($vars['mailflags']) ? !$vars['mailflags']['bounce'] && !$vars['mailflags']['auto-reply'] : true; + $reopen = $autorespond; // Do not reopen bounces if ($autorespond && $message->isBounceOrAutoReply()) - $autorespond = false; + $autorespond = $reopen= false; + elseif ($autorespond && isset($vars['autorespond'])) + $autorespond = $vars['autorespond']; - $this->onMessage($message, ($autorespond && $alerts)); //must be called b4 sending alerts to staff. + $this->onMessage($message, ($autorespond && $alerts), $reopen); //must be called b4 sending alerts to staff. if ($autorespond && $alerts && $cfg && $cfg->notifyCollabsONNewMessage()) $this->notifyCollaborators($message, array('signature' => '')); @@ -2404,6 +2407,7 @@ implements RestrictedAccess, Threadable { $sentlist[] = $staff->getEmail(); } } + return $message; } @@ -2825,6 +2829,7 @@ implements RestrictedAccess, Threadable { if (!$this->save()) return false; + $vars['note'] = ThreadEntryBody::clean($vars['note']); if ($vars['note']) $this->logNote(_S('Ticket Updated'), $vars['note'], $thisstaff); @@ -2897,9 +2902,11 @@ implements RestrictedAccess, Threadable { } static function isTicketNumberUnique($number) { - return 0 === static::objects() + $num = static::objects() ->filter(array('number' => $number)) - ->count(); + ->count(); + + return ($num === 0); } /* Quick staff's tickets stats */ @@ -2994,8 +3001,8 @@ implements RestrictedAccess, Threadable { $user_form = UserForm::getUserForm()->getForm($vars); // Add all the user-entered info for filtering foreach ($interesting as $F) { - $field = $user_form->getField($F); - $vars[$F] = $field->toString($field->getClean()); + if ($field = $user_form->getField($F)) + $vars[$F] = $field->toString($field->getClean()); } // Attempt to lookup the user and associated data $user = User::lookupByEmail($vars['email']); @@ -3373,11 +3380,9 @@ implements RestrictedAccess, Threadable { // Save the (common) dynamic form // Ensure we have a subject $subject = $form->getAnswer('subject'); - if ($subject && !$subject->getValue()) { - if ($topic) { - $form->setAnswer('subject', $topic->getFullName()); - } - } + if ($subject && !$subject->getValue() && $topic) + $subject->setValue($topic->getFullName()); + $form->setTicketId($ticket->getId()); $form->save(); diff --git a/include/class.timezone.php b/include/class.timezone.php index 272444b458130a7f6eff208708ab1e0b48c15743..eb2afca6771cc7e545e0805b9a0dde325fe4888f 100644 --- a/include/class.timezone.php +++ b/include/class.timezone.php @@ -166,12 +166,20 @@ class DbTimezone { // Attempt to fetch timezone direct from the database $TZ = db_timezone(); + // Translate ambiguous 'GMT' timezone + if ($TZ === 'GMT') { + // PHP assumes GMT == UTC, MySQL assumes GMT == Europe/London. + // To shore up the difference, assuming use of MySQL, use the + // timezone in PHP which honors BST (British Summer Time) + return 'Europe/London'; + } // Forbid timezone abbreviations like 'CDT' - if (!in_array($TZ, array('UTC', 'GMT')) && strpos($TZ, '/') === false) + elseif ($TZ !== 'UTC' && strpos($TZ, '/') === false) { // Attempt to lookup based on the abbreviation if (!($TZ = timezone_name_from_abbr($TZ))) // Abbreviation doesn't point to anything valid return false; + } // SYSTEM does not describe a time zone, ensure we have a valid zone // by attempting to create an instance of DateTimeZone() diff --git a/include/class.validator.php b/include/class.validator.php index 2cce38f21432dc457efbae59bd4277bec9d213d3..14be7ccaf005423bf34c395aa375c04227e9671e 100644 --- a/include/class.validator.php +++ b/include/class.validator.php @@ -185,23 +185,7 @@ class Validator { } static function is_ip($ip) { - - if(!$ip or empty($ip)) - return false; - - $ip=trim($ip); - # Thanks to http://stackoverflow.com/a/1934546 - if (function_exists('inet_pton')) { # PHP 5.1.0 - # Let the built-in library parse the IP address - return @inet_pton($ip) !== false; - } else if (preg_match( - '/^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){7,})' - .'((?1)(?>:(?1)){0,5})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){5,})' - .'(?3)?::(?>((?1)(?>:(?1)){0,3}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])' - .'(?>\.(?4)){3}))$/iD', $ip)) { - return true; - } - return false; + return filter_var(trim($ip), FILTER_VALIDATE_IP) !== false; } static function is_username($username, &$error='') { @@ -212,6 +196,100 @@ class Validator { return $error == ''; } + + /* + * check_ip + * Checks if an IP (IPv4 or IPv6) address is contained in the list of given IPs or subnets. + * + * @credit - borrowed from Symfony project + * + */ + public static function check_ip($ip, $ips) { + + if (!Validator::is_ip($ip)) + return false; + + $method = substr_count($ip, ':') > 1 ? 'check_ipv6' : 'check_ipv4'; + $ips = is_array($ips) ? $ips : array($ips); + foreach ($ips as $_ip) { + if (self::$method($ip, $_ip)) { + return true; + } + } + + return false; + } + + /** + * check_ipv4 + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @credit - borrowed from Symfony project + */ + public static function check_ipv4($ip, $cidr) { + + if (false !== strpos($cidr, '/')) { + list($address, $netmask) = explode('/', $cidr, 2); + + if ($netmask === '0') + return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + + if ($netmask < 0 || $netmask > 32) + return false; + + } else { + $address = $cidr; + $netmask = 32; + } + + return 0 === substr_compare( + sprintf('%032b', ip2long($ip)), + sprintf('%032b', ip2long($address)), + 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @credit - borrowed from Symfony project + * @author David Soria Parra <dsp at php dot net> + * + * @see https://github.com/dsp/v6tools + * + */ + public static function check_ipv6($ip, $cidr) { + + if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) + return false; + + if (false !== strpos($cidr, '/')) { + list($address, $netmask) = explode('/', $cidr, 2); + if ($netmask < 1 || $netmask > 128) + return false; + } else { + $address = $cidr; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($ip)); + if (!$bytesAddr || !$bytesTest) + return false; + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return false; + } + } + + return true; + } + function process($fields,$vars,&$errors){ $val = new Validator(); diff --git a/include/client/header.inc.php b/include/client/header.inc.php index 42b7b6c781fbd55834b77da9872ad1a4aebfe977..2e93d012db87de7f2fa984e12b6110dcef13be4b 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -28,7 +28,7 @@ if ($lang) { <title><?php echo Format::htmlchars($title); ?></title> <meta name="description" content="customer support platform"> <meta name="keywords" content="osTicket, Customer support system, support ticket system"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> + <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="<?php echo ROOT_PATH; ?>css/osticket.css" media="screen"> <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/theme.css" media="screen"> <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/print.css" media="print"> diff --git a/include/client/kb-categories.inc.php b/include/client/kb-categories.inc.php index 236847fb6dbf75439b83d8e08932df4d767e8997..c4df171c59d466adc540111ee315b4a559499ad6 100644 --- a/include/client/kb-categories.inc.php +++ b/include/client/kb-categories.inc.php @@ -45,7 +45,7 @@ <input type="hidden" name="a" value="search"/> <select name="topicId" style="width:100%;max-width:100%" onchange="javascript:this.form.submit();"> - <option value="">— Browse by Topic —</option> + <option value="">—<?php echo __("Browse by Topic"); ?>—</option> <?php $topics = Topic::objects() ->annotate(array('has_faqs'=>SqlAggregate::COUNT('faqs'))) diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index f37874b55a8916081c924599130f64c863d36f37..6840c252b0715c9b9ad5a5e4df52d1db5e7cc47b 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -47,8 +47,10 @@ if($sort && $sortOptions[$sort]) $order_by =$sortOptions[$sort]; $order_by=$order_by ?: $sortOptions['date']; -if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) - $order=$orderWays[strtoupper($_REQUEST['order'])]; +if ($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) + $order = $orderWays[strtoupper($_REQUEST['order'])]; +else + $order = $orderWays['DESC']; $x=$sort.'_sort'; $$x=' class="'.strtolower($_REQUEST['order'] ?: 'desc').'" '; @@ -103,7 +105,7 @@ $tickets->distinct('ticket_id'); TicketForm::ensureDynamicDataView(); -$total=$tickets->count(); +$total=$visibility->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); $qstr = '&'. Http::build_query($qs); @@ -123,6 +125,7 @@ if($search) $negorder=$order=='-'?'ASC':'DESC'; //Negate the sorting +$tickets->order_by($order.$order_by); $tickets->values( 'ticket_id', 'number', 'created', 'isanswered', 'source', 'status_id', 'status__state', 'status__name', 'cdata__subject', 'dept_id', @@ -241,12 +244,12 @@ if ($closedTickets) {?> <a class="Icon <?php echo strtolower($T['source']); ?>Ticket" title="<?php echo $T['user__default_email__address']; ?>" href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $ticketNumber; ?></a> </td> - <td> <?php echo Format::date($T['created']); ?></td> - <td> <?php echo $status; ?></td> + <td><?php echo Format::date($T['created']); ?></td> + <td><?php echo $status; ?></td> <td> <div style="max-height: 1.2em; max-width: 320px;" class="link truncate" href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></div> </td> - <td> <span class="truncate"><?php echo $dept; ?></span></td> + <td><span class="truncate"><?php echo $dept; ?></span></td> </tr> <?php } diff --git a/include/html2text.php b/include/html2text.php index 0fcaa5cab999744bac97d90e0c8092b476bfbe4c..e7ddedeaf4afb33f5e6c5166cdf0f433d0924bfb 100644 --- a/include/html2text.php +++ b/include/html2text.php @@ -1014,7 +1014,7 @@ function mb_wordwrap($string, $width=75, $break="\n", $cut=false) { } else { // Anchor the beginning of the pattern with a lookahead // to avoid crazy backtracking when words are longer than $width - $pattern = '/(?=[\s\p{Ps}])(.{1,'.$width.'})(?:\s|$|(\p{Ps}))/uS'; + $search = '/(?=[\s\p{Ps}])(.{1,'.$width.'})(?:\s|$|(\p{Ps}))/uS'; $replace = '$1'.$break.'$2'; } return rtrim(preg_replace($search, $replace, $string), $break); diff --git a/include/ost-sampleconfig.php b/include/ost-sampleconfig.php index 0b26400698271c1d89ab46f55a209d501b5b1dae..b4a5049f4d01b5a9596c0142211f41bd19e73cbd 100644 --- a/include/ost-sampleconfig.php +++ b/include/ost-sampleconfig.php @@ -107,6 +107,39 @@ define('TABLE_PREFIX','%CONFIG-PREFIX'); # define('ROOT_PATH', '/support/'); + +# Option: TRUSTED_PROXIES (default: <none>) +# +# To support running osTicket installation on a web servers that sit behind a +# load balancer, HTTP cache, or other intermediary (reverse) proxy; it's +# necessary to define trusted proxies to protect against forged http headers +# +# osTicket supports passing the following http headers from a trusted proxy; +# - HTTP_X_FORWARDED_FOR => Chain of client's IPs +# - HTTP_X_FORWARDED_PROTO => Client's HTTP protocal (http | https) +# +# You'll have to explicitly define comma separated IP addreseses or CIDR of +# upstream proxies to trust. Wildcard "*" (not recommended) can be used to +# trust all chained IPs as proxies in cases that ISP/host doesn't provide +# IPs of loadbalancers or proxies. +# +# References: +# http://en.wikipedia.org/wiki/X-Forwarded-For +# + +define('TRUSTED_PROXIES', ''); + + +# Option: LOCAL_NETWORKS (default: 127.0.0.0/24) +# +# When running osTicket as part of a cluster it might become necessary to +# whitelist local/virtual networks that can bypass some authentication/checks. +# +# define comma separated IP addreseses or enter CIDR of local network. + +define('LOCAL_NETWORKS', '127.0.0.0/24'); + + # # Session Storage Options # --------------------------------------------------- diff --git a/include/pear/Net/SMTP.php b/include/pear/Net/SMTP.php index 8f4e92b7532a0c51f97219017a246574d3d17d8c..530f558c9c39e0fca73fa98a431eeb5bd3cbd9fd 100644 --- a/include/pear/Net/SMTP.php +++ b/include/pear/Net/SMTP.php @@ -166,6 +166,13 @@ class Net_SMTP $this->pipelining = $pipelining; $this->socket = new Net_Socket(); + + // Turn off peer name verification by default + if (!$socket_options) + $socket_options = array( + 'ssl' => array('verify_peer_name' => false) + ); + $this->socket_options = $socket_options; $this->timeout = $timeout; diff --git a/include/staff/apikey.inc.php b/include/staff/apikey.inc.php index c3b41fb0c41d14abc18ccb196666f7066fbe9128..c7da2e8e9e6550e85591122933964cd019e9e217 100644 --- a/include/staff/apikey.inc.php +++ b/include/staff/apikey.inc.php @@ -17,7 +17,7 @@ if($api && $_REQUEST['a']!='add'){ } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="apikeys.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="apikeys.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/banrule.inc.php b/include/staff/banrule.inc.php index 6b4b5cea101f0fe457eb04f2c4dbfb7909630f45..ad8c69b81f8952b2451522ffeb929184188b91f4 100644 --- a/include/staff/banrule.inc.php +++ b/include/staff/banrule.inc.php @@ -19,7 +19,7 @@ if($rule && $_REQUEST['a']!='add'){ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="banlist.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="banlist.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/cannedresponse.inc.php b/include/staff/cannedresponse.inc.php index 0dabac6fd3319fd5dca169521e47da16f7693bb6..20818d994242254f10fe2c85e5747742e9a944ef 100644 --- a/include/staff/cannedresponse.inc.php +++ b/include/staff/cannedresponse.inc.php @@ -21,7 +21,7 @@ if($canned && $_REQUEST['a']!='add'){ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="canned.php?<?php echo Http::build_query($qs); ?>" method="post" id="save" enctype="multipart/form-data"> +<form action="canned.php?<?php echo Http::build_query($qs); ?>" method="post" class="save" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/category.inc.php b/include/staff/category.inc.php index 5ac65cc2131e550dd05bf61f60e602c7fd981829..4eac204218ad029b49f49945442b41401c20582a 100644 --- a/include/staff/category.inc.php +++ b/include/staff/category.inc.php @@ -36,7 +36,7 @@ if($category && $_REQUEST['a']!='add'){ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="categories.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="categories.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> @@ -51,9 +51,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <div style="margin:8px 0"><strong><?php echo __('Category Type');?>:</strong> <span class="error">*</span></div> <div style="margin-left:5px"> - <input type="radio" name="ispublic" value="2" <?php echo $info['ispublic']?'checked="checked"':''; ?>><b><?php echo __('Featured');?></b> <?php echo __('(on front-page sidebar)');?> + <input type="radio" name="ispublic" value="2" <?php echo $info['ispublic']==2?'checked="checked"':''; ?>><b><?php echo __('Featured');?></b> <?php echo __('(on front-page sidebar)');?> <br/> - <input type="radio" name="ispublic" value="1" <?php echo $info['ispublic']?'checked="checked"':''; ?>><b><?php echo __('Public');?></b> <?php echo __('(publish)');?> + <input type="radio" name="ispublic" value="1" <?php echo $info['ispublic']==1?'checked="checked"':''; ?>><b><?php echo __('Public');?></b> <?php echo __('(publish)');?> <br/> <input type="radio" name="ispublic" value="0" <?php echo !$info['ispublic']?'checked="checked"':''; ?>><?php echo __('Private');?> <?php echo __('(internal)');?> <br/> diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php index 1d32543973633c1817bbe1a0e141c82259c74ae1..2ea8825e35c787bdcba95078a23e50f8e46f1192 100644 --- a/include/staff/department.inc.php +++ b/include/staff/department.inc.php @@ -26,7 +26,7 @@ if($dept && $_REQUEST['a']!='add') { $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); ?> -<form action="departments.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="departments.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php index 775e058792ccb28790e1b21b55ba479734879105..563766f0217135f464217569034e115cd650432d 100644 --- a/include/staff/dynamic-form.inc.php +++ b/include/staff/dynamic-form.inc.php @@ -31,7 +31,7 @@ if($form && $_REQUEST['a']!='add') { $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form class="manage-form" action="<?php echo $url ?>" method="post" id="save"> +<form class="manage-form" action="<?php echo $url ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo $action; ?>"> diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php index a71079656404d42f7b509b72adfaf38c09c7dd2d..8fb39d2cf4333d88ca3a7e65452ba862c29e079d 100644 --- a/include/staff/dynamic-list.inc.php +++ b/include/staff/dynamic-list.inc.php @@ -19,7 +19,7 @@ if ($list) { $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info); ?> -<form action="" method="post" id="save"> +<form action="" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> @@ -257,7 +257,7 @@ $(function() { $('#items').on('click', 'a.items-action', function(e) { e.preventDefault(); var ids = []; - $('form#save :checkbox.mass:checked').each(function() { + $('form.save :checkbox.mass:checked').each(function() { ids.push($(this).val()); }); if (ids.length && confirm(__('You sure?'))) { diff --git a/include/staff/email.inc.php b/include/staff/email.inc.php index 953da7f55ccbdc324d09f5f2f08fd56b4dd2b462..3653f06a7949bacc7d92d1b32a911c075abe07c0 100644 --- a/include/staff/email.inc.php +++ b/include/staff/email.inc.php @@ -39,7 +39,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); — <?php echo $info['email']; ?></small> <?php } ?> </h2> -<form action="emails.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="emails.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/faq-categories.inc.php b/include/staff/faq-categories.inc.php index e90f06cd88017ce603f952c76eebf535c561ff6c..4c484822751eeb0a6b8e3f5ad8ce59562ad523b6 100644 --- a/include/staff/faq-categories.inc.php +++ b/include/staff/faq-categories.inc.php @@ -140,8 +140,7 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. } } else { //Category Listing. $categories = Category::objects() - ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs'))) - ->all(); + ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs'))); if (count($categories)) { $categories->sort(function($a) { return $a->getLocalName(); }); diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php index d75958fdadd7dd97b4eed2fdd0c5d613fa5f8155..4579deb8d34ca38484835ec6bb33d178721ddbda 100644 --- a/include/staff/faq.inc.php +++ b/include/staff/faq.inc.php @@ -41,7 +41,7 @@ if($faq){ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); $qstr = Http::build_query($qs); ?> -<form action="faq.php?<?php echo $qstr; ?>" method="post" id="save" enctype="multipart/form-data"> +<form action="faq.php?<?php echo $qstr; ?>" method="post" class="save" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index 4175f2b3b2ff35df8fcefc5b9d243996454a476e..2330fc47dc4ebeece8aa04e8365c71bb30bfb186 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -23,7 +23,7 @@ if($filter && $_REQUEST['a']!='add'){ } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="filters.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="filters.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index 13a122c54df8a1fb40a03435e431b068d9fd05bd..b89c16ab49c93c57bfa790a1d2153cf1a77b2e6b 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -1,5 +1,9 @@ <?php header("Content-Type: text/html; charset=UTF-8"); + +$title = ($ost && ($title=$ost->getPageTitle())) + ? $title : ('osTicket :: '.__('Staff Control Panel')); + if (!isset($_SERVER['HTTP_X_PJAX'])) { ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html<?php @@ -17,7 +21,7 @@ if ($lang) { <meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="pragma" content="no-cache" /> <meta http-equiv="x-pjax-version" content="<?php echo GIT_VERSION; ?>"> - <title><?php echo ($ost && ($title=$ost->getPageTitle()))?$title:'osTicket :: '.__('Staff Control Panel'); ?></title> + <title><?php echo Format::htmlchars($title); ?></title> <!--[if IE]> <style type="text/css"> .tip_shadow { display:block !important; } diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index a5a2c31805f760bbeae7aacc0340b9bb8984ec7c..8cb90850d546d9080495833ea95de0d1f5376f1a 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -35,7 +35,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <li><a href="#forms"><i class="icon-paste"></i> <?php echo __('Forms'); ?></a></li> </ul> -<form action="helptopics.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="helptopics.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php index d356542c4a0d1f1165f76cab2a29e0ae31c955ab..0b5c58580c22ffdb3c7774db0ac170a904accf57 100644 --- a/include/staff/helptopics.inc.php +++ b/include/staff/helptopics.inc.php @@ -147,7 +147,7 @@ $order_by = 'sort'; <td><?php echo $priority; ?></td> <td><a href="departments.php?id=<?php echo $deptId; ?>"><?php echo $dept; ?></a></td> - <td> <?php echo Format::datetime($team->updated); ?></td> + <td> <?php echo Format::datetime($topic->updated); ?></td> </tr> <?php } //end of foreach. diff --git a/include/staff/login.header.php b/include/staff/login.header.php index d4068027840b811e2641f4870e6cd3927603e4f9..4460229c1acb8c2814128e343c9d0e861048abcc 100644 --- a/include/staff/login.header.php +++ b/include/staff/login.header.php @@ -12,7 +12,7 @@ defined('OSTSCPINC') or die('Invalid path'); <meta name="robots" content="noindex" /> <meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="pragma" content="no-cache" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> + <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="text/javascript" src="<?php echo ROOT_PATH; ?>js/jquery-1.11.2.min.js"></script> <script type="text/javascript"> $(document).ready(function() { diff --git a/include/staff/page.inc.php b/include/staff/page.inc.php index 97cb838e50a13c9739e1c020aced734c17ecd4ef..6ecd1e44397f4296a9994e2ce2acf503b278a94e 100644 --- a/include/staff/page.inc.php +++ b/include/staff/page.inc.php @@ -37,7 +37,7 @@ if($page && $_REQUEST['a']!='add'){ } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="pages.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="pages.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/plugin.inc.php b/include/staff/plugin.inc.php index 6ab5b3c284d7c1eedf88a477a17cc931c9f05a70..5f9c35416f9e4eed86bcb1ae36b44517d3ddff60 100644 --- a/include/staff/plugin.inc.php +++ b/include/staff/plugin.inc.php @@ -18,7 +18,7 @@ if($plugin && $_REQUEST['a']!='add') { $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); ?> -<form action="?<?php echo Http::build_query(array('id' => $_REQUEST['id'])); ?>" method="post" id="save"> +<form action="?<?php echo Http::build_query(array('id' => $_REQUEST['id'])); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index 5383a8c9893c1c9a62a79b489f964e6796c58ebd..627842f6942e7d11339107507d45203bc84f4535 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -2,7 +2,7 @@ if(!defined('OSTSTAFFINC') || !$staff || !$thisstaff) die('Access Denied'); ?> -<form action="profile.php" method="post" id="save" autocomplete="off"> +<form action="profile.php" method="post" class="save" autocomplete="off"> <?php csrf_token(); ?> <input type="hidden" name="do" value="update"> <input type="hidden" name="id" value="<?php echo $staff->getId(); ?>"> diff --git a/include/staff/role.inc.php b/include/staff/role.inc.php index 9d82a8a318275582b0d1cf281c7ecd47242321a6..5f05c6d7943e09bc72804834ecaab312d40050e6 100644 --- a/include/staff/role.inc.php +++ b/include/staff/role.inc.php @@ -18,7 +18,7 @@ if ($role) { $info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $info); ?> -<form action="" method="post" id="save"> +<form action="" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/settings-agents.inc.php b/include/staff/settings-agents.inc.php index 2a06676f0bc6d82355ebaa84a78ea42067933085..310a6fbfcbbdd0c53e67d6a71c6ebd7edb2e1eaf 100644 --- a/include/staff/settings-agents.inc.php +++ b/include/staff/settings-agents.inc.php @@ -3,7 +3,7 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config ?> <h2><?php echo __('Agents Settings'); ?></h2> -<form action="settings.php?t=agents" method="post" id="save"> +<form action="settings.php?t=agents" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="agents" > <ul class="tabs" id="agents-tabs"> diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php index eddfb56a3340d923c50bd883fcfe2f39b399504e..aece5823459b9ecf0aeb3bd2b8c565d04619fe4f 100644 --- a/include/staff/settings-emails.inc.php +++ b/include/staff/settings-emails.inc.php @@ -2,7 +2,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); ?> <h2><?php echo __('Email Settings and Options');?></h2> -<form action="emailsettings.php" method="post" id="save"> +<form action="emailsettings.php" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="emails" > <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> diff --git a/include/staff/settings-kb.inc.php b/include/staff/settings-kb.inc.php index 2f7fa6762f54a60089e27ffc0c1f5d6d9d08b49d..0fe2de9d274348fe0d59890d703eead332663428 100644 --- a/include/staff/settings-kb.inc.php +++ b/include/staff/settings-kb.inc.php @@ -2,7 +2,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); ?> <h2><?php echo __('Knowledge Base Settings and Options');?></h2> -<form action="settings.php?t=kb" method="post" id="save"> +<form action="settings.php?t=kb" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="kb" > <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> diff --git a/include/staff/settings-pages.inc.php b/include/staff/settings-pages.inc.php index f3702000368d23ea8cc8ef02201641aaa8029326..e7f37590e2edd2895474bb4b2d2f94f7c35ad2bd 100644 --- a/include/staff/settings-pages.inc.php +++ b/include/staff/settings-pages.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) $pages = Page::getPages(); ?> <h2><?php echo __('Company Profile'); ?></h2> -<form action="settings.php?t=pages" method="post" id="save" +<form action="settings.php?t=pages" method="post" class="save" enctype="multipart/form-data"> <?php csrf_token(); ?> diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php index 6d03642201a509181a3d623691e88806298213ae..22502ed5e54dde3ae77d17c0c8aa15f4c7204b4b 100644 --- a/include/staff/settings-system.inc.php +++ b/include/staff/settings-system.inc.php @@ -4,7 +4,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) $gmtime = Misc::gmtime(); ?> <h2><?php echo __('System Settings and Preferences');?> <small>— <span class="ltr">osTicket (<?php echo $cfg->getVersion(); ?>)</span></small></h2> -<form action="settings.php?t=system" method="post" id="save"> +<form action="settings.php?t=system" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="system" > <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> diff --git a/include/staff/settings-tasks.inc.php b/include/staff/settings-tasks.inc.php index e06a96b5d1a984536c7263e93d38996132efe13d..d5a51f034161885ff7f29da5e7bf40bc908afd30 100644 --- a/include/staff/settings-tasks.inc.php +++ b/include/staff/settings-tasks.inc.php @@ -4,7 +4,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; ?> <h2><?php echo __('Task Settings and Options');?></h2> -<form action="settings.php?t=tasks" method="post" id="save"> +<form action="settings.php?t=tasks" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="tasks" > diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index 59570b4423a44d7f95f435177b84044b2b7ef4f3..8ec82503174c174509d51d3a923240dee7b4b1a2 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -4,7 +4,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; ?> <h2><?php echo __('Ticket Settings and Options');?></h2> -<form action="settings.php?t=tickets" method="post" id="save"> +<form action="settings.php?t=tickets" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="tickets" > diff --git a/include/staff/settings-users.inc.php b/include/staff/settings-users.inc.php index 4096ccbd921cc483729dc0534dad5be2787190a8..1ef385bf96ac456bdc8367995e454fee800168b4 100644 --- a/include/staff/settings-users.inc.php +++ b/include/staff/settings-users.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) ?> <h2><?php echo __('Users Settings'); ?></h2> -<form action="settings.php?t=users" method="post" id="save"> +<form action="settings.php?t=users" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="users" > <ul class="tabs" id="users-tabs"> diff --git a/include/staff/slaplan.inc.php b/include/staff/slaplan.inc.php index beba3be42ae2971bc44f7d4ce6f4502b362e1148..bc775ba57957b1e51a04c2fb5b000949d4785a04 100644 --- a/include/staff/slaplan.inc.php +++ b/include/staff/slaplan.inc.php @@ -20,7 +20,7 @@ if($sla && $_REQUEST['a']!='add'){ } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="slas.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="slas.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index c063948b75a68ed6e7ae6c9c4cac1adc8f1f410c..ff37fa8fc17671947468c6f1764bbf37930ee97b 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -35,7 +35,7 @@ else { } ?> -<form action="staff.php?<?php echo Http::build_query($qs); ?>" method="post" id="save" autocomplete="off"> +<form action="staff.php?<?php echo Http::build_query($qs); ?>" method="post" class="save" autocomplete="off"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> @@ -518,6 +518,7 @@ $('#join_team').find('button').on('click', function() { <?php foreach ($staff->dept_access as $dept_access) { + if (!$dept_access->dept_id) continue; echo sprintf('addAccess(%d, %s, %d, %d, %s);', $dept_access->dept_id, JsonDataEncoder::encode($dept_access->dept->getName()), $dept_access->role_id, diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php index 7ddba38eab8213491443aeccdf489eeb7b62ad88..97f7c1265249a845d5bfb49d3705e66638e44569 100644 --- a/include/staff/staffmembers.inc.php +++ b/include/staff/staffmembers.inc.php @@ -16,12 +16,6 @@ $sortOptions = array( $orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC'); $sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name'; -if ($sort && $sortOptions[$sort]) { - $order_column = $sortOptions[$sort]; -} - -$order_column = $order_column ? $order_column : array('firstname', 'lastname'); - switch ($cfg->getAgentNameFormat()) { case 'last': case 'lastfirst': @@ -31,6 +25,12 @@ case 'legal': // Otherwise leave unchanged } +if ($sort && $sortOptions[$sort]) { + $order_column = $sortOptions[$sort]; +} + +$order_column = $order_column ?: array('firstname', 'lastname'); + if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) { $order = $orderWays[strtoupper($_REQUEST['order'])]; } else { diff --git a/include/staff/syslogs.inc.php b/include/staff/syslogs.inc.php index 0d3f05e216a6e898f93b2a1134f497a2746ee6cf..9d4d43df1dbd020a61be1defc16109591bff1db3 100644 --- a/include/staff/syslogs.inc.php +++ b/include/staff/syslogs.inc.php @@ -156,7 +156,7 @@ else <td> <a class="tip" href="#log/<?php echo $row['log_id']; ?>"><?php echo Format::htmlchars($row['title']); ?></a></td> <td><?php echo $row['log_type']; ?></td> <td> <?php echo Format::daydatetime($row['created']); ?></td> - <td><?php echo $row['ip_address']; ?></td> + <td><?php echo Format::htmlchars($row['ip_address']); ?></td> </tr> <?php } //end of while. diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php index 600ea6a5826062b2c31de4550d505747ca069c76..254d1559d6407d5e15d522185e2c62778ff567f4 100644 --- a/include/staff/team.inc.php +++ b/include/staff/team.inc.php @@ -23,7 +23,7 @@ if ($team && $_REQUEST['a']!='add') { $info = $team->getInfo(); ?> -<form action="teams.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="teams.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/template.inc.php b/include/staff/template.inc.php index c8a91cace854b47634bf2563e481afcd22a46eec..0ff7ff3d9e95c0bfd59708e17dc2d4a274dc312e 100644 --- a/include/staff/template.inc.php +++ b/include/staff/template.inc.php @@ -19,7 +19,7 @@ if($template && $_REQUEST['a']!='add'){ } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="templates.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> +<form action="templates.php?<?php echo Http::build_query($qs); ?>" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/templates/task-view.tmpl.php b/include/staff/templates/task-view.tmpl.php index e2e26b7cc52c814c2e8f213c61d3a3df4183adc3..b2dff125312309c5493da4ec93d4edac0edd78ce 100644 --- a/include/staff/templates/task-view.tmpl.php +++ b/include/staff/templates/task-view.tmpl.php @@ -469,7 +469,7 @@ else </ul> <?php if ($role->hasPerm(TaskModel::PERM_REPLY)) { ?> - <form id="task_reply" class="tab_content spellcheck" + <form id="task_reply" class="tab_content spellcheck save" action="<?php echo $action; ?>" name="task_reply" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> @@ -559,7 +559,7 @@ else } ?> <form id="task_note" action="<?php echo $action; ?>" - class="tab_content spellcheck <?php + class="tab_content spellcheck save <?php echo $role->hasPerm(TaskModel::PERM_REPLY) ? 'hidden' : ''; ?>" name="task_note" method="post" enctype="multipart/form-data"> diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php index 37697ea8c5059688306ceef2b077df57e1f1bf13..e15e8554d3d67afeea3ed47de83429638524dfbd 100644 --- a/include/staff/ticket-edit.inc.php +++ b/include/staff/ticket-edit.inc.php @@ -10,7 +10,7 @@ if ($_POST) // timezone) $info['duedate'] = Format::date(strtotime($info['duedate']), false, false, 'UTC'); ?> -<form action="tickets.php?id=<?php echo $ticket->getId(); ?>&a=edit" method="post" id="save" enctype="multipart/form-data"> +<form action="tickets.php?id=<?php echo $ticket->getId(); ?>&a=edit" method="post" class="save" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="do" value="update"> <input type="hidden" name="a" value="edit"> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index 5319cb1afee69d3bad59fcf55e92b6b1385f86fa..6d383e580aa55714518ab59a7e6707f9b1402940 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -25,7 +25,7 @@ if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) { if ($_POST) $info['duedate'] = Format::date(strtotime($info['duedate']), false, false, 'UTC'); ?> -<form action="tickets.php?a=open" method="post" id="save" enctype="multipart/form-data"> +<form action="tickets.php?a=open" method="post" class="save" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="do" value="create"> <input type="hidden" name="a" value="open"> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 43097ab357e17b35f6a75b561a6a1d348739d804..ba3e0a468006f2caf6c66c1520212ae57d36ec6d 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -356,7 +356,7 @@ if($ticket->isOverdue()) echo Format::htmlchars($ticket->getSource()); if (!strcasecmp($ticket->getSource(), 'Web') && $ticket->getIP()) - echo ' <span class="faded">('.$ticket->getIP().')</span>'; + echo ' <span class="faded">('.Format::htmlchars($ticket->getIP()).')</span>'; ?> </td> </tr> @@ -537,7 +537,7 @@ if ($errors['err'] && isset($_POST['a'])) { </ul> <?php if ($role->hasPerm(TicketModel::PERM_REPLY)) { ?> - <form id="reply" class="tab_content spellcheck exclusive" + <form id="reply" class="tab_content spellcheck exclusive save" data-lock-object-id="ticket/<?php echo $ticket->getId(); ?>" data-lock-id="<?php echo $mylock ? $mylock->getId() : ''; ?>" action="tickets.php?id=<?php @@ -734,7 +734,7 @@ if ($errors['err'] && isset($_POST['a'])) { </form> <?php } ?> - <form id="note" class="hidden tab_content spellcheck exclusive" + <form id="note" class="hidden tab_content spellcheck exclusive save" data-lock-object-id="ticket/<?php echo $ticket->getId(); ?>" data-lock-id="<?php echo $mylock ? $mylock->getId() : ''; ?>" action="tickets.php?id=<?php echo $ticket->getId(); ?>#note" diff --git a/include/staff/tpl.inc.php b/include/staff/tpl.inc.php index 10fc3e70bb5ae13933389891cceab34a52173f39..650591ec0828a2ca2f196eb5cb5954df7f3dde1c 100644 --- a/include/staff/tpl.inc.php +++ b/include/staff/tpl.inc.php @@ -76,7 +76,7 @@ $tpl=$msgtemplates[$selected]; <input type="hidden" name="tpl_id" value="<?php echo $tpl_id; ?>"> </form> <hr/> -<form action="templates.php?id=<?php echo $id; ?>&a=manage" method="post" id="save"> +<form action="templates.php?id=<?php echo $id; ?>&a=manage" method="post" class="save"> <?php csrf_token(); ?> <?php foreach ($extras as $k=>$v) { ?> <input type="hidden" name="<?php echo $k; ?>" value="<?php echo Format::htmlchars($v); ?>" /> diff --git a/include/upgrader/streams/core/8f99b8bf-03ff59bf.task.php b/include/upgrader/streams/core/8f99b8bf-03ff59bf.task.php index cd41e131bdc7617503aa789d731e538ad0fb7db5..1e323d52fa15a984c2e4ba3749cd8a6e3d200b97 100644 --- a/include/upgrader/streams/core/8f99b8bf-03ff59bf.task.php +++ b/include/upgrader/streams/core/8f99b8bf-03ff59bf.task.php @@ -13,7 +13,7 @@ class SequenceLoader extends MigrationTask { $i18n = new Internationalization($cfg->get('system_language', 'en_US')); $sequences = $i18n->getTemplate('sequence.yaml')->getData(); foreach ($sequences as $s) { - Sequence::create($s)->save(); + Sequence::__create($s); } db_query('UPDATE '.SEQUENCE_TABLE.' SET `next`= ' .'(SELECT MAX(ticket_id)+1 FROM '.TICKET_TABLE.') ' diff --git a/index.php b/index.php index 6c9558fa930d71173e59645febce619cf38651af..68062b124c00df15c392dd9ae2c8ee3ac9a4c356 100644 --- a/index.php +++ b/index.php @@ -28,8 +28,8 @@ if ($cfg && $cfg->isKnowledgebaseEnabled()) { ?> <div class="search-form"> <form method="get" action="kb/faq.php"> <input type="hidden" name="a" value="search"/> - <input type="text" name="q" class="search" placeholder="Search our knowledge base"/> - <button type="submit" class="green button">Search</button> + <input type="text" name="q" class="search" placeholder="<?php echo __('Search our knowledge base'); ?>"/> + <button type="submit" class="green button"><?php echo __('Search'); ?></button> </form> </div> <div class="thread-body"> @@ -53,7 +53,7 @@ if($cfg && $cfg->isKnowledgebaseEnabled()){ <?php $cats = Category::getFeatured(); if ($cats->all()) { ?> -<h1>Featured Knowledge Base Articles</h1> +<h1><?php echo __('Featured Knowledge Base Articles'); ?></h1> <?php } diff --git a/login.php b/login.php index c84d06cd87e67bf2e1e01b884081002a094033d0..5c4a713124bc6b55175f48d9f2926e4676f631ec 100644 --- a/login.php +++ b/login.php @@ -86,7 +86,7 @@ elseif ($_POST && isset($_POST['lticket'])) { Http::redirect('tickets.php'); // This will succeed as it is checked in the authentication backend - $ticket = Ticket::lookupByNumber($_POST['lticket']); + $ticket = Ticket::lookupByNumber($_POST['lticket'], $_POST['lemail']); // We're using authentication backend so we can guard aganist brute // force attempts (which doesn't buy much since the link is emailed) diff --git a/main.inc.php b/main.inc.php index 026e440ca84cc904d8a5ebcf8e6b5101cd81d41e..e92ec4a71d3e59876361fba0aa8446328b383779 100644 --- a/main.inc.php +++ b/main.inc.php @@ -27,6 +27,9 @@ Bootstrap::i18n_prep(); Bootstrap::loadCode(); Bootstrap::connect(); +#Global override +$_SERVER['REMOTE_ADDR'] = osTicket::get_client_ip(); + if(!($ost=osTicket::start()) || !($cfg = $ost->getConfig())) Bootstrap::croak(__('Unable to load config info from DB. Get tech support.')); diff --git a/scp/emailtest.php b/scp/emailtest.php index 682749760bc3ce8264b52087a3538d2618c45148..ae8d68579d88768ee3034c856d89ace84c5b6283 100644 --- a/scp/emailtest.php +++ b/scp/emailtest.php @@ -55,7 +55,7 @@ require(STAFFINC_DIR.'header.inc.php'); $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="emailtest.php" method="post" id="save"> +<form action="emailtest.php" method="post" class="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <h2><?php echo __('Test Outgoing Email');?></h2> diff --git a/scp/js/scp.js b/scp/js/scp.js index 3c04b4a9cb6e17d33fd1ea22841433c1c5aaabbc..ae2512f63f921854e78ec117e9061d2bfe5eaadf 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -146,11 +146,11 @@ var scp_prep = function() { } }; - $("form#save").on('change', ':input[name], :button[name]', function() { + $("form.save").on('change', ':input[name], :button[name]', function() { if (!$(this).is('.nowarn')) warnOnLeave($(this)); }); - $("form#save").on('click', ':input[type=reset], :button[type=reset]', function() { + $("form.save").on('click', ':input[type=reset], :button[type=reset]', function() { var fObj = $(this).closest('form'); if(fObj.data('changed')){ $('input[type=submit], button[type=submit]', fObj).removeClass('save pending'); @@ -161,7 +161,7 @@ var scp_prep = function() { } }); - $('form#save, form:has(table.list)').submit(function() { + $('form.save, form:has(table.list)').submit(function() { $(window).unbind('beforeunload'); $('#overlay, #loading').show(); return true; diff --git a/scp/js/upgrader.js b/scp/js/upgrader.js index a10c02626b92fcb6a87680bcabe9c699a627d9a6..831337926e0689f31f1389d7db96a968da89729b 100644 --- a/scp/js/upgrader.js +++ b/scp/js/upgrader.js @@ -27,6 +27,13 @@ jQuery(function($) { }); function autoUpgrade(url, data) { + + if (!$.isFunction('__')) { + function __(s) { + return s; + } + } + function _lp(count) { $.ajax({ type: 'POST', diff --git a/setup/doc/api/tickets.md b/setup/doc/api/tickets.md index 8965e76109cafd1a0133ef6ab4ee3d3344eb5b57..a3ba24ef7d919e6ca1b553021556f40e4e0f2999 100644 --- a/setup/doc/api/tickets.md +++ b/setup/doc/api/tickets.md @@ -36,6 +36,7 @@ request content. following fields (_please refer to the format-specific examples below_): * __name__: *required* name of the file to be attached. Multiple files with the same name are allowable + * __data__: *required* contents of the file to be attached. * __type__: Mime type of the file. Default is `text/plain` * __encoding__: Set to `base64` if content is base64 encoded diff --git a/setup/test/tests/test.validation.php b/setup/test/tests/test.validation.php index bce9fe85505d08e41251fc127e230bba9f775394..0297d51d7d61535f82e27548af169aa7222bb46d 100644 --- a/setup/test/tests/test.validation.php +++ b/setup/test/tests/test.validation.php @@ -54,6 +54,19 @@ class TestValidation extends Test { #$this->assert(Validator::is_email('δοκιμή@παÏάδειγμα.δοκιμή')); #$this->assert(Validator::is_email('甲æ–@é»’å·.日本')); } + + function testIPAddresses() { + + // Validate IP Addreses + $this->assert(Validator::is_ip('127.0.0.1')); + $this->assert(Validator::is_ip('192.168.129.74')); + + // Test IP check + $this->assert(Validator::check_ip('127.0.0.1', '127.0.0.0/24')); + $this->assert(Validator::check_ip('192.168.129.42', + ['127.0.0.0/24', '192.168.129.0/24'])); + $this->assert(!Validator::check_ip('10.0.5.15', '127.0.0.0/24')); + } } return 'TestValidation'; ?>