diff --git a/include/class.nav.php b/include/class.nav.php index dfe308f796f6538be1177c10da4febc0c6e79af7..9597d0b7daca86bf79a87fcf939d9d4007f8cab5 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -20,6 +20,7 @@ class StaffNav { var $activetab; var $activeMenu; var $panel; + var $subnavinfo; var $staff; @@ -109,6 +110,17 @@ class StaffNav { $this->activeMenu=sizeof($this->submenus[$this->getPanel().'.'.$this->activetab]); } + function addSubNavInfo($classes=null, $id=null) { + $T = $this->subnavinfo; + $this->subnavinfo = array( + 'classes' => (@$T['classes'] ?: '') . ($classes ? " $classes" : ''), + 'id' => $id ?: @$T['id'], + ); + } + + function getSubNavInfo() { + return $this->subnavinfo; + } function getTabs(){ global $thisstaff; diff --git a/include/class.orm.php b/include/class.orm.php index 5c247029590f7bcddbb54d8867e33394ca7a9b20..afb6ede9f7bd78adaed577d21bbef00b6eb26f65 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -1196,7 +1196,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl * If no such model or multiple models exist, an exception is thrown. */ function one() { - $list = $this->all(); + $list = $this->all()->asArray(); if (count($list) == 0) throw new DoesNotExist(); elseif (count($list) > 1) @@ -1846,6 +1846,82 @@ implements Iterator { $this->eoi = true; } } + + /** + * Find the first item in the current set which matches the given criteria. + * This would be used in favor of ::filter() which might trigger another + * database query. See ::findAll() for more details. + * + * Example: + * >>> $a = new User(); + * >>> $a->roles->add(Role::lookup(['name' => 'administator'])); + * >>> $a->roles->findFirst(['roles__name__startswith' => 'admin']); + * <Role: administrator> + */ + function findFirst(array $criteria) { + $records = $this->findAll($criteria, 1); + return @$records[0]; + } + + /** + * Find the first item in the current set which matches the given criteria. + * This would be used in favor of ::filter() which might trigger another + * database query. The criteria is intended to be quite simple and should + * not traverse relationships which have not already been fetched. + * Otherwise, the ::filter() or ::window() methods would provide better + * performance, as they can provide results with one more trip to the + * database. + */ + function findAll(array $criteria, $limit=false) { + $records = array(); + foreach ($this as $record) { + $matches = true; + foreach ($criteria as $field => $check) { + if (!SqlCompiler::evaluate($record, $field, $check)) { + $matches = false; + break; + } + } + if ($matches) + $records[] = $record; + } + return $records; + } + + /** + * Sort the instrumented list in place. This would be useful to change the + * sorting order of the items in the list without fetching the list from + * the database again. + * + * Parameters: + * $key - (callable|int) A callable function to produce the sort keys + * or one of the SORT_ constants used by the array_multisort + * function + * $reverse - (bool) true if the list should be sorted descending + * + * Returns: + * This instrumented list for chaining and inlining. + */ + function sort($key=false, $reverse=false) { + // Fetch all records into the cache + $this->asArray(); + if (is_callable($key)) { + array_multisort( + array_map($key, $this->cache), + $reverse ? SORT_DESC : SORT_ASC, + $this->cache); + } + elseif ($key) { + array_multisort($this->cache, + $reverse ? SORT_DESC : SORT_ASC, $key); + } + elseif ($reverse) { + rsort($this->cache); + } + else + sort($this->cache); + return $this; + } } // Use a global variable, as constructing exceptions is expensive @@ -2076,7 +2152,9 @@ class SqlCompiler { 'lte' => function($a, $b) { return $a <= $b; }, 'contains' => function($a, $b) { return stripos($a, $b) !== false; }, 'startswith' => function($a, $b) { return stripos($a, $b) === 0; }, - 'hasbit' => function($a, $b) { return $a & $b == $b; }, + 'endswith' => function($a, $b) { return iEndsWith($a, $b); }, + 'regex' => function($a, $b) { return preg_match("/$a/iu", $b); }, + 'hasbit' => function($a, $b) { return ($a & $b) == $b; }, ); } list($field, $path, $operator) = self::splitCriteria($field); if (!isset($ops[$operator])) diff --git a/include/class.queue.php b/include/class.queue.php index c3a4d202ae3e884c2cd133c75d9f2ca656ffebb7..821b30514096018abd440d6f3685a16d0547e1ce 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -26,6 +26,15 @@ class CustomQueue extends SavedSearch { 'constraint' => array( 'staff_id' => 'Staff.staff_id', ) + ), + 'parent' => array( + 'constraint' => array( + 'parent_id' => 'CustomQueue.id', + ), + 'null' => true, + ), + 'children' => array( + 'reverse' => 'CustomQueue.parent', ) ), ); @@ -110,9 +119,39 @@ class CustomQueue extends SavedSearch { return 'bogus'; } + function getChildren() { + return $this->children; + } + + function getPublicChildren() { + return $this->children->findAll(array( + 'flags__hasbit' => self::FLAG_PUBLIC + )); + } + + function getMyChildren() { + global $thisstaff; + if (!$thisstaff instanceof Staff) + return array(); + + return $this->children->findAll(array( + 'staff_id' => $thisstaff->getId(), + Q::not(array( + 'flags__hasbit' => self::FLAG_PUBLIC + )) + )); + } + + function getHref() { + // TODO: Get base page from getRoot(); + $root = $this->getRoot(); + return 'tickets.php?queue='.$this->getId(); + } + function getBasicQuery($form=false) { $root = $this->getRoot(); $query = $root::objects(); + $form = $form ?: $this->loadFromState($this->getCriteria()); return $this->mangleQuerySet($query, $form); } @@ -146,6 +185,7 @@ class CustomQueue extends SavedSearch { // Set basic queue information $this->title = $vars['name']; + $this->parent_id = $vars['parent_id']; // Update queue columns (but without save) if (isset($vars['columns'])) { diff --git a/include/class.search.php b/include/class.search.php index 91c19f3c38886df2dd8384de29be7354be355445..862567654df5aa62f824be8d917f1930d16f0b56 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -664,8 +664,11 @@ class SavedSearch extends VerySimpleModel { 'ordering' => array('sort'), ); - const FLAG_PUBLIC = 0x0001; - const FLAG_QUEUE = 0x0002; + const FLAG_PUBLIC = 0x0001; // Shows up in e'eryone's saved searches + const FLAG_QUEUE = 0x0002; // Shows up in queue navigation + const FLAG_CONTAINER = 0x0004; // Container for other queues ('Open') + + var $criteria; static function forStaff(Staff $agent) { return static::objects()->filter(Q::any(array( @@ -678,6 +681,13 @@ class SavedSearch extends VerySimpleModel { return $this->title; } + function getCriteria() { + if (!isset($this->criteria)) { + $this->criteria = JsonDataParser::decode($this->config); + } + return $this->criteria; + } + function getSearchForm() { if ($state = JsonDataParser::parse($this->config)) { $form = $this->loadFromState($state); diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index c37893e1f13926b7c76302491600a2e5cdac1f40..3bc47d876ea4b507f9621d5276f5790c4eebc410 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -94,11 +94,7 @@ if ($lang) { <ul id="nav"> <?php include STAFFINC_DIR . "templates/navigation.tmpl.php"; ?> </ul> - <nav id="customQ_nav" class="jb-overflowmenu"> - <ul> - <?php include STAFFINC_DIR . "templates/sub-navigation.tmpl.php"; ?> - </ul> - </nav> + <?php include STAFFINC_DIR . "templates/sub-navigation.tmpl.php"; ?> <div id="content"> <?php if($errors['err']) { ?> diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php index 0c5b36a0a6ba82220eefac1410d04055110ca0d9..f01789c5405ffbb781cae3793d344a611d6b00b2 100644 --- a/include/staff/queue.inc.php +++ b/include/staff/queue.inc.php @@ -71,8 +71,13 @@ 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) { ?> - <option value="<?php echo $cq->id; ?>"><?php echo $cq->getName(); ?></option> +<?php foreach (CustomQueue::objects() as $cq) { + if ($cq->getId() == $queue->getId()) + continue; +?> + <option value="<?php echo $cq->id; ?>" + <?php if ($cq->getId() == $queue->parent_id) echo 'selected="selected"'; ?> + ><?php echo $cq->getName(); ?></option> <?php } ?> </select> diff --git a/include/staff/templates/queue-navigation.tmpl.php b/include/staff/templates/queue-navigation.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..0e78f83301f64c373a2f9a04a88fe05db0edf420 --- /dev/null +++ b/include/staff/templates/queue-navigation.tmpl.php @@ -0,0 +1,36 @@ +<?php +// +// Calling conventions +// $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 +$queue = $q; +$selected = $_REQUEST['queue'] == $queue->getId(); +?> +<li class="item <?php if ($child_selected) echo 'child active'; + elseif ($selected) echo 'active'; ?>"> + <a href="<?php echo $queue->getHref(); ?>"><i class="icon-sort-down pull-right"></i><?php echo $queue->getName(); ?></a> + <div class="customQ-dropdown"> + <ul class="scroll-height"> + <!-- Start Dropdown and child queues --> + <?php foreach ($queue->getPublicChildren() as $q) { + include 'queue-subnavigation.tmpl.php'; + } ?> + <!-- Dropdown Titles --> + <li> + <h4><?php echo __('Personal Queues'); ?></h4> + </li> + <?php foreach ($queue->getMyChildren() 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 new file mode 100644 index 0000000000000000000000000000000000000000..da26d870f67b1968a8e0f25dd331ff84634c3406 --- /dev/null +++ b/include/staff/templates/queue-subnavigation.tmpl.php @@ -0,0 +1,52 @@ +<?php +// Calling conventions +// $q - <CustomQueue> object for this navigation entry +$queue = $q; +$children = $queue->getPublicChildren(); +$hasChildren = count($children) > 0; +$selected = $_REQUEST['queue'] == $q->getId(); +global $thisstaff; +?> +<!-- SubQ class: only if top level Q has subQ --> +<li class="<?php if ($hasChildren) echo 'subQ'; ?>"> + <?php + if ($hasChildren) { ?> + <i class="icon-caret-down"></i> + <?php } + if ($thisstaff->isAdmin()) { ?> + <!-- Edit Queue --> + <div class="editQ pull-right"> + <i class="icon-cog"></i> + <div class="manageQ"> + <ul> + <?php if ($hasChildren) { ?> + <li class="positive"> + <a href="<?php echo $queue->getHref(); ?>"> + <i class="icon-fixed-width icon-plus-sign"></i><?php echo __('Add Queue'); ?></a> + </li> + <?php } ?> + <li> + <a href="queues.php?id=<?php echo $q->getId(); ?>"> + <i class="icon-fixed-width icon-pencil"></i> + <?php echo __('Edit'); ?></a> + </li> + <li class="danger"> + <a href="#"><i class="icon-fixed-width icon-trash"></i><?php echo __('Delete'); ?></a> + </li> + </ul> + </div> + </div> + <?php } ?> + <!-- Display Latest Ticket count --> + <span class="pull-right">(?)</span> + <!-- End Edit Queue --> + <a class="truncate <?php if ($selected) echo ' active'; ?>" href="<?php echo $queue->getHref(); + ?>"><?php echo $q->getName(); ?></a> +<?php if ($hasChildren) { + echo '<ul>'; + foreach ($children as $q) { + include __FILE__; + } + echo '</ul>'; +} ?> +</li> diff --git a/include/staff/templates/sub-navigation.tmpl.php b/include/staff/templates/sub-navigation.tmpl.php index 3a3c7e9f53313758c9382e8619f838464beef220..ecf520faf55f2712be717500b08dd5700422a737 100644 --- a/include/staff/templates/sub-navigation.tmpl.php +++ b/include/staff/templates/sub-navigation.tmpl.php @@ -1,9 +1,18 @@ -<?php /* +<?php if(($subnav=$nav->getSubMenu()) && is_array($subnav)){ $activeMenu=$nav->getActiveMenu(); if($activeMenu>0 && !isset($subnav[$activeMenu-1])) $activeMenu=0; + $info = $nav->getSubNavInfo(); +?> +<nav class="<?php echo @$info['class']; ?>" id="<?php echo $info['id']; ?>"> + <ul id="sub_nav"> +<?php foreach($subnav as $k=> $item) { + if (is_callable($item)) { + $item($tab, $nav); + continue; + } if($item['droponly']) continue; $class=$item['iconclass']; if ($activeMenu && $k+1==$activeMenu @@ -27,644 +36,6 @@ if(($subnav=$nav->getSubMenu()) && is_array($subnav)){ $class, $item['href'], $item['title'], $id, $attr, $item['desc']); } } -*/ ?> -<!-- .item is required for overflow of parent Q --> -<li class="item"> - <a class="open" href="#"><i class="icon-sort-down pull-right"></i>Open</a> - <!-- Start Dropdown --> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <!-- SubQ class: only if top level Q has subQ --> - <li class="subQ"> - <i class="icon-caret-down"></i> - <!-- Edit Queue --> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <!-- End Edit Queue --> - <!-- Display Latest Ticket count --> - <span>(80)</span> - <a class="truncate">Open testing to see how long the top queue can be</a> - <ul> - <li> - <!-- Edit Queue --> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <!-- End Edit Queue --> - <span>(60)</span> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - </li> - <li> - <!-- Edit Queue --> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <!-- End Edit Queue --> - <span class="disabled">(0)</span> - <a href="#">Answered</a> - </li> - <!-- Dropdown Titles --> - <li> - <h4>Personal Queue</h4> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <span>(33)</span> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - <!-- Add Queue button sticky at the bottom --> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="mine" href="#"><i class="icon-sort-down pull-right"></i>My Tickets</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li> - <div class="editQ"><i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a href="#">My Tickets</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> - -<li class="item"> - <a href="#"><i class="icon-sort-down pull-right"></i>Search</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li> - <h4>Shared Searches</h4> - </li> - <li> - <div class="editQ"><i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-share"></i>Share</a> - </li> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate" href="#">item queue</a> - </li> - <li> - <h4>My Searches</h4> - </li> - <li> - <div class="editQ"><i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-share"></i>Share</a> - </li> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate" href="#">item queue</a> - </li> - - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class=" icon-plus-sign"></i></div> - <span>Add saved search</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="closed" href="#"><i class="icon-sort-down pull-right"></i>Closed</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li> - <div class="editQ"><i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a href="#">Closed</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="new" href="#">New Ticket</a> -</li> -<li class="item"> - <a class="closed" href="#"><i class="icon-sort-down pull-right"></i>Custom item 1</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li class="subQ"> - <i class="icon-caret-down"></i> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">Open</a> - <ul> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a href="#">Answered</a> - </li> - <li> - <h4>Personal Queue</h4> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="closed" href="#"><i class="icon-sort-down pull-right"></i>Custom item 2</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li class="subQ"> - <i class="icon-caret-down"></i> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">Open</a> - <ul> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a href="#">Answered</a> - </li> - <li> - <h4>Personal Queue</h4> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="closed" href="#"><i class="icon-sort-down pull-right"></i>Custom item 3</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li class="subQ"> - <i class="icon-caret-down"></i> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">Open</a> - <ul> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a href="#">Answered</a> - </li> - <li> - <h4>Personal Queue</h4> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="closed" href="#"><i class="icon-sort-down pull-right"></i>Custom item 4</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li class="subQ"> - <i class="icon-caret-down"></i> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">Open</a> - <ul> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a href="#">Answered</a> - </li> - <li> - <h4>Personal Queue</h4> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> -<li class="item"> - <a class="closed" href="#"><i class="icon-sort-down pull-right"></i>Custom item 5</a> - <div class="customQ-dropdown"> - <ul class="scroll-height"> - <li class="subQ"> - <i class="icon-caret-down"></i> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">Open</a> - <ul> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li class="positive"> - <a href="#"><i class="icon-fixed-width icon-plus-sign"></i>Add Queue</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <span>(80)</span> - <a href="#">Answered</a> - </li> - <li> - <h4>Personal Queue</h4> - </li> - <li> - <div class="editQ"> - <i class="icon-cog"></i> - <div class="manageQ"> - <ul> - <li> - <a href="#"><i class="icon-fixed-width icon-pencil"></i>Edit</a> - </li> - <li class="danger"> - <a href="#"><i class="icon-fixed-width icon-trash"></i>Delete</a> - </li> - </ul> - </div> - </div> - <a class="truncate">testing to see how long I can make this go</a> - </li> - </ul> - <div class="add-queue"> - <a class="flush-right" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);"> - <div class="add pull-right"><i class="green icon-plus-sign"></i></div> - <span>Add personal queue</span> - </a> - </div> - </div> -</li> +?> + </ul> +</nav> diff --git a/scp/css/scp.css b/scp/css/scp.css index c21757f146894962d60a930ac04fab1369bfb0c7..7e6dd76bb0eb47940964dc009ed85104adb37799 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -42,6 +42,7 @@ div#header a { .full-width { width: 100%; + box-sizing: border-box; } .headline { margin-bottom: 15px; @@ -171,7 +172,7 @@ a time { text-align:center; } -#nav { +#nav, #sub_nav { clear:both; margin:0; padding:0 20px; @@ -182,9 +183,7 @@ a time { white-space: nowrap; } - - -#nav .active { +#nav .active, #sub_nav > li { padding:0; list-style:none; display:inline; @@ -305,18 +304,25 @@ a time { Start Custom Queues Menu System /***********************************************************************/ -#customQ_nav { +#sub_nav { background:#f7f7f7; border-bottom:1px solid #bebebe; + padding: 2px 20px; +} +#customQ_nav { clear:both; margin:0; padding:0; line-height:26px; - border-left:1px solid #aaa; - border-right:1px solid #aaa; } +#sub_nav > ul { + margin-top: 0; +} +#sub_nav > li + li > a { + margin-left: 10px; +} #customQ_nav .jb-overflowmenu-menu-primary li.item { position:relative; @@ -327,6 +333,7 @@ a time { margin:0 2px; } +#sub_nav > li > a, #customQ_nav .jb-overflowmenu-menu-primary li.item > a { display:inline-block; padding:0 10px 0 21px; @@ -344,25 +351,31 @@ a time { border-right:1px solid #bebebe; } +#sub_nav > li > a:hover, #customQ_nav .jb-overflowmenu-menu-primary li.item:hover > a { color:#E65524; text-decoration: none; } +#sub_nav a.active, #sub_nav li.active, #customQ_nav .jb-overflowmenu-menu-primary li.item > a.active { font-weight:bold; } +#sub_nav li.child.active { + font-weight: 500; +} + #customQ_nav .jb-overflowmenu-menu-primary li.item a > i { margin-top:5px; } -#customQ_nav .open { background-image:url(../images/icons/open.gif) } -#customQ_nav .answered { background-image:url(../images/icons/answered.gif) } -#customQ_nav .mine { background-image:url(../images/icons/mine.gif) } -#customQ_nav .closed { background-image:url(../images/icons/closed.gif) } -#customQ_nav .search { background-image:url(../images/icons/search.gif) } -#customQ_nav .new { background-image:url(../images/icons/new.gif) } +#sub_nav .open { background-image:url(../images/icons/open.gif) } +#sub_nav .answered { background-image:url(../images/icons/answered.gif) } +#sub_nav .mine { background-image:url(../images/icons/mine.gif) } +#sub_nav .closed { background-image:url(../images/icons/closed.gif) } +#sub_nav .search { background-image:url(../images/icons/search.gif) } +#sub_nav .new { background-image:url(../images/icons/new.gif) } /**********************initiate Custom Queues Dropdown******************/ @@ -409,7 +422,6 @@ a time { .customQ-dropdown li > span { color:#E65524; font-weight:bold; - float:right; } .customQ-dropdown li > span.disabled { color:#ccc; @@ -425,7 +437,7 @@ a time { border: solid 1px #ddd; border: solid 1px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - padding:12px 0; + padding:3px 0; background:#fff; text-align: left; font-size: 0.95em; @@ -558,11 +570,10 @@ a time { .jb-overflowmenu-menu-primary .customQ-dropdown ul li > div.editQ { margin-top:2px; - float:right; } .customQ-dropdown ul li > div.editQ i { - color:rgba(255, 255, 255, 0); + color:rgba(255, 255, 255, 0.6); } .customQ-dropdown ul li:hover > div.editQ i { @@ -659,7 +670,6 @@ a time { padding: 0; margin: 2px 0 0 0; height:30px!IMPORTANT; - right:30px!IMPORTANT; } .jb-overflowmenu .jb-overflowmenu-menu-primary li.item { @@ -719,17 +729,17 @@ a time { } /*** Overflow Dropdown ***/ -#customQ_nav .jb-overflowmenu-menu-secondary li.item:before { +#sub_nav .jb-overflowmenu-menu-secondary li.item:before { font-family: "FontAwesome"; content:"\F0D9"; padding:5px; } -#customQ_nav .jb-overflowmenu-menu-secondary li.item a > i { +#sub_nav .jb-overflowmenu-menu-secondary li.item a > i { margin-top:5px; } -#customQ_nav .jb-overflowmenu-menu-secondary li.item { +#sub_nav .jb-overflowmenu-menu-secondary li.item { position:relative; padding:0px 10px; display: inline-block; @@ -739,7 +749,7 @@ a time { white-space:nowrap; } -#customQ_nav .jb-overflowmenu-menu-secondary li.item > a { +#sub_nav .jb-overflowmenu-menu-secondary li.item > a { display:inline-block; padding:5px 10px 5px 21px; background-position:0 50%; @@ -747,7 +757,7 @@ a time { color:#000; } -#customQ_nav .jb-overflowmenu-menu-secondary li.item:hover { +#sub_nav .jb-overflowmenu-menu-secondary li.item:hover { padding:0 10px; color:#E65524; background-color: #fff; @@ -756,12 +766,12 @@ a time { border-right:none; } -#customQ_nav .jb-overflowmenu-menu-secondary li.item:hover > a { +#sub_nav .jb-overflowmenu-menu-secondary li.item:hover > a { color:#E65524; text-decoration: none; } -#customQ_nav .jb-overflowmenu-menu-secondary li.item > a.active { +#sub_nav .jb-overflowmenu-menu-secondary li.item > a.active { font-weight:bold; } .jb-overflowmenu-menu-secondary div.customQ-dropdown { diff --git a/scp/js/scp.js b/scp/js/scp.js index dc48639fbeb288860df1e676379d57f2ce572040..0bb8a3dc16143c438729f2f7f4fccbba73551705 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -475,6 +475,22 @@ var scp_prep = function() { $('.attached.input input') .on('focus', function() { $(this).parent().addClass('focus'); }) .on('blur', function() { $(this).parent().removeClass('focus'); }) + + $('#customQ_nav').overflowmenu({ + guessHeight: false, + change: function( e, ui ) { + var handle = ui.container.find('.jb-overflowmenu-menu-secondary-handle'); + if ( ui.secondary.children().length > 0 ) { + // necessary? + ui.primary.css('right', ui.secondary.width()); + handle.css('display', 'inline-block') + } + else { + ui.primary.css('right', 0); + handle.css('display', 'none') + } + } + }); }; $(document).ready(scp_prep); @@ -1295,16 +1311,3 @@ $(function() { }); }); }); - -$( document ).ready(function(){ - var target = $('#customQ_nav').overflowmenu({ - change: function( e, ui ){ - var handle = ui.container.find('.jb-overflowmenu-menu-secondary-handle'); - if( ui.secondary.children().length ){ - handle.css('display', 'inline-block') - }else{ - handle.css('display', 'none') - } - } - }); -}) diff --git a/scp/tickets.php b/scp/tickets.php index 25e21dfd4e2a4854311393d4d35ccec99195688d..15b96085f13b49ab600e14135638d3af05623e5d 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -58,6 +58,11 @@ if (!$ticket) { $_GET['status'] = $_REQUEST['status'] = $queue_name; } +require_once INCLUDE_DIR . 'class.queue.php'; +if (isset($_REQUEST['queue'])) { + $queue = CustomQueue::lookup($_REQUEST['queue']); +} + // Configure form for file uploads $response_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', @@ -356,7 +361,7 @@ if ($redirect) { } /*... Quick stats ...*/ -$stats= $thisstaff->getTicketsStats(); +$stats = $thisstaff->getTicketsStats(); // Clear advanced search upon request if (isset($_GET['clear_filter'])) @@ -364,53 +369,26 @@ if (isset($_GET['clear_filter'])) //Navigation $nav->setTabActive('tickets'); -$open_name = _P('queue-name', - /* This is the name of the open ticket queue */ - 'Open'); -if($cfg->showAnsweredTickets()) { - $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']+$stats['answered']).')', - 'title'=>__('Open Tickets'), - 'href'=>'tickets.php?status=open', - 'iconclass'=>'Ticket'), - ((!$_REQUEST['status'] && !isset($_SESSION['advsearch'])) || $_REQUEST['status']=='open')); -} else { - - if ($stats) { - - $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']).')', - 'title'=>__('Open Tickets'), - 'href'=>'tickets.php?status=open', - 'iconclass'=>'Ticket'), - ((!$_REQUEST['status'] && !isset($_SESSION['advsearch'])) || $_REQUEST['status']=='open')); - } - - if($stats['answered']) { - $nav->addSubMenu(array('desc'=>__('Answered').' ('.number_format($stats['answered']).')', - 'title'=>__('Answered Tickets'), - 'href'=>'tickets.php?status=answered', - 'iconclass'=>'answeredTickets'), - ($_REQUEST['status']=='answered')); - } -} - -if($stats['assigned']) { - - $nav->addSubMenu(array('desc'=>__('My Tickets').' ('.number_format($stats['assigned']).')', - 'title'=>__('Assigned Tickets'), - 'href'=>'tickets.php?status=assigned', - 'iconclass'=>'assignedTickets'), - ($_REQUEST['status']=='assigned')); -} - -if($stats['overdue']) { - $nav->addSubMenu(array('desc'=>__('Overdue').' ('.number_format($stats['overdue']).')', - 'title'=>__('Stale Tickets'), - 'href'=>'tickets.php?status=overdue', - 'iconclass'=>'overdueTickets'), - ($_REQUEST['status']=='overdue')); - - if(!$sysnotice && $stats['overdue']>10) - $sysnotice=sprintf(__('%d overdue tickets!'),$stats['overdue']); +$nav->addSubNavInfo('jb-overflowmenu', 'customQ_nav'); + +// Fetch ticket queues organized by root and sub-queues +$queues = CustomQueue::objects() + ->filter(Q::any(array( + 'flags__hasbit' => CustomQueue::FLAG_PUBLIC, + 'staff_id' => $thisstaff->getId(), + ))) + ->all(); + +// Start with all the top-level (container) queues +foreach ($queues->findAll(array('parent_id' => 0)) +as $q) { + $nav->addSubMenu(function() use ($q, $queue) { + // 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 + && false !== strpos($queue->getPath(), "/{$q->getId()}/"); + include STAFFINC_DIR . 'templates/queue-navigation.tmpl.php'; + }); } if (isset($_SESSION['advsearch'])) { @@ -428,12 +406,6 @@ if (isset($_SESSION['advsearch'])) { (!$_REQUEST['status'] || $_REQUEST['status']=='search')); } -$nav->addSubMenu(array('desc' => __('Closed'), - 'title'=>__('Closed Tickets'), - 'href'=>'tickets.php?status=closed', - 'iconclass'=>'closedTickets'), - ($_REQUEST['status']=='closed')); - if ($thisstaff->hasPerm(TicketModel::PERM_CREATE, false)) { $nav->addSubMenu(array('desc'=>__('New Ticket'), 'title'=> __('Open a New Ticket'), @@ -472,11 +444,21 @@ if($ticket) { $inc = 'ticket-open.inc.php'; elseif($_REQUEST['a'] == 'export') { $ts = strftime('%Y%m%d'); - if (!($query=$_SESSION[':Q:tickets'])) - $errors['err'] = __('Query token not found'); - elseif (!Export::saveTickets($query, "tickets-$ts.csv", 'csv')) - $errors['err'] = __('Unable to dump query results.') - .' '.__('Internal error occurred'); + if (isset($queue) && $queue) { + // XXX: Check staff access? + $inc = 'templates/queue-tickets.tmpl.php'; + if (!($query = $queue->getBasicQuery())) + $errors['err'] = __('Query token not found'); + elseif (!Export::saveTickets($query, "tickets-$ts.csv", 'csv')) + $errors['err'] = __('Unable to dump query results.') + .' '.__('Internal error occurred'); + } + } + elseif (isset($queue) && $queue) { + // XXX: Check staff access? + $inc = 'templates/queue-tickets.tmpl.php'; + $tickets = $queue->getQuery(); + $count = count($tickets); } //Clear active submenu on search with no status