diff --git a/include/ajax.forms.php b/include/ajax.forms.php index 1d4dc14236ad922978c8cefd7e8020bc1a6d14da..f99870a9770d436cc36c028c068a5d25ba9061d9 100644 --- a/include/ajax.forms.php +++ b/include/ajax.forms.php @@ -358,7 +358,7 @@ class DynamicFormsAjaxAPI extends AjaxController { function attach() { $field = new FileUploadField(); return JsonDataEncoder::encode( - array('id'=>$field->ajaxUpload(true)) + array('id'=>$field->ajaxUpload()) ); } diff --git a/include/ajax.orgs.php b/include/ajax.orgs.php index f7be2b2d130e5672e71a88669b102cb2b96f74ee..1a1c8d19ca060a22e139135cae4d5af4965510f4 100644 --- a/include/ajax.orgs.php +++ b/include/ajax.orgs.php @@ -24,7 +24,10 @@ class OrgsAjaxAPI extends AjaxController { if(!isset($_REQUEST['q'])) { Http::response(400, 'Query argument is required'); - } + } + + if (!$_REQUEST['q']) + return $this->json_encode(array()); $q = $_REQUEST['q']; $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 6acae0719f35404ed9b43e871a238b53d92c6a22..c20abba7ec0120fbaceffca81f762c5e5eb0c650 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -26,14 +26,17 @@ class TicketsAjaxAPI extends AjaxController { function lookup() { global $thisstaff; - $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $tickets=array(); + // Bail out of query is empty + if (!$_REQUEST['q']) + return $this->json_encode($tickets); $visibility = Q::any(array( 'staff_id' => $thisstaff->getId(), 'team_id__in' => $thisstaff->teams->values_flat('team_id'), )); + if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts())) { $visibility->add(array('dept_id__in' => $depts)); } diff --git a/include/ajax.users.php b/include/ajax.users.php index 22257675d152e6eed6e4916f5de17b0f7f5acb05..f8b9b78b4ea8a8d7dfc3c3d0a3c67352067e214b 100644 --- a/include/ajax.users.php +++ b/include/ajax.users.php @@ -29,6 +29,10 @@ class UsersAjaxAPI extends AjaxController { Http::response(400, __('Query argument is required')); } + $matches = array(); + if (!$_REQUEST['q']) + return $this->json_encode($matches); + $q = $_REQUEST['q']; $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $users=array(); diff --git a/include/class.auth.php b/include/class.auth.php index 9619a389f0af1b32cca214132205155d915f8d6f..a106d6a4c9fa89dce87115dfa7c460b4f19a0903 100644 --- a/include/class.auth.php +++ b/include/class.auth.php @@ -1006,8 +1006,9 @@ class PasswordResetTokenBackend extends StaffAuthenticationBackend { return false; elseif (!($_config = new Config('pwreset'))) return false; - elseif (($staff = StaffSession::lookup($_POST['userid'])) && - !$staff->getId()) + + $staff = StaffSession::lookup($_POST['userid']); + if (!$staff || !$staff->getId()) $errors['msg'] = __('Invalid user-id given'); elseif (!($id = $_config->get($_POST['token'])) || $id != $staff->getId()) diff --git a/include/class.client.php b/include/class.client.php index 932a5bde686bea6071c23d413368c39dc1fde5b7..9a624e13c545934b7bd864551152ec05b5525c8e 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -53,6 +53,8 @@ implements EmailContact, ITicketUser, TemplateVariable { } function getVar($tag) { + global $cfg; + switch (strtolower($tag)) { case 'ticket_link': $qstr = array(); diff --git a/include/class.config.php b/include/class.config.php index 8b7dde5d8542e1e59f35bffd6a7c8021041579cc..61b5012dfb1b6309fec706a0f457a917e6297185 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -150,8 +150,19 @@ extends VerySimpleModel { ); static function items($namespace, $column='namespace') { - return static::objects() + + $items = static::objects() ->filter([$column => $namespace]); + + try { + count($items); + } + catch (InconsistentModelException $ex) { + // Pending upgrade ?? + $items = array(); + } + + return $items; } function save($refetch=false) { diff --git a/include/class.export.php b/include/class.export.php index bcf509299946398a188bbcafa9a1552eca7ef883..72b66967adde73f9d2e912017292d0b85dd6d151 100644 --- a/include/class.export.php +++ b/include/class.export.php @@ -59,7 +59,7 @@ class Export { // Reset the $sql query $tickets = $sql->models() ->select_related('user', 'user__default_email', 'dept', 'staff', - 'team', 'staff', 'cdata', 'topic', 'status', 'cdata.priority') + 'team', 'staff', 'cdata', 'topic', 'status', 'cdata__:priority') ->annotate(array( 'collab_count' => TicketThread::objects() ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1))) @@ -81,7 +81,7 @@ class Export { 'cdata.subject' => __('Subject'), 'user.name' => __('From'), 'user.default_email.address' => __('From Email'), - 'cdata.priority.priority_desc' => __('Priority'), + 'cdata.:priority.priority_desc' => __('Priority'), 'dept::getLocalName' => __('Department'), 'topic::getName' => __('Help Topic'), 'source' => __('Source'), diff --git a/include/class.filter_action.php b/include/class.filter_action.php index 98d71d85ec55fc407dd3db3dd208c00eacc1d27a..6c687cc6faee238d1adb0f007560eb6941404d12 100644 --- a/include/class.filter_action.php +++ b/include/class.filter_action.php @@ -541,12 +541,14 @@ class FA_SendEmail extends TriggerAction { } )), 'subject' => new TextboxField(array( + 'required' => true, 'configuration' => array( 'size' => 80, 'placeholder' => __('Subject') ), )), 'message' => new TextareaField(array( + 'required' => true, 'configuration' => array( 'placeholder' => __('Message'), 'html' => true, diff --git a/include/class.format.php b/include/class.format.php index 70b06a8a37c09f05f0e27017f9d520c08f99cd29..dda04156936c4046bc1d677470257cb46062f2ea 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -194,6 +194,9 @@ class Format { function html2text($html, $width=74, $tidy=true) { + if (!$html) + return $html; + # Tidy html: decode, balance, sanitize tags if($tidy) @@ -201,8 +204,9 @@ class Format { # See if advanced html2text is available (requires xml extension) if (function_exists('convert_html_to_text') - && extension_loaded('dom')) - return convert_html_to_text($html, $width); + && extension_loaded('dom') + && ($text = convert_html_to_text($html, $width))) + return $text; # Try simple html2text - insert line breaks after new line tags. $html = preg_replace( @@ -518,7 +522,9 @@ class Format { $format = self::getStrftimeFormat($format); // Properly convert to user local time - $time = DateTime::createFromFormat('U', $timestamp, new DateTimeZone('UTC')); + if (!($time = DateTime::createFromFormat('U', $timestamp, new DateTimeZone('UTC')))) + return ''; + $offset = $user_timezone->getOffset($time); $timestamp = $time->getTimestamp() + $offset; return strftime($format ?: $strftimeFallback, $timestamp); diff --git a/include/class.i18n.php b/include/class.i18n.php index 9163f6cf0d4bfdd4c687f3d54f9faa015b5d0087..c290821194ddacee624ef6a4400da4f170db2857 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -260,6 +260,7 @@ class Internationalization { return self::availableLanguages(); if (!isset($langs)) { + $langs = array(); $pri = $cfg->getPrimaryLanguage(); if ($info = self::getLanguageInfo($pri)) $langs = array($pri => $info); diff --git a/include/class.orm.php b/include/class.orm.php index 1638d8b97de68423fd079d3ee8733705e3582aa0..f816265e326e1014eac1ec9407713784d7534577 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -1029,7 +1029,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl } function isWindowed() { - return $this->limit || $this->offset; + return $this->limit || $this->offset || (count($this->values) + count($this->annotations) + @count($this->extra['select'])) > 1; } function select_related() { diff --git a/include/class.thread.php b/include/class.thread.php index 06ad342f583a99f3b1b0574393c267cda861c39d..0a1162487feece9b9d247574657b2287a75afa24 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -604,6 +604,8 @@ implements TemplateVariable { var $_headers; var $_thread; var $_actions; + var $is_autoreply; + var $is_bounce; static protected $perms = array( self::PERM_EDIT => array( diff --git a/include/class.ticket.php b/include/class.ticket.php index 43e74d1756d63ce47869d026d32f39cef608626f..8e0871df0160a787dbe1c8d26550d25d9c199f5e 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -224,7 +224,7 @@ class TicketCData extends VerySimpleModel { 'ticket' => array( 'constraint' => array('ticket_id' => 'TicketModel.ticket_id'), ), - 'priority' => array( + ':priority' => array( 'constraint' => array('priority' => 'Priority.priority_id'), 'null' => true, ), @@ -1351,8 +1351,8 @@ implements RestrictedAccess, Threadable { // Only alerts dept members if the ticket is NOT assigned. if ($cfg->alertDeptMembersONNewTicket() && !$this->isAssigned()) { - if ($members = $dept->getMembersForAlerts()->all()) - $recipients = array_merge($recipients, $members); + if (($members = $dept->getMembersForAlerts())) + $recipients = array_merge($recipients, $members->all()); } if ($cfg->alertDeptManagerONNewTicket() && $dept && @@ -1542,6 +1542,9 @@ implements RestrictedAccess, Threadable { $this->setStaffId(0); // Clear assignment } + if (!$autorespond) + return; + // Figure out the user if ($this->getOwnerId() == $message->getUserId()) $user = new TicketOwner( @@ -1553,11 +1556,11 @@ implements RestrictedAccess, Threadable { /********** double check auto-response ************/ if (!$user) - $autorespond=false; - elseif ($autorespond && (Email::getIdByEmail($user->getEmail()))) - $autorespond=false; - elseif ($autorespond && ($dept=$this->getDept())) - $autorespond=$dept->autoRespONNewMessage(); + $autorespond = false; + elseif ((Email::getIdByEmail($user->getEmail()))) + $autorespond = false; + elseif (($dept=$this->getDept())) + $autorespond = $dept->autoRespONNewMessage(); if (!$autorespond || !$cfg->autoRespONNewMessage() @@ -2293,28 +2296,18 @@ implements RestrictedAccess, Threadable { } // Do not auto-respond to bounces and other auto-replies - if ($alerts) - $alerts = isset($vars['mailflags']) + $autorespond = isset($vars['mailflags']) ? !$vars['mailflags']['bounce'] && !$vars['mailflags']['auto-reply'] : true; - if ($alerts && $message->isBounceOrAutoReply()) - $alerts = false; - - if ($alerts && $this->getThread()->getLastEmailMessage(array( - 'user_id' => $message->user_id, - 'id__lt' => $message->id, - 'created__gt' => SqlFunction::NOW()->minus(SqlInterval::MINUTE(5)), - ))) { - // One message already from this user in the last five minutes - $alerts = false; - } + if ($autorespond && $message->isBounceOrAutoReply()) + $autorespond = false; - $this->onMessage($message, $alerts); //must be called b4 sending alerts to staff. + $this->onMessage($message, $autorespond); //must be called b4 sending alerts to staff. - if ($alerts && $cfg && $cfg->notifyCollabsONNewMessage()) + if ($autorespond && $cfg && $cfg->notifyCollabsONNewMessage()) $this->notifyCollaborators($message, array('signature' => '')); - if (!$alerts) + if (!($alerts && $autorespond)) return $message; //Our work is done... $dept = $this->getDept(); @@ -2388,8 +2381,10 @@ implements RestrictedAccess, Threadable { return false; } $files = array(); - foreach ($canned->attachments->getAll() as $file) + foreach ($canned->attachments->getAll() as $file) { $files[] = $file->file_id; + $_SESSION[':cannedFiles'][$file->file_id] = 1; + } if ($cfg->isRichTextEnabled()) $response = new HtmlThreadEntryBody( diff --git a/include/class.topic.php b/include/class.topic.php index d448ea7ec10279dfef915674ee253d50e785efa5..731ea0e1c55acbc5844a064867b9c2ece1743088 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -418,7 +418,6 @@ implements TemplateVariable { $this->priority_id = $vars['priority_id'] ?: 0; $this->status_id = $vars['status_id'] ?: 0; $this->sla_id = $vars['sla_id'] ?: 0; - $this->form_id = $vars['form_id'] ?: 0; $this->page_id = $vars['page_id'] ?: 0; $this->isactive = !!$vars['isactive']; $this->ispublic = !!$vars['ispublic']; diff --git a/include/class.user.php b/include/class.user.php index 165eebb100afe5c47ecf9b056d65f35f19f0cee5..cc64e736298c436b6398ece1700380728fb469d1 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -31,7 +31,7 @@ class UserEmailModel extends VerySimpleModel { ); function __toString() { - return $this->address; + return (string) $this->address; } } @@ -616,7 +616,7 @@ implements TemplateVariable { } function __toString() { - return $this->address; + return (string) $this->address; } function getVar($what) { diff --git a/include/html2text.php b/include/html2text.php index 2863e10524cac3e7d500ad5e6b9710c5eb0db21e..d8ef77c5e77c51284a0fa9e304c6989754873f58 100644 --- a/include/html2text.php +++ b/include/html2text.php @@ -181,7 +181,7 @@ class HtmlInlineElement { } function traverse($node) { - if ($node->hasChildNodes()) { + if ($node && $node->hasChildNodes()) { for ($i = 0; $i < $node->childNodes->length; $i++) { $n = $node->childNodes->item($i); $this->children[] = identify_node($n, $this); @@ -194,7 +194,9 @@ class HtmlInlineElement { $after_block = false; $this->ws = $this->getStyle('white-space', 'normal'); // Direction - $dir = $this->node->getAttribute('dir'); + if ($this->node) + $dir = $this->node->getAttribute('dir'); + // Ensure we have a value, but don't emit a control char unless // direction is declared $this->dir = $dir ?: 'ltr'; @@ -287,10 +289,11 @@ class HtmlInlineElement { if ($this->style && $this->style->has($property)) return $this->style->get($property, $default); - if ($tag === false) + if ($this->node && $tag === false) $tag = $this->node->nodeName; + if ($classes === false) { - if ($c = $this->node->getAttribute('class')) + if ($this->node && ($c = $this->node->getAttribute('class'))) $classes = explode(' ', $c); else $classes = array(); diff --git a/include/staff/plugin.inc.php b/include/staff/plugin.inc.php index 58d4a0313d852d26a59ac040b8be6b536870f2ba..6ab5b3c284d7c1eedf88a477a17cc931c9f05a70 100644 --- a/include/staff/plugin.inc.php +++ b/include/staff/plugin.inc.php @@ -1,9 +1,9 @@ <?php - -$info=array(); +$info = array(); +$page = null; if($plugin && $_REQUEST['a']!='add') { $config = $plugin->getConfig(); - if (!($page = $config->hasCustomConfig())) { + if ($config && !($page = $config->hasCustomConfig())) { if ($config) $form = $config->getForm(); if ($form && $_POST) diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 9a3c5e18a0ebaf92c3c300f8c28e719a8ac237c2..99bfb2381c0efd0b717b705181edf92379051284 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -189,6 +189,7 @@ case 'search': // Fall-through and show open tickets case 'open': $status='open'; + $queue_name = $queue_name ?: 'open'; $results_type=__('Open Tickets'); if (!$cfg->showAnsweredTickets()) $tickets->filter(array('isanswered'=>0)); @@ -273,7 +274,7 @@ case 'number': break; case 'priority,created': - $tickets->order_by(($sort_dir ? '-' : '') . 'cdata__priority__priority_urgency'); + $tickets->order_by(($sort_dir ? '-' : '') . 'cdata__:priority__priority_urgency'); // Fall through to columns for `created` case 'created': $queue_columns['date']['heading'] = __('Date Created'); @@ -283,7 +284,7 @@ case 'created': break; case 'priority,due': - $tickets->order_by('cdata__priority__priority_urgency', $orm_dir_r); + $tickets->order_by('cdata__:priority__priority_urgency', $orm_dir_r); // Fall through to add in due date filter case 'due': $queue_columns['date']['heading'] = __('Due Date'); @@ -340,7 +341,7 @@ default: } case 'priority,updated': - $tickets->order_by('cdata__priority__priority_urgency', $orm_dir_r); + $tickets->order_by('cdata__:priority__priority_urgency', $orm_dir_r); // Fall through for columns defined for `updated` case 'updated': $queue_columns['date']['heading'] = __('Last Updated'); @@ -371,7 +372,9 @@ TicketForm::ensureDynamicDataView(); // Select pertinent columns // ------------------------------------------------------------ -$tickets->values('lock__staff_id', 'staff_id', 'isoverdue', 'team_id', 'ticket_id', 'number', 'cdata__subject', 'user__default_email__address', 'source', 'cdata__priority__priority_color', 'cdata__priority__priority_desc', 'status_id', 'status__name', 'status__state', 'dept_id', 'dept__name', 'user__name', 'lastupdate', 'isanswered', 'staff__firstname', 'staff__lastname', 'team__name'); +$tickets->values('lock__staff_id', 'staff_id', 'isoverdue', 'team_id', +'ticket_id', 'number', 'cdata__subject', 'user__default_email__address', +'source', 'cdata__:priority__priority_color', 'cdata__:priority__priority_desc', 'status_id', 'status__name', 'status__state', 'dept_id', 'dept__name', 'user__name', 'lastupdate', 'isanswered', 'staff__firstname', 'staff__lastname', 'team__name'); // Add in annotations $tickets->annotate(array( @@ -546,7 +549,7 @@ return false;"> ><?php echo $tid; ?></a></td> <td align="center" nowrap><?php echo Format::datetime($T[$date_col ?: 'lastupdate']) ?: $date_fallback; ?></td> <td><div style="max-width: <?php - $base = 280; + $base = 279; // Make room for the paperclip and some extra if ($T['attachment_count']) $base -= 18; // Assume about 8px per digit character @@ -582,8 +585,9 @@ return false;"> $displaystatus="<b>$displaystatus</b>"; echo "<td>$displaystatus</td>"; } else { ?> - <td class="nohover" align="center" style="background-color:<?php echo $T['cdata__priority__priority_color']; ?>;"> - <?php echo $T['cdata__priority__priority_desc']; ?></td> + <td class="nohover" align="center" + style="background-color:<?php echo $T['cdata__:priority__priority_color']; ?>;"> + <?php echo $T['cdata__:priority__priority_desc']; ?></td> <?php } ?> diff --git a/scp/css/scp.css b/scp/css/scp.css index 1aa6361639c51abee2bff81811197a7d4fb31669..8050c819b3f12c5f4bdaddce27ab7c41c1534bbe 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2835,6 +2835,16 @@ input[type=checkbox] { margin-top: 3px; } +input, textarea { + padding: 3px 5px; + font-size: 0.95em; + font-family: inherit; + border-radius:4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border: 1px solid #bbb; +} + small { font-weight: normal; letter-spacing: 0.01px; diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php index 49ef4baa1d721e7f1e28783d8228e754a73a9f43..6bcbf3110d835657c99f9383807a3b6aa7b0a3a3 100644 --- a/setup/test/tests/stubs.php +++ b/setup/test/tests/stubs.php @@ -127,6 +127,7 @@ class IntlBreakIterator { class SqlFunction { static function NOW() {} + static function LENGTH() {} static function COALESCE() {} static function DATEDIFF() {} }