diff --git a/include/ajax.search.php b/include/ajax.search.php index f886875b71ec6acf6649babf361ee86ffbdc28ea..55dd5aa014083d3453b5cc864351a4ac51b3bbd4 100644 --- a/include/ajax.search.php +++ b/include/ajax.search.php @@ -104,7 +104,7 @@ class SearchAjaxAPI extends AjaxController { $_SESSION['advsearch'] = $form->getState(); Http::response(200, $this->encode(array( - 'redirect' => 'tickets.php?advanced', + 'redirect' => 'tickets.php?queue=adhoc', ))); } diff --git a/include/class.config.php b/include/class.config.php index 74954e42c26ff5d593eee30103825c22eba39bb2..20c09e1dd26d94a713e95bec75ff0e17e17ff6d4 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -1254,7 +1254,7 @@ class OsticketConfig extends Config { return false; // Sort ticket queues - $queues = CustomQueue::objects()->all(); + $queues = CustomQueue::queues()->all(); foreach ($vars['qsort'] as $queue_id => $sort) { if ($q = $queues->findFirst(array('id' => $queue_id))) { $q->sort = $sort; diff --git a/include/class.forms.php b/include/class.forms.php index e71d132fea3b0d78e54892131797e37ef5ca03bc..a5057f3a5ec0906ca0fde20ad1f8f454eba21250 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -4140,7 +4140,7 @@ class FreeTextWidget extends Widget { echo Format::viewableImages($config['content']); ?></div> </div> <?php - if (($attachments=$this->field->getFiles())) { ?> + if (($attachments = $this->field->getFiles()) && count($attachments)) { ?> <section class="freetext-files"> <div class="title"><?php echo __('Related Resources'); ?></div> <?php foreach ($attachments as $attach) { ?> diff --git a/include/class.orm.php b/include/class.orm.php index 8b0fdedefb756fce7c49c00319813c5234d5b7b3..762e3d2f446774fbd999e44850187b7d80e3e1b2 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -2165,13 +2165,17 @@ class SqlCompiler { 'regex' => function($a, $b) { return preg_match("/$a/iu", $b); }, 'hasbit' => function($a, $b) { return ($a & $b) == $b; }, ); } + // TODO: Support Q expressions + if ($check instanceof Q) + return $check->evaluate($record, $field); + list($field, $path, $operator) = self::splitCriteria($field); if (!isset($ops[$operator])) throw new OrmException($operator.': Unsupported operator'); if ($path) $record = $record->getByPath($path); - // TODO: Support Q expressions + return $ops[$operator]($record->get($field), $check); } @@ -3372,6 +3376,26 @@ class Q implements Serializable { return new static($constraints); } + function evaluate($record, $field) { + // Start with FALSE for OR and TRUE for AND + $result = !$this->ored; + foreach ($this->constraints as $check) { + $R = SqlCompiler::evaluate($record, $field, $check); + if ($this->ored) { + if ($result |= $R) + break; + } + elseif (!$R) { + // Anything AND false + $result = false; + break; + } + } + if ($this->negated) + $result = !$result; + return $result; + } + function serialize() { return serialize(array($this->negated, $this->ored, $this->constraints)); } diff --git a/include/class.queue.php b/include/class.queue.php index 69a9363157e3cd84c79112b0f163206071f7d456..2317a3f4aabb06050ae8d5c413fe3991205c1934 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -40,7 +40,7 @@ class CustomQueue extends SavedSearch { ), ); - static function objects() { + static function queues() { return parent::objects()->filter(array( 'flags__hasbit' => static::FLAG_QUEUE )); @@ -108,14 +108,6 @@ class CustomQueue extends SavedSearch { $col->queue = $this; } - function getId() { - return $this->id; - } - - function getRoot() { - return 'Ticket'; - } - function getStatus() { return 'bogus'; } @@ -143,10 +135,6 @@ class CustomQueue extends SavedSearch { )); } - function getPath() { - return $this->path ?: $this->buildPath(); - } - function buildPath() { if (!$this->id) return; @@ -162,12 +150,6 @@ class CustomQueue extends SavedSearch { return $base; } - function getHref() { - // TODO: Get base page from getRoot(); - $root = $this->getRoot(); - return 'tickets.php?queue='.$this->getId(); - } - function inheritCriteria() { return $this->flags & self::FLAG_INHERIT_CRITERIA; } diff --git a/include/class.search.php b/include/class.search.php index 70da8d0fa8d507bcdbff2b293bfff1af0bb4121e..d7d44eff5003d23959eff714d6600dcfeb49d9bd 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -679,10 +679,28 @@ class SavedSearch extends VerySimpleModel { ->exclude(array('flags__hasbit'=>self::FLAG_QUEUE)); } + function getId() { + return $this->id; + } + function getName() { return $this->title; } + function getHref() { + // TODO: Get base page from getRoot(); + $root = $this->getRoot(); + return 'tickets.php?queue='.$this->getId(); + } + + function getRoot() { + return 'Ticket'; + } + + function getPath() { + return $this->path ?: $this->buildPath(); + } + function getCriteria() { if (!isset($this->criteria)) { $this->criteria = JsonDataParser::decode($this->config); @@ -1112,10 +1130,14 @@ class SavedSearch extends VerySimpleModel { // For saved searches (not queues), staff can have a permission to // see all records - return !$this->hasFlag(self::FLAG_QUEUE) + return !$this->isAQueue() && $thisstaff->hasPerm(SearchBackend::PERM_EVERYTHING); } + function isAQueue() { + return $this->hasFlag(self::FLAG_QUEUE); + } + protected function hasFlag($flag) { return $this->flags & $flag !== 0; } diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php index ff03e04d2a29b2ffc4a8b14cb018cc5b733b7fb7..bff6a39b79735ddeb2ebb399bcc2c92353faf347 100644 --- a/include/staff/queue.inc.php +++ b/include/staff/queue.inc.php @@ -74,7 +74,7 @@ else { <div><strong><?php echo __("Parent Queue"); ?>:</strong></div> <select name="parent_id"> <option value="0">— <?php echo __('Top-Level Queue'); ?> —</option> -<?php foreach (CustomQueue::objects() as $cq) { +<?php foreach (CustomQueue::queues() as $cq) { if ($cq->getId() == $queue->getId()) continue; ?> diff --git a/include/staff/queues-ticket.inc.php b/include/staff/queues-ticket.inc.php index 74ff8662c42812b9c39e74c9878c51a3a2676b85..6ddb5c598ae47865ab9342547a037f2aa2e1acf4 100644 --- a/include/staff/queues-ticket.inc.php +++ b/include/staff/queues-ticket.inc.php @@ -17,7 +17,7 @@ require_once INCLUDE_DIR . 'class.queue.php'; </td> <td> <select name="default_ticket_queue"> -<?php foreach (CustomQueue::objects() as $cq) { +<?php foreach (CustomQueue::queues() as $cq) { ?> <option value="<?php echo $cq->id; ?>" <?php if ($cq->getId() == $config['default_ticket_queue']) echo 'selected="selected"'; ?> @@ -78,7 +78,7 @@ require_once INCLUDE_DIR . 'class.queue.php'; </thead> <tbody class="sortable-rows" data-sort="qsort"> <?php -$all_queues = CustomQueue::objects()->all(); +$all_queues = CustomQueue::queues()->all(); $emitLevel = function($queues, $level=0) use ($all_queues, &$emitLevel) { $queues->sort(function($a) { return $a->sort; }); foreach ($queues as $q) { ?> diff --git a/include/staff/templates/queue-savedsearches-nav.tmpl.php b/include/staff/templates/queue-savedsearches-nav.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..38a367b183da7587392b4d0957e1ae3f5df979b2 --- /dev/null +++ b/include/staff/templates/queue-savedsearches-nav.tmpl.php @@ -0,0 +1,41 @@ +<?php +// +// Calling conventions +// $searches = All visibile saved searches +// $child_selected - <bool> true if the selected queue is a descendent +// $adhoc - not FALSE if an adhoc advanced search exists +?> +<li class="item <?php if ($child_selected) echo 'child active'; ?>"> + <a><i class="icon-sort-down pull-right"></i><?php echo __('Search'); + ?></a> + <div class="customQ-dropdown"> + <ul class="scroll-height"> + <!-- Start Dropdown and child queues --> + <?php foreach ($searches->findAll(array( + 'flags__hasbit' => SavedSearch::FLAG_PUBLIC, + )) as $q) { + include 'queue-subnavigation.tmpl.php'; + } ?> + <!-- Dropdown Titles --> + <li> + <h4><?php echo __('Personal Queues'); ?></h4> + </li> + <?php foreach ($searches->findAll(array( + 'staff_id' => $thisstaff->getId(), + Q::not(array( + 'flags__hasbit' => SavedSearch::FLAG_PUBLIC + )) + )) as $q) { + include 'queue-subnavigation.tmpl.php'; + } ?> + </ul> + <!-- Add Queue button sticky at the bottom --> + <div class="add-queue"> + <a class="flush-right full-width" onclick="javascript: + $.dialog('ajax.php/tickets/search', 201);"> + <div class="add pull-right"><i class="green icon-plus-sign"></i></div> + <span><?php echo __('Add personal queue'); ?></span> + </a> + </div> + </div> +</li> diff --git a/include/staff/templates/queue-subnavigation.tmpl.php b/include/staff/templates/queue-subnavigation.tmpl.php index da26d870f67b1968a8e0f25dd331ff84634c3406..9bd90855f654529795edbc5ab25cd5d4ed658069 100644 --- a/include/staff/templates/queue-subnavigation.tmpl.php +++ b/include/staff/templates/queue-subnavigation.tmpl.php @@ -2,7 +2,7 @@ // Calling conventions // $q - <CustomQueue> object for this navigation entry $queue = $q; -$children = $queue->getPublicChildren(); +$children = $queue instanceof CustomQueue ? $queue->getPublicChildren() : array(); $hasChildren = count($children) > 0; $selected = $_REQUEST['queue'] == $q->getId(); global $thisstaff; diff --git a/scp/queues.php b/scp/queues.php index dc95a569764f0df20832efec6e5108a0fb23fea3..a227238adfeb64826093f9cce4de77104353ed2a 100644 --- a/scp/queues.php +++ b/scp/queues.php @@ -44,6 +44,7 @@ if ($_POST) { case 'create': $queue = CustomQueue::create(array( + 'flags' => CustomQueue::FLAG_PUBLIC, 'root' => $_POST['root'] ?: 'Ticket' )); diff --git a/scp/tickets.php b/scp/tickets.php index 5d7db9168ea226fd793458c9dedb0e3a2c44803f..1587110b0d111dd780988da6ef6f69b64894565d 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -60,7 +60,9 @@ if (!$ticket) { require_once INCLUDE_DIR . 'class.queue.php'; $queue_id = @$_REQUEST['queue'] ?: $cfg->getDefaultTicketQueueId(); -$queue = CustomQueue::lookup($queue_id); +if ((int) $queue_id) { + $queue = CustomQueue::lookup($queue_id); +} // Configure form for file uploads $response_form = new SimpleForm(array( @@ -371,7 +373,7 @@ $nav->setTabActive('tickets'); $nav->addSubNavInfo('jb-overflowmenu', 'customQ_nav'); // Fetch ticket queues organized by root and sub-queues -$queues = CustomQueue::objects() +$queues = CustomQueue::queues() ->filter(Q::any(array( 'flags__hasbit' => CustomQueue::FLAG_PUBLIC, 'staff_id' => $thisstaff->getId(), @@ -391,20 +393,39 @@ as $q) { } if (isset($_SESSION['advsearch'])) { - // XXX: De-duplicate and simplify this code - TicketForm::ensureDynamicDataView(); - $search = SavedSearch::create(); - $form = $search->getFormFromSession('advsearch'); - $tickets = TicketModel::objects(); - $tickets = $search->mangleQuerySet($tickets, $form); - $count = $tickets->count(); - $nav->addSubMenu(array('desc' => __('Search').' ('.number_format($count).')', - 'title'=>__('Advanced Ticket Search'), - 'href'=>'tickets.php?status=search', - 'iconclass'=>'Ticket'), - (!$_REQUEST['status'] || $_REQUEST['status']=='search')); + // XXX: De-duplicate and simplify this code + $adhoc = SavedSearch::create(array('title' => __("Advanced Search"))); + $form = $adhoc->getFormFromSession('advsearch'); + $adhoc->config = $form->getState(); + + if ($_REQUEST['queue'] == 'adhoc') + $queue = $adhoc; } +// Add my advanced searches +$nav->addSubMenu(function() use ($queue, $adhoc) { + global $thisstaff; + // A queue is selected if it is the one being displayed. It is + // "child" selected if its ID is in the path of the one selected + $child_selected = $queue && !$queue->isAQueue(); + $searches = SavedSearch::objects() + ->filter(Q::any(array( + 'flags__hasbit' => SavedSearch::FLAG_PUBLIC, + 'staff_id' => $thisstaff->getId(), + ))) + ->exclude(array( + 'flags__hasbit' => SavedSearch::FLAG_QUEUE + )) + ->all(); + + if (isset($adhoc)) { + // TODO: Add "Ad Hoc Search" to the personal children + } + + include STAFFINC_DIR . 'templates/queue-savedsearches-nav.tmpl.php'; +}); + + if ($thisstaff->hasPerm(TicketModel::PERM_CREATE, false)) { $nav->addSubMenu(array('desc'=>__('New Ticket'), 'title'=> __('Open a New Ticket'), @@ -458,10 +479,6 @@ if($ticket) { $tickets = $queue->getQuery(false, $quick_filter); } - //Clear active submenu on search with no status - if($_REQUEST['a']=='search' && !$_REQUEST['status']) - $nav->setActiveSubMenu(-1); - //set refresh rate if the user has it configured if(!$_POST && !$_REQUEST['a'] && ($min=(int)$thisstaff->getRefreshRate())) { $js = "+function(){ var qq = setInterval(function() { if ($.refreshTicketView === undefined) return; clearInterval(qq); $.refreshTicketView({$min}*60000); }, 200); }();";