diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 099d9ecafe64ac75e6edd163bcf4c15a31ba30d7..c50406fcdcff519def7a7651bc5974295eb97465 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -73,15 +73,16 @@ class TicketsAjaxAPI extends AjaxController { $email = $T['user__default_email__address']; $count = $T['tickets']; if ($T['number']) { - $tickets[] = array('id'=>$T['number'], 'value'=>$T['number'], + $tickets[$T['number']] = array('id'=>$T['number'], 'value'=>$T['number'], 'info'=>"{$T['number']} — {$email}", 'matches'=>$_REQUEST['q']); } else { - $tickets[] = array('email'=>$email, 'value'=>$email, + $tickets[$email] = array('email'=>$email, 'value'=>$email, 'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']); } } + $tickets = array_values($tickets); return $this->json_encode($tickets); } diff --git a/include/class.export.php b/include/class.export.php index 2ea048ae92996d72523d50f28849eb6a2cc85490..4ee0c0c796ed0e0d9f69349162ff0dac39817e3c 100644 --- a/include/class.export.php +++ b/include/class.export.php @@ -65,7 +65,6 @@ class Export { $tickets = $sql->models() ->select_related('user', 'user__default_email', 'dept', 'staff', 'team', 'staff', 'cdata', 'topic', 'status', 'cdata__:priority') - ->options(QuerySet::OPT_NOCACHE) ->annotate(array( 'collab_count' => TicketThread::objects() ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1))) diff --git a/include/class.format.php b/include/class.format.php index 216cb6e630ea42be0d8ca88bdca19a372c33b182..2e745a592a3083f876fb53116a60153d5eb98748 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -546,6 +546,10 @@ class Format { if (!$timestamp || !($datetime = DateTime::createFromFormat('U', $timestamp))) return ''; + // Normalize timezone + if ($timezone) + $timezone = Format::timezone($timezone); + // Set the desired timezone (caching since it will be mostly same // for most date formatting. $timezone = Format::timezone($timezone, $cfg->getTimezone()); diff --git a/include/class.forms.php b/include/class.forms.php index 6a1cab27febbd54a78abe67d0422d13c5c61f02c..099c971f5fe5d4d3020c14e19d7b62d4f96b7cc1 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -2159,6 +2159,8 @@ class DatetimeField extends FormField { } function toString($value) { + if (is_array($value)) + return ''; $timestamp = is_int($value) ? $value : (int) strtotime($value); if ($timestamp <= 0) diff --git a/include/class.queue.php b/include/class.queue.php index 9a0ad18620417d0c17c45d71c524031971c80d1e..e336c2085e4e8cead73977a5b9ddf587c4c28f18 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -149,7 +149,7 @@ class CustomQueue extends VerySimpleModel { $all = $this->getSupportedMatches($this->getRoot()); $items = array(); $criteria = $criteria ?: $this->getCriteria(true); - foreach ($criteria as $C) { + foreach ($criteria ?: array() as $C) { list($path, $method, $value) = $C; if ($path === ':keywords') { $items[] = Format::htmlchars("\"{$value}\""); diff --git a/include/class.staff.php b/include/class.staff.php index 9f346df4e58dfc4189571e6d4444858a2b753b88..0deedfefc4568e579f211d098f07899ec8991822 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -598,7 +598,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable { $visibility = Q::any(new Q(array('status__state'=>'open', $assigned))); // -- Routed to a department of mine - if (!$this->showAssignedOnly() && ($depts=$this->getDepts())) { + if (($depts=$this->getDepts()) && count($depts)) { $visibility->add(array('dept_id__in' => $depts)); $visibility->add(array('thread__referrals__dept__id__in' => $depts)); } diff --git a/include/class.thread.php b/include/class.thread.php index a9485d0b85477e339e6d4fc3cbe4ae978a15abb6..f1c6ac988fd06715631d52fa03604da7108045fa 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -161,11 +161,20 @@ implements Searchable { return $collaborators; } + function isCollaborator($user) { + return $this->collaborators->findFirst(array( + 'user_id' => $user->getId(), + 'thread_id' => $this->getId())); + } + function addCollaborator($user, $vars, &$errors, $event=true) { if (!$user) return null; + if ($this->isCollaborator($user)) + return false; + $vars = array_merge(array( 'threadId' => $this->getId(), 'userId' => $user->getId()), $vars); diff --git a/include/class.ticket.php b/include/class.ticket.php index a3f33f0521c2bb510b7e6a24fd07145c6b14b577..e827b4198316879b90391e29310bd6ac15e60dff 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -2657,52 +2657,25 @@ implements RestrictedAccess, Threadable, Searchable { $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; $errors = array(); - - $hdr = Mail_parse::splitHeaders($vars['header'], true); - $existingCollab = Collaborator::getIdByUserId($vars['userId'], $this->getThreadId()); - - if (($vars['userId'] != $this->user_id) && (!$existingCollab)) { - if ($vars['userId'] == 0) { - $emailStream = '<<<EOF' . $vars['header'] . 'EOF'; - $parsed = EmailDataParser::parse($emailStream); - $email = $parsed['email']; - if (!$existinguser = User::lookupByEmail($email)) { - $name = $parsed['name']; - $user = User::fromVars(array('name' => $name, 'email' => $email)); - $vars['userId'] = $user->getId(); + if ($vars['userId'] != $this->user_id) { + if ($vars['userId']) { + $user = User::lookup($vars['userId']); + } elseif ($vars['header'] + && ($hdr= Mail_parse::splitHeaders($vars['header'], true)) + && $hdr['From'] + && ($addr= Mail_Parse::parseAddressList($hdr['From']))) { + $info = array( + 'name' => $addr[0]->personal, + 'email' => $addr[0]->mailbox.'@'.$addr[0]->host); + if ($user=User::fromVars($info)) + $vars['userId'] = $user->getId(); } - } - else - $user = User::lookup($vars['userId']); - - $c = $this->getThread()->addCollaborator($user,array(), $errors); - $addresses = array(); - foreach (array('To', 'TO', 'Cc', 'CC') as $k) { - if ($user && isset($hdr[$k]) && $hdr[$k]) - $addresses[] = Mail_Parse::parseAddressList($hdr[$k]); - } - if (count($addresses) > 1) { - $isMsg = true; - $c->setCc(); - } - } - else { - $c = Collaborator::lookup($existingCollab); - if ($c && !$c->isCc()) { - foreach (array('To', 'TO', 'Cc', 'CC') as $k) { - if (isset($hdr[$k]) && $hdr[$k]) - $addresses[] = Mail_Parse::parseAddressList($hdr[$k]); - } - if (count($addresses) > 1) { - $isMsg = true; - $c->setCc(); + if ($user) { + $c = $this->getThread()->addCollaborator($user,array(), + $errors); } - } - } - - if ($vars['userId'] == $this->user_id) - $isMsg = true; + } // Get active recipients of the response // Initial Message from Tickets created by Agent @@ -3429,7 +3402,6 @@ implements RestrictedAccess, Threadable, Searchable { return true; } - /*============== Static functions. Use Ticket::function(params); =============nolint*/ static function getIdByNumber($number, $email=null, $ticket=false) { diff --git a/include/cli/modules/i18n.php b/include/cli/modules/i18n.php index 2adb14a9051d758dfd678631f3fe3fa37dcaa330..efec614160c9e518674b52a1994650b2b75cfa3e 100644 --- a/include/cli/modules/i18n.php +++ b/include/cli/modules/i18n.php @@ -79,10 +79,9 @@ class i18n_Compiler extends Module { self::$crowdin_api_url); $args += array('key' => $this->key); - foreach ($args as &$a) - $a = urlencode($a); - unset($a); - $url .= '?' . Format::array_implode('=', '&', $args); + if ($branch = $this->getOption('branch', false)) + $args += array('branch' => $branch); + $url .= '?' . Http::build_query($args); return $this->_http_get($url); } @@ -189,7 +188,7 @@ class i18n_Compiler extends Module { $contents = $zip->getFromIndex($i); if (!$contents) continue; - if (fnmatch('*/messages*.po', $info['name']) !== false) { + if (strpos($info['name'], '/messages.po') !== false) { $po_file = $contents; // Don't add the PO file as-is to the PHAR file continue; @@ -222,10 +221,10 @@ class i18n_Compiler extends Module { $this->stderr->write($lang . ": Unable to fetch Redactor language file\n"); // JQuery UI Datepicker - // http://jquery-ui.googlecode.com/svn/tags/latest/ui/i18n/jquery.ui.datepicker-de.js + // https://github.com/jquery/jquery-ui/tree/master/ui/i18n foreach ($langs as $l) { list($code, $js) = $this->_http_get( - 'http://jquery-ui.googlecode.com/svn/tags/latest/ui/i18n/jquery.ui.datepicker-' + 'https://raw.githubusercontent.com/jquery/jquery-ui/master/ui/i18n/datepicker-' .str_replace('_','-',$l).'.js'); // If locale-specific version is not available, use the base // language version (de if de_CH is not available) @@ -673,7 +672,7 @@ class i18n_Compiler extends Module { $this->stdout->write(sprintf( "'%s' (%s) and '%s' (%s)\n", $orig, $usage, $other_orig, $other_usage - )); + )); } } } diff --git a/scp/css/scp.css b/scp/css/scp.css index f255ae506c045beacf0499e528f31b4b5adee4b1..9d59e5eeac77a58e27ef5b224f7ca083746ac747 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -1849,6 +1849,7 @@ ul.tabs.vertical li a { } ul.tabs.alt { + height: auto; background-color:initial; border-bottom:2px solid #ccc; border-bottom-color: rgba(0,0,0,0.1);