diff --git a/include/ajax.search.php b/include/ajax.search.php index d8ce8dbab1b4ef7443b5a3b664cdac63f3ce4425..93da251e7d164e9548550dce3b35cf16051bbcf3 100644 --- a/include/ajax.search.php +++ b/include/ajax.search.php @@ -366,7 +366,7 @@ class SearchAjaxAPI extends AjaxController { $field_name = $_GET['field']; $id = $_GET['id']; $object_id = $_GET['object_id']; - $condition = new QueueColumnCondition(); + $condition = new QueueColumnCondition(array()); include STAFFINC_DIR . 'templates/queue-column-condition.tmpl.php'; } diff --git a/include/class.list.php b/include/class.list.php index b504d1bb94437b15ff973555ef73a209df3a31bf..e5c84fbd91b58cc2f8dc742bbec745bba1151882 100644 --- a/include/class.list.php +++ b/include/class.list.php @@ -1282,7 +1282,7 @@ implements CustomListItem, TemplateVariable, Searchable { 'state' => new TicketStateChoiceField(array( 'label' => __('State'), )), - 'name' => new TextBoxField(array( + 'id' => new TicketStatusChoiceField(array( 'label' => __('Status Name'), )), ); diff --git a/include/class.queue.php b/include/class.queue.php index 62e1ef5dcab6dcb81540ccf18ae82d3f1dd7612c..dfa3846425c8d86c867d491f52c2b75be4fafb1e 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -118,12 +118,13 @@ class CustomQueue extends VerySimpleModel { function getCriteria($include_parent=false) { if (!isset($this->criteria)) { - $old = @$this->config[0] === '{'; $this->criteria = is_string($this->config) ? JsonDataParser::decode($this->config) : $this->config; + // XXX: Drop this block in v1.12 // Auto-upgrade v1.10 saved-search criteria to new format // But support new style with `conditions` support + $old = @$this->config[0] === '{'; if ($old && is_array($this->criteria) && !isset($this->criteria['conditions']) ) { @@ -641,7 +642,10 @@ class CustomQueue extends VerySimpleModel { && $this->hasFlag(self::FLAG_INHERIT_COLUMNS) && $this->parent ) { - return $this->parent->getColumns(); + $columns = $this->parent->getColumns(); + foreach ($columns as $c) + $c->setQueue($this); + return $columns; } elseif (count($this->columns)) { return $this->columns; @@ -1214,9 +1218,10 @@ class CustomQueue extends VerySimpleModel { $errors['criteria'] = __('Validation errors exist on criteria'); } else { + $this->criteria = static::isolateCriteria($form->getClean(), + $this->getRoot()); $this->config = JsonDataEncoder::encode([ - 'criteria' => self::isolateCriteria($form->getClean(), - $this->getRoot()), + 'criteria' => $this->criteria, 'conditions' => $conditions, ]); // Clear currently set criteria.and conditions. @@ -1260,6 +1265,44 @@ class CustomQueue extends VerySimpleModel { && $this->sorts->saveAll(); } + /** + * Fetch a tree-organized listing of the queues. Each queue is listed in + * the tree exactly once, and every visible queue is represented. The + * returned structure is an array where the items are two-item arrays + * where the first item is a CustomQueue object an the second is a list + * of the children using the same pattern (two-item arrays of a CustomQueue + * and its children). Visually: + * + * [ [ $queue, [ [ $child, [] ], [ $child, [] ] ], [ $queue, ... ] ] + * + * Parameters: + * $staff - <Staff> staff object which should be used to determine + * visible queues. + * $pid - <int> parent_id of root queue. Default is zero (top-level) + */ + static function getHierarchicalQueues(Staff $staff, $pid=0) { + $all = static::objects() + ->filter(Q::any(array( + 'flags__hasbit' => self::FLAG_PUBLIC, + 'flags__hasbit' => static::FLAG_QUEUE, + 'staff_id' => $staff->getId(), + ))) + ->exclude(['flags__hasbit' => self::FLAG_DISABLED]) + ->asArray(); + + // Find all the queues with a given parent + $for_parent = function($pid) use ($all, &$for_parent) { + $results = []; + foreach (new \ArrayIterator($all) as $q) { + if ($q->parent_id == $pid) + $results[] = [ $q, $for_parent($q->getId()) ]; + } + return $results; + }; + + return $for_parent($pid); + } + static function getOrmPath($name, $query=null) { // Special case for custom data `__answers!id__value`. Only add the // join and constraint on the query the first pass, when the query @@ -1684,7 +1727,8 @@ class QueueColumnCondition { // Add the annotation to a QuerySet function annotate($query) { - $Q = $this->getSearchQ($query); + if (!($Q = $this->getSearchQ($query))) + return $query; // Add an annotation to the query return $query->annotate(array( @@ -1929,6 +1973,7 @@ extends VerySimpleModel { var $_annotations; var $_conditions; + var $_queue; // Apparent queue if being inherited function getId() { return $this->id; @@ -1947,7 +1992,15 @@ extends VerySimpleModel { // These getters fetch data from the annotated overlay from the // queue_column table function getQueue() { - return $this->queue; + return $this->_queue ?: $this->queue; + } + /** + * If a column is inherited into a child queue and there are conditions + * added to that queue, then the column will need to be linked at + * run-time to the child queue rather than the parent. + */ + function setQueue(CustomQueue $queue) { + $this->_queue = $queue; } function getWidth() { @@ -1993,7 +2046,7 @@ extends VerySimpleModel { $text = $this->renderBasicValue($row); // Filter - if ($filter = $this->getFilter()) { + if ($text && ($filter = $this->getFilter())) { $text = $filter->filter($text, $row) ?: $text; } diff --git a/include/class.search.php b/include/class.search.php index a061cb58eecf3f153b3209c36cf377c7a2d7099c..be17902260dd53b308ffdd3a47ad30156ac7d811 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -1408,6 +1408,8 @@ class TicketStatusChoiceField extends SelectionField { function getSearchQ($method, $value, $name=false) { $name = $name ?: $this->get('name'); + if (!$value) + return false; switch ($method) { case '!includes': return Q::not(array("{$name}__in" => array_keys($value))); diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php index 8506fbee9d1ce3d78b429fe74c969f7f2771f158..b936699df859013ba8706f9c27daba2c310a840e 100644 --- a/include/staff/queue.inc.php +++ b/include/staff/queue.inc.php @@ -346,6 +346,8 @@ if ($queue->getConditions()) { foreach ($queue->getConditions() as $i=>$condition) { $id = QueueColumnCondition::getUid(); list($label, $field) = $condition->getField(); + if (!$field || !$label) + continue; $field_name = $condition->getFieldName(); $object_id = $queue->id; include STAFFINC_DIR . 'templates/queue-column-condition.tmpl.php'; diff --git a/include/staff/templates/queue-column.tmpl.php b/include/staff/templates/queue-column.tmpl.php index 806825ad4ade38dbb6c33052c38e1be774626a2d..3ca9d9a310268851e76d061b1b88d8fadbee2c33 100644 --- a/include/staff/templates/queue-column.tmpl.php +++ b/include/staff/templates/queue-column.tmpl.php @@ -122,6 +122,8 @@ if ($column->getConditions(false)) { foreach ($column->getConditions() as $i=>$condition) { $id = QueueColumnCondition::getUid(); list($label, $field) = $condition->getField(); + if (!$label || !$field) + continue; $field_name = $condition->getFieldName(); $object_id = $column->getId(); include STAFFINC_DIR . 'templates/queue-column-condition.tmpl.php'; diff --git a/include/staff/templates/queue-columns.tmpl.php b/include/staff/templates/queue-columns.tmpl.php index 6088c925de0233116b5e39d457741dcb9d3ec9d6..fd8e99529a4ac346ebdfa177b100a735ae5fa9a4 100644 --- a/include/staff/templates/queue-columns.tmpl.php +++ b/include/staff/templates/queue-columns.tmpl.php @@ -1,12 +1,13 @@ <div style="overflow-y: auto; height:auto; max-height: 350px;"> <table class="table"> <?php +$hidden_cols = $queue->inheritColumns() || count($queue->columns) === 0; if ($queue->parent) { ?> <tbody> <tr> <td colspan="3"> <input type="checkbox" name="inherit-columns" <?php - if ($queue->inheritColumns()) echo 'checked="checked"'; ?> + if ($hidden_cols) echo 'checked="checked"'; ?> onchange="javascript:$(this).closest('table').find('.if-not-inherited').toggle(!$(this).prop('checked'));" /> <?php echo __('Inherit columns from the parent queue'); ?> <br /><br /> diff --git a/include/staff/templates/queue-navigation.tmpl.php b/include/staff/templates/queue-navigation.tmpl.php index b903f29fd12e025df3e519af361d0eb693e89a52..3e40918fd8f94efdd80c27d285b626c5e3aff58f 100644 --- a/include/staff/templates/queue-navigation.tmpl.php +++ b/include/staff/templates/queue-navigation.tmpl.php @@ -4,6 +4,7 @@ // $q - <CustomQueue> object for this navigation entry // $selected - <bool> true if this queue is currently active // $child_selected - <bool> true if the selected queue is a descendent +$childs = $children; $this_queue = $q; $selected = (!isset($_REQUEST['a']) && $_REQUEST['queue'] == $this_queue->getId()); ?> @@ -28,8 +29,21 @@ $selected = (!isset($_REQUEST['a']) && $_REQUEST['queue'] == $this_queue->getId </li> <!-- Start Dropdown and child queues --> - <?php foreach ($this_queue->getPublicChildren() as $q) { - include 'queue-subnavigation.tmpl.php'; + <?php foreach ($childs as $_) { + list($q, $children) = $_; + if (!$q->isPrivate()) + include 'queue-subnavigation.tmpl.php'; + } + $first_child = true; + foreach ($childs as $_) { + list($q, $children) = $_; + if (!$q->isPrivate()) + continue; + if ($first_child) { + $first_child = false; + echo '<li class="personalQ"></li>'; + } + include 'queue-subnavigation.tmpl.php'; } ?> <!-- Personal Queues --> <?php diff --git a/include/staff/templates/queue-savedsearches-nav.tmpl.php b/include/staff/templates/queue-savedsearches-nav.tmpl.php index 4899b794a2edce13e0cc6009f6f23beb883cb1e4..ef06cf06c91db2b6e5a32ea0395885cc8d05cd25 100644 --- a/include/staff/templates/queue-savedsearches-nav.tmpl.php +++ b/include/staff/templates/queue-savedsearches-nav.tmpl.php @@ -18,11 +18,13 @@ <!-- Start Dropdown and child queues --> <?php foreach ($searches->findAll(array( 'staff_id' => $thisstaff->getId(), + 'parent_id' => 0, Q::not(array( 'flags__hasbit' => CustomQueue::FLAG_PUBLIC )) )) as $q) { - include 'queue-subnavigation.tmpl.php'; + if ($q->checkAccess($thisstaff)) + include 'queue-subnavigation.tmpl.php'; } ?> <?php if (isset($_SESSION['advsearch'])) { ?> diff --git a/include/staff/templates/queue-subnavigation.tmpl.php b/include/staff/templates/queue-subnavigation.tmpl.php index b0cbeeb75d98b1db5ac61e7004b1cd20aa48c732..fa33b7b7223e23097ee83b1b5a55013efea4c994 100644 --- a/include/staff/templates/queue-subnavigation.tmpl.php +++ b/include/staff/templates/queue-subnavigation.tmpl.php @@ -1,10 +1,9 @@ <?php // Calling conventions // $q - <CustomQueue> object for this navigation entry +// $children - <Array<CustomQueue>> all direct children of this queue $queue = $q; -$children = !$queue instanceof SavedSearch ? $queue->getPublicChildren() : array(); -$subq_searches = !$queue instanceof SavedSearch ? $queue->getMyChildren() : array(); -$hasChildren = count($children) + count($subq_searches) > 0; +$hasChildren = count($children) > 0; $selected = $_REQUEST['queue'] == $q->getId(); global $thisstaff; ?> @@ -27,24 +26,30 @@ global $thisstaff; </a> <?php - $closure_include = function($q) use ($thisstaff, $ost, $cfg) { + $closure_include = function($q, $children) { global $thisstaff, $ost, $cfg; include __FILE__; }; if ($hasChildren) { ?> <ul class="subMenuQ"> <?php - foreach ($children as $q) - $closure_include($q); + foreach ($children as $_) { + list($q, $childs) = $_; + if (!$q->isPrivate()) + $closure_include($q, $childs); + } // Include personal sub-queues $first_child = true; - foreach ($subq_searches as $q) { - if ($first_child) { + foreach ($children as $_) { + list($q, $childs) = $_; + if ($q->isPrivate()) { + if ($first_child) { $first_child = false; echo '<li class="personalQ"></li>'; + } + $closure_include($q, $childs); } - $closure_include($q); } ?> </ul> <?php diff --git a/scp/tickets.php b/scp/tickets.php index 450403f99b1639037ba303f06f8cbfbb99c944fe..82e9b07032bb34897244e6bd88691a8ef6dd5aab 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -445,19 +445,14 @@ $nav->setTabActive('tickets'); $nav->addSubNavInfo('jb-overflowmenu', 'customQ_nav'); // Fetch ticket queues organized by root and sub-queues -$queues = SavedQueue::queues() - ->filter(Q::any(array( - 'flags__hasbit' => CustomQueue::FLAG_PUBLIC, - 'staff_id' => $thisstaff->getId(), - ))) - ->exclude(['flags__hasbit' => CustomQueue::FLAG_DISABLED]) - ->getIterator(); +$queues = CustomQueue::getHierarchicalQueues($thisstaff); // Start with all the top-level (container) queues -foreach ($queues->findAll(array('parent_id' => 0)) -as $q) { - $nav->addSubMenu(function() use ($q, $queue) { - $selected = false; +foreach ($queues as $_) { + list($q, $children) = $_; + if ($q->isPrivate()) + continue; + $nav->addSubMenu(function() use ($q, $queue, $children) { // 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