diff --git a/bootstrap.php b/bootstrap.php
index 4b64227a839c1e9cd864db6cc74a1930e9906f98..056c5f01dcd4098d7f8649be2211249a7b9f20f8 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -293,6 +293,10 @@ class Bootstrap {
         }
         if (extension_loaded('iconv'))
             iconv_set_encoding('internal_encoding', 'UTF-8');
+
+        function mb_str_wc($str) {
+            return count(preg_split('~[^\p{L}\p{N}\'].+~u', trim($str)));
+        }
     }
 
     function croak($message) {
diff --git a/include/ajax.search.php b/include/ajax.search.php
index dbc0a291b86d3c7bebb6f1c67333735b23739b9e..fabe2d810fdd8e694d566b418b656c028e94a916 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -395,7 +395,7 @@ class SearchAjaxAPI extends AjaxController {
         if ($ids && is_array($ids))
             $criteria = array('id__in' => $ids);
 
-        $counts = SavedQueue::ticketsCount($thisstaff, $criteria, 'q');
+        $counts = SavedQueue::counts($thisstaff, $criteria);
         Http::response(200, false, 'application/json');
         return $this->encode($counts);
     }
diff --git a/include/class.orm.php b/include/class.orm.php
index 473be838ceba796105db5770b71d1f620f895f5e..35886ad1f619298310582337a51679a931ca50ad 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -333,6 +333,11 @@ class VerySimpleModel {
         return static::getMeta()->newInstance($row);
     }
 
+    function __wakeup() {
+        // If a model is stashed in a session, refresh the model from the database
+        $this->refetch();
+    }
+
     function get($field, $default=false) {
         if (array_key_exists($field, $this->ht))
             return $this->ht[$field];
@@ -1142,6 +1147,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
     const OPT_NOSORT    = 'nosort';
     const OPT_NOCACHE   = 'nocache';
     const OPT_MYSQL_FOUND_ROWS = 'found_rows';
+    const OPT_INDEX_HINT = 'indexhint';
 
     const ITER_MODELS   = 1;
     const ITER_HASH     = 2;
@@ -1281,6 +1287,10 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return $this;
     }
 
+    function addExtraJoin(array $join) {
+       return $this->extra(array('joins' => array($join)));
+    }
+
     function distinct() {
         foreach (func_get_args() as $D)
             $this->distinct[] = $D;
@@ -1477,6 +1487,18 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return isset($this->options[$option]);
     }
 
+    function getOption($option) {
+        return @$this->options[$option] ?: false;
+    }
+
+    function setOption($option, $value) {
+        $this->options[$option] = $value;
+    }
+
+    function clearOption($option) {
+        unset($this->options[$option]);
+    }
+
     function countSelectFields() {
         $count = count($this->values) + count($this->annotations);
         if (isset($this->extra['select']))
@@ -2611,13 +2633,22 @@ class SqlCompiler {
             foreach ($queryset->extra['tables'] as $S) {
                 $join = ' JOIN ';
                 // Left joins require an ON () clause
-                if ($lastparen = strrpos($S, '(')) {
-                    if (preg_match('/\bon\b/i', substr($S, $lastparen - 4, 4)))
-                        $join = ' LEFT' . $join;
-                }
+                // TODO: Have a way to indicate a LEFT JOIN
                 $sql .= $join.$S;
             }
         }
+
+        // Add extra joins from QuerySet
+        if (isset($queryset->extra['joins'])) {
+            foreach ($queryset->extra['joins'] as $J) {
+                list($base, $constraints, $alias) = $J;
+                $join = $constraints ? ' LEFT JOIN ' : ' JOIN ';
+                $sql .= "{$join}{$base} $alias";
+                if ($constraints instanceof Q)
+                    $sql .= ' ON ('.$this->compileQ($constraints, $queryset->model).')';
+            }
+        }
+
         return $sql;
     }
 
@@ -2965,6 +2996,7 @@ class MySqlCompiler extends SqlCompiler {
         $meta = $model::getMeta();
         $table = $this->quote($meta['table']).' '.$rootAlias;
         // Handle related tables
+        $need_group_by = false;
         if ($queryset->related) {
             $count = 0;
             $fieldMap = $theseFields = array();
@@ -3010,13 +3042,16 @@ class MySqlCompiler extends SqlCompiler {
         }
         // Support retrieving only a list of values rather than a model
         elseif ($queryset->values) {
+            $additional_group_by = array();
             foreach ($queryset->values as $alias=>$v) {
                 list($f) = $this->getField($v, $model);
                 $unaliased = $f;
                 if ($f instanceof SqlFunction) {
                     $fields[$f->toSql($this, $model, $alias)] = true;
                     if ($f instanceof SqlAggregate) {
-                        // Don't group_by aggregate expressions
+                        // Don't group_by aggregate expressions, but if there is an
+                        // aggergate expression, then we need a GROUP BY clause.
+                        $need_group_by = true;
                         continue;
                     }
                 }
@@ -3028,8 +3063,10 @@ class MySqlCompiler extends SqlCompiler {
                 // If there are annotations, add in these fields to the
                 // GROUP BY clause
                 if ($queryset->annotations && !$queryset->distinct)
-                    $group_by[] = $unaliased;
+                    $additional_group_by[] = $unaliased;
             }
+            if ($need_group_by && $additional_group_by)
+                $group_by = array_merge($group_by, $additional_group_by);
         }
         // Simple selection from one table
         elseif (!$queryset->aggregated) {
@@ -3050,6 +3087,8 @@ class MySqlCompiler extends SqlCompiler {
             foreach ($queryset->annotations as $alias=>$A) {
                 // The root model will receive the annotations, add in the
                 // annotation after the root model's fields
+                if ($A instanceof SqlAggregate)
+                    $need_group_by = true;
                 $T = $A->toSql($this, $model, $alias);
                 if ($fieldMap) {
                     array_splice($fields, count($fieldMap[0][0]), 0, array($T));
@@ -3061,7 +3100,7 @@ class MySqlCompiler extends SqlCompiler {
                 }
             }
             // If no group by has been set yet, use the root model pk
-            if (!$group_by && !$queryset->aggregated && !$queryset->distinct) {
+            if (!$group_by && !$queryset->aggregated && !$queryset->distinct && $need_group_by) {
                 foreach ($meta['pk'] as $pk)
                     $group_by[] = $rootAlias .'.'. $pk;
             }
@@ -3083,12 +3122,15 @@ class MySqlCompiler extends SqlCompiler {
         $group_by = $group_by ? ' GROUP BY '.implode(', ', $group_by) : '';
 
         $joins = $this->getJoins($queryset);
+        if ($hint = $queryset->getOption(QuerySet::OPT_INDEX_HINT)) {
+            $hint = " USE INDEX ({$hint})";
+        }
 
         $sql = 'SELECT ';
         if ($queryset->hasOption(QuerySet::OPT_MYSQL_FOUND_ROWS))
             $sql .= 'SQL_CALC_FOUND_ROWS ';
         $sql .= implode(', ', $fields).' FROM '
-            .$table.$joins.$where.$group_by.$having.$sort;
+            .$table.$hint.$joins.$where.$group_by.$having.$sort;
         // UNIONS
         if ($queryset->chain) {
             // If the main query is sorted, it will need parentheses
diff --git a/include/class.pagenate.php b/include/class.pagenate.php
index b20ad52a5da542faa088d21d31045a338761c313..70d1ca1262d3111ed962bc54a5233d335fdd4f2a 100644
--- a/include/class.pagenate.php
+++ b/include/class.pagenate.php
@@ -22,6 +22,7 @@ class PageNate {
     var $total;
     var $page;
     var $pages;
+    var $approx=false;
 
 
     function __construct($total,$page,$limit=20,$url='') {
@@ -32,7 +33,7 @@ class PageNate {
         $this->setTotal($total);
     }
 
-    function setTotal($total) {
+    function setTotal($total, $approx=false) {
         $this->total = intval($total);
         $this->pages = ceil( $this->total / $this->limit );
 
@@ -42,6 +43,7 @@ class PageNate {
         if (($this->limit-1)*$this->start > $this->total) {
             $this->start -= $this->start % $this->limit;
         }
+        $this->approx = $approx;
     }
 
     function setURL($url='',$vars='') {
@@ -97,8 +99,12 @@ class PageNate {
         }
         $html=__('Showing')." ";
         if ($this->total > 0) {
-            $html .= sprintf(__('%1$d - %2$d of %3$d' /* Used in pagination output */),
-               $start, $end, $this->total);
+            if ($this->approx)
+                $html .= sprintf(__('%1$d - %2$d of about %3$d' /* Used in pagination output */),
+                   $start, $end, $this->total);
+            else
+                $html .= sprintf(__('%1$d - %2$d of %3$d' /* Used in pagination output */),
+                   $start, $end, $this->total);
         }else{
             $html .= " 0 ";
         }
diff --git a/include/class.queue.php b/include/class.queue.php
index 48f82c81f44e47f7ee8dac4a8bac2eb75c84679b..e3212c502929ddbaa93785eca9f72a9f46007617 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -174,10 +174,7 @@ class CustomQueue extends VerySimpleModel {
      */
     function getForm($source=null, $searchable=null) {
         $fields = array();
-        $validator = false;
         if (!isset($searchable)) {
-            $searchable = $this->getCurrentSearchFields($source);
-            $validator = true;
             $fields = array(
                 ':keywords' => new TextboxField(array(
                     'id' => 3001,
@@ -188,11 +185,17 @@ class CustomQueue extends VerySimpleModel {
                         'classes' => 'full-width headline',
                         'placeholder' => __('Keywords — Optional'),
                     ),
+                    'validators' => function($self, $v) {
+                        if (mb_str_wc($v) > 3)
+                            $self->addError(__('Search term cannot have more than 3 keywords'));
+                    },
                 )),
             );
+
+            $searchable = $this->getCurrentSearchFields($source);
         }
 
-        foreach ($searchable as $path=>$field)
+        foreach ($searchable ?: array() as $path => $field)
             $fields = array_merge($fields, static::getSearchField($field, $path));
 
         $form = new AdvancedSearchForm($fields, $source);
@@ -998,7 +1001,8 @@ class CustomQueue extends VerySimpleModel {
     }
 
     function inheritCriteria() {
-        return $this->flags & self::FLAG_INHERIT_CRITERIA;
+        return $this->flags & self::FLAG_INHERIT_CRITERIA &&
+            $this->parent_id;
     }
 
     function inheritColumns() {
@@ -1051,8 +1055,7 @@ class CustomQueue extends VerySimpleModel {
     }
 
     function isPrivate() {
-        return !$this->isAQueue() && !$this->isPublic() &&
-            $this->staff_id;
+        return !$this->isAQueue() && $this->staff_id;
     }
 
     function isPublic() {
@@ -1081,6 +1084,57 @@ class CustomQueue extends VerySimpleModel {
         $this->clearFlag(self::FLAG_DISABLED);
     }
 
+    function getRoughCount() {
+        if (($count = $this->getRoughCountAPC()) !== false)
+            return $count;
+
+        $query = Ticket::objects();
+        $Q = $this->getBasicQuery();
+        $expr = SqlCase::N()->when(new SqlExpr(new Q($Q->constraints)),
+            new SqlField('ticket_id'));
+        $query = $query->aggregate(array(
+            "ticket_count" => SqlAggregate::COUNT($expr)
+        ));
+
+        $row = $query->values()->one();
+        return $row['ticket_count'];
+    }
+
+    function getRoughCountAPC() {
+        if (!function_exists('apcu_store'))
+            return false;
+
+        $key = "rough.counts.".SECRET_SALT;
+        $cached = false;
+        $counts = apcu_fetch($key, $cached);
+        if ($cached === true && isset($counts["q{$this->id}"]))
+            return $counts["q{$this->id}"];
+
+        // Fetch rough counts of all queues. That is, fetch a total of the
+        // counts based on the queue criteria alone. Do no consider agent
+        // access. This should be fast and "rought"
+        $queues = static::objects()
+            ->filter(['flags__hasbit' => CustomQueue::FLAG_PUBLIC])
+            ->exclude(['flags__hasbit' => CustomQueue::FLAG_DISABLED]);
+
+        $query = Ticket::objects();
+        $prefix = "";
+
+        foreach ($queues as $queue) {
+            $Q = $queue->getBasicQuery();
+            $expr = SqlCase::N()->when(new SqlExpr(new Q($Q->constraints)),
+                new SqlField('ticket_id'));
+            $query = $query->aggregate(array(
+                "q{$queue->id}" => SqlAggregate::COUNT($expr)
+            ));
+        }
+
+        $counts = $query->values()->one();
+
+        apcu_store($key, $counts, 900);
+        return @$counts["q{$this->id}"];
+    }
+
     function updateExports($fields, $save=true) {
 
         if (!$fields)
@@ -1176,8 +1230,7 @@ class CustomQueue extends VerySimpleModel {
 
         // Set basic queue information
         $this->path = $this->buildPath();
-        $this->setFlag(self::FLAG_INHERIT_CRITERIA,
-            $this->parent_id > 0 && isset($vars['inherit']));
+        $this->setFlag(self::FLAG_INHERIT_CRITERIA, $this->parent_id);
         $this->setFlag(self::FLAG_INHERIT_COLUMNS,
             isset($vars['inherit-columns']));
         $this->setFlag(self::FLAG_INHERIT_EXPORT,
@@ -1298,6 +1351,8 @@ class CustomQueue extends VerySimpleModel {
 
         if ($this->dirty)
             $this->updated = SqlFunction::NOW();
+
+        $clearCounts = ($this->dirty || $this->__new__);
         if (!($rv = parent::save($refetch || $this->dirty)))
             return $rv;
 
@@ -1316,6 +1371,11 @@ class CustomQueue extends VerySimpleModel {
             };
             $move_children($this);
         }
+
+        // Refetch the queue counts
+        if ($clearCounts)
+            SavedQueue::clearCounts();
+
         return $this->columns->saveAll()
             && $this->exports->saveAll()
             && $this->sorts->saveAll();
@@ -1336,23 +1396,35 @@ class CustomQueue extends VerySimpleModel {
      *      visible queues.
      * $pid - <int> parent_id of root queue. Default is zero (top-level)
      */
-    static function getHierarchicalQueues(Staff $staff, $pid=0) {
-        $all = static::objects()
+    static function getHierarchicalQueues(Staff $staff, $pid=0,
+            $primary=true) {
+        $query = static::objects()
+            ->annotate(array('_sort' =>  SqlCase::N()
+                        ->when(array('sort' => 0), 999)
+                        ->otherwise(new SqlField('sort'))))
             ->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();
-
+            ->order_by('parent_id', '_sort', 'title');
+        $all = $query->asArray();
         // Find all the queues with a given parent
-        $for_parent = function($pid) use ($all, &$for_parent) {
+        $for_parent = function($pid) use ($primary, $all, &$for_parent) {
             $results = [];
             foreach (new \ArrayIterator($all) as $q) {
-                if ($q->parent_id == $pid)
-                    $results[] = [ $q, $for_parent($q->getId()) ];
+                if ($q->parent_id != $pid)
+                    continue;
+
+                if ($pid == 0 && (
+                            ($primary &&  !$q->isAQueue())
+                            || (!$primary && $q->isAQueue())))
+                    continue;
+
+                $results[] = [ $q, $for_parent($q->getId()) ];
             }
+
             return $results;
         };
 
@@ -1392,8 +1464,10 @@ class CustomQueue extends VerySimpleModel {
 
         $queue = new static($vars);
         $queue->created = SqlFunction::NOW();
-        if (!isset($vars['flags']))
+        if (!isset($vars['flags'])) {
+            $queue->setFlag(self::FLAG_PUBLIC);
             $queue->setFlag(self::FLAG_QUEUE);
+        }
 
         return $queue;
     }
@@ -2645,6 +2719,7 @@ extends VerySimpleModel {
     );
 
     var $_columns;
+    var $_extra;
 
     function getRoot($hint=false) {
         switch ($hint ?: $this->root) {
@@ -2662,6 +2737,12 @@ extends VerySimpleModel {
         return $this->id;
     }
 
+    function getExtra() {
+        if (isset($this->extra) && !isset($this->_extra))
+            $this->_extra = JsonDataParser::decode($this->extra);
+        return $this->_extra;
+    }
+
     function applySort(QuerySet $query, $reverse=false, $root=false) {
         $fields = CustomQueue::getSearchableFields($this->getRoot($root));
         foreach ($this->getColumnPaths() as $path=>$descending) {
@@ -2672,6 +2753,10 @@ extends VerySimpleModel {
                     CustomQueue::getOrmPath($path, $query));
             }
         }
+        // Add index hint if defined
+        if (($extra = $this->getExtra()) && isset($extra['index'])) {
+            $query->setOption(QuerySet::OPT_INDEX_HINT, $extra['index']);
+        }
         return $query;
     }
 
@@ -2705,6 +2790,11 @@ extends VerySimpleModel {
             array('id' => $this->id));
     }
 
+    function getAdvancedConfigForm($source=false) {
+        return new QueueSortAdvancedConfigForm($source ?: $this->getExtra(),
+            array('id' => $this->id));
+    }
+
     static function forQueue(CustomQueue $queue) {
         return static::objects()->filter([
             'root' => $queue->root ?: 'T',
@@ -2740,6 +2830,11 @@ extends VerySimpleModel {
             $this->columns = JsonDataEncoder::encode($columns);
         }
 
+        if ($this->getExtra() !== null) {
+            $extra = $this->getAdvancedConfigForm($vars)->getClean();
+            $this->extra = JsonDataEncoder::encode($extra);
+        }
+
         if (count($errors))
             return false;
 
@@ -2999,3 +3094,24 @@ extends AbstractForm {
         );
     }
 }
+
+class QueueSortAdvancedConfigForm
+extends AbstractForm {
+    function getInstructions() {
+        return __('If unsure, leave these options blank and unset');
+    }
+
+    function buildFields() {
+        return array(
+            'index' => new TextboxField(array(
+                'label' => __('Database Index'),
+                'hint' => __('Use this index when sorting on this column'),
+                'required' => false,
+                'layout' => new GridFluidCell(12),
+                'configuration' => array(
+                    'placeholder' => __('Automatic'),
+                ),
+            )),
+        );
+    }
+}
diff --git a/include/class.search.php b/include/class.search.php
index c32916504b3d4200ccdde403e00ebe4656c4a9cb..3ffae21424c16361e8f21370a8cc4dcf829f35bb 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -384,9 +384,11 @@ class MysqlSearchBackend extends SearchBackend {
             $criteria->extra(array(
                 'tables' => array(
                     str_replace(array(':', '{}'), array(TABLE_PREFIX, $search),
-                    "(SELECT COALESCE(Z3.`object_id`, Z5.`ticket_id`, Z8.`ticket_id`) as `ticket_id`, SUM({}) AS `relevance` FROM `:_search` Z1 LEFT JOIN `:thread_entry` Z2 ON (Z1.`object_type` = 'H' AND Z1.`object_id` = Z2.`id`) LEFT JOIN `:thread` Z3 ON (Z2.`thread_id` = Z3.`id` AND Z3.`object_type` = 'T') LEFT JOIN `:ticket` Z5 ON (Z1.`object_type` = 'T' AND Z1.`object_id` = Z5.`ticket_id`) LEFT JOIN `:user` Z6 ON (Z6.`id` = Z1.`object_id` and Z1.`object_type` = 'U') LEFT JOIN `:organization` Z7 ON (Z7.`id` = Z1.`object_id` AND Z7.`id` = Z6.`org_id` AND Z1.`object_type` = 'O') LEFT JOIN :ticket Z8 ON (Z8.`user_id` = Z6.`id`) WHERE {} GROUP BY `ticket_id`) Z1"),
-                )
+                    "(SELECT COALESCE(Z3.`object_id`, Z5.`ticket_id`, Z8.`ticket_id`) as `ticket_id`, Z1.relevance FROM (SELECT Z1.`object_id`, Z1.`object_type`, {} AS `relevance` FROM `:_search` Z1 WHERE {} ORDER BY relevance DESC) Z1 LEFT JOIN `:thread_entry` Z2 ON (Z1.`object_type` = 'H' AND Z1.`object_id` = Z2.`id`) LEFT JOIN `:thread` Z3 ON (Z2.`thread_id` = Z3.`id` AND Z3.`object_type` = 'T') LEFT JOIN `:ticket` Z5 ON (Z1.`object_type` = 'T' AND Z1.`object_id` = Z5.`ticket_id`) LEFT JOIN `:user` Z6 ON (Z6.`id` = Z1.`object_id` and Z1.`object_type` = 'U') LEFT JOIN `:organization` Z7 ON (Z7.`id` = Z1.`object_id` AND Z7.`id` = Z6.`org_id` AND Z1.`object_type` = 'O') LEFT JOIN `:ticket` Z8 ON (Z8.`user_id` = Z6.`id`)) Z1"),
+                ),
             ));
+            $criteria->extra(array('order_by' => array(array(new SqlCode('Z1.relevance', 'DESC')))));
+
             $criteria->filter(array('ticket_id'=>new SqlCode('Z1.`ticket_id`')));
             break;
 
@@ -481,7 +483,7 @@ class MysqlSearchBackend extends SearchBackend {
             LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`id` = A2.`object_id` AND A2.`object_type`='H')
             WHERE A2.`object_id` IS NULL AND (A1.poster <> 'SYSTEM')
             AND (LENGTH(A1.`title`) + LENGTH(A1.`body`) > 0)
-            ORDER BY A1.`id` DESC LIMIT 500";
+            LIMIT 500";
         if (!($res = db_query_unbuffered($sql, $auto_create)))
             return false;
 
@@ -501,7 +503,7 @@ class MysqlSearchBackend extends SearchBackend {
         $sql = "SELECT A1.`ticket_id` FROM `".TICKET_TABLE."` A1
             LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`ticket_id` = A2.`object_id` AND A2.`object_type`='T')
             WHERE A2.`object_id` IS NULL
-            ORDER BY A1.`ticket_id` DESC LIMIT 300";
+            LIMIT 300";
         if (!($res = db_query_unbuffered($sql, $auto_create)))
             return false;
 
@@ -524,8 +526,7 @@ class MysqlSearchBackend extends SearchBackend {
 
         $sql = "SELECT A1.`id` FROM `".USER_TABLE."` A1
             LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`id` = A2.`object_id` AND A2.`object_type`='U')
-            WHERE A2.`object_id` IS NULL
-            ORDER BY A1.`id` DESC";
+            WHERE A2.`object_id` IS NULL";
         if (!($res = db_query_unbuffered($sql, $auto_create)))
             return false;
 
@@ -550,8 +551,7 @@ class MysqlSearchBackend extends SearchBackend {
 
         $sql = "SELECT A1.`id` FROM `".ORGANIZATION_TABLE."` A1
             LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`id` = A2.`object_id` AND A2.`object_type`='O')
-            WHERE A2.`object_id` IS NULL
-            ORDER BY A1.`id` DESC";
+            WHERE A2.`object_id` IS NULL";
         if (!($res = db_query_unbuffered($sql, $auto_create)))
             return false;
 
@@ -575,8 +575,7 @@ class MysqlSearchBackend extends SearchBackend {
         require_once INCLUDE_DIR . 'class.faq.php';
         $sql = "SELECT A1.`faq_id` FROM `".FAQ_TABLE."` A1
             LEFT JOIN `".TABLE_PREFIX."_search` A2 ON (A1.`faq_id` = A2.`object_id` AND A2.`object_type`='K')
-            WHERE A2.`object_id` IS NULL
-            ORDER BY A1.`faq_id` DESC";
+            WHERE A2.`object_id` IS NULL";
         if (!($res = db_query_unbuffered($sql, $auto_create)))
             return false;
 
@@ -701,18 +700,19 @@ class SavedQueue extends CustomQueue {
         return $this->_columns;
     }
 
+    static function getHierarchicalQueues(Staff $staff) {
+        return CustomQueue::getHierarchicalQueues($staff, 0, false);
+    }
+
     /**
      * Fetch an AdvancedSearchForm instance for use in displaying or
      * configuring this search in the user interface.
      *
      */
     function getForm($source=null, $searchable=array()) {
-        global $thisstaff;
-
-        if (!$this->isAQueue())
-            $searchable =  $this->getCurrentSearchFields($source,
-                     parent::getCriteria());
-        else // Only allow supplemental matches.
+        $searchable = null;
+        if ($this->isAQueue())
+            // Only allow supplemental matches.
             $searchable = array_intersect_key($this->getCurrentSearchFields($source),
                     $this->getSupplementalMatches());
 
@@ -847,19 +847,49 @@ class SavedQueue extends CustomQueue {
         return (!$errors);
     }
 
-    static function ticketsCount($agent, $criteria=array(),
-            $prefix='') {
+    function getCount($agent, $cached=true) {
+        $criteria = $cached ? array() : array('id' => $this->getId());
+        $counts = self::counts($agent, $criteria, $cached);
+        return $counts["q{$this->getId()}"] ?: 0;
+    }
+
+    // Get ticket counts for queues the agent has acces to.
+    static function counts($agent, $criteria=array(), $cached=true) {
 
         if (!$agent instanceof Staff)
             return array();
 
-        $queues = SavedQueue::objects()
+        // Cache TLS in seconds
+        $ttl = 3600;
+        // Cache key based on agent and salt of the installation
+        $key = "counts.queues.{$agent->getId()}.".SECRET_SALT;
+        if ($criteria && is_array($criteria)) // Consider additional criteria.
+            $key .= '.'.md5(serialize($criteria));
+
+        // only consider cache if requesed
+        if ($cached) {
+            if (function_exists('apcu_store')) {
+                $found = false;
+                $counts = apcu_fetch($key, $found);
+                if ($found === true)
+                    return $counts;
+            } elseif (isset($_SESSION[$key])
+                    && isset($_SESSION[$key]['qcount'])
+                    && (time() - $_SESSION[$key]['time']) < $ttl) {
+                return $_SESSION[$key]['qcount'];
+            } else {
+                // Auto clear missed session cache (if any)
+                unset($_SESSION[$key]);
+            }
+        }
+
+        $queues = static::objects()
             ->filter(Q::any(array(
-                'flags__hasbit' => CustomQueue::FLAG_PUBLIC,
+                'flags__hasbit' => CustomQueue::FLAG_QUEUE,
                 'staff_id' => $agent->getId(),
             )));
 
-        if ($criteria)
+        if ($criteria && is_array($criteria))
             $queues->filter($criteria);
 
         $query = Ticket::objects();
@@ -870,11 +900,43 @@ class SavedQueue extends CustomQueue {
             $Q = $queue->getBasicQuery();
             $expr = SqlCase::N()->when(new SqlExpr(new Q($Q->constraints)), new SqlField('ticket_id'));
             $query->aggregate(array(
-                "$prefix{$queue->id}" => SqlAggregate::COUNT($expr, true)
+                "q{$queue->id}" => SqlAggregate::COUNT($expr, true)
             ));
+
+            // Add extra tables joins  (if any)
+            if ($Q->extra && isset($Q->extra['tables'])) {
+                $contraints = array();
+                if ($Q->constraints)
+                     $constraints = new Q($Q->constraints);
+                foreach ($Q->extra['tables'] as $T)
+                    $query->addExtraJoin(array($T, $constraints, ''));
+            }
         }
 
-        return $query->values()->one();
+        $counts = $query->values()->one();
+        // Always cache the results
+        if (function_exists('apcu_store')) {
+            apcu_store($key, $counts, $ttl);
+        } else {
+            // Poor man's cache
+            $_SESSION[$key]['qcount'] = $counts;
+            $_SESSION[$key]['time'] = time();
+        }
+
+        return $counts;
+    }
+
+    static function clearCounts() {
+        if (function_exists('apcu_store')) {
+            if (class_exists('APCUIterator')) {
+                $regex = '/^counts.queues.\d+.' . preg_quote(SECRET_SALT, '/') . '$/';
+                foreach (new APCUIterator($regex, APC_ITER_KEY) as $key) {
+                    apcu_delete($key);
+                }
+            }
+            // Also clear rough counts
+            apcu_delete("rough.counts.".SECRET_SALT);
+        }
     }
 
     static function lookup($criteria) {
@@ -903,6 +965,10 @@ class SavedSearch extends SavedQueue {
     function isSaved() {
         return (!$this->__new__);
     }
+
+    function getCount($agent, $cached=true) {
+        return 500;
+    }
 }
 
 class AdhocSearch
diff --git a/include/class.thread.php b/include/class.thread.php
index 481a34835aed3ce179a8c83bf8e07b3ed3b84596..3d367a902f496b69e54d4bc5fc3fd710a4ef3e0a 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -1088,8 +1088,6 @@ implements TemplateVariable {
 
             if ($info instanceof AttachmentFile)
                 $fileId = $info->getId();
-            elseif (is_array($info) && isset($info['id']))
-                $fileId = $info['id'];
             elseif ($AF = AttachmentFile::create($info))
                 $fileId = $AF->getId();
             elseif ($add_error) {
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 0347004deffe2efb3a92ff50ea94d8434cf1a5d6..37c80c73c669fa1b9f59dbbebfed6eb563b3fc3d 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -3200,6 +3200,9 @@ implements RestrictedAccess, Threadable, Searchable {
     function save($refetch=false) {
         if ($this->dirty) {
             $this->updated = SqlFunction::NOW();
+            if (isset($this->dirty['status_id']))
+                // Refetch the queue counts
+                SavedQueue::clearCounts();
         }
         return parent::save($this->dirty || $refetch);
     }
@@ -4210,7 +4213,8 @@ implements RestrictedAccess, Threadable, Searchable {
          Punt for now
          */
 
-        $sql='SELECT ticket_id FROM '.TICKET_TABLE.' T1 '
+        $sql='SELECT ticket_id FROM '.TICKET_TABLE.' T1'
+            .' USE INDEX (status_id)'
             .' INNER JOIN '.TICKET_STATUS_TABLE.' status
                 ON (status.id=T1.status_id AND status.state="open") '
             .' LEFT JOIN '.SLA_TABLE.' T2 ON (T1.sla_id=T2.id AND T2.flags & 1 = 1) '
diff --git a/include/i18n/en_US/queue.yaml b/include/i18n/en_US/queue.yaml
index ab2b1a4bbb3453e14c7843b81a75d65751b94d9e..34a35c28707db92ce80c80af9ed8728b1cd1ce7d 100644
--- a/include/i18n/en_US/queue.yaml
+++ b/include/i18n/en_US/queue.yaml
@@ -29,6 +29,7 @@
 ---
 - id: 1
   title: Open
+  parent_id: 0
   flags: 0x03
   sort: 1
   root: T
@@ -69,14 +70,57 @@
     - sort_id: 2
     - sort_id: 3
     - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
 
 - id: 2
+  title: Open
+  parent_id: 1
+  flags: 0x2b
+  root: T
+  sort: 1
+  sort_id: 4
+  config: '{"criteria":[["isanswered","nset",null]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 10
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Last Updated
+    - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
+    - column_id: 5
+      bits: 1
+      sort: 5
+      width: 85
+      heading: Priority
+    - column_id: 8
+      bits: 1
+      sort: 6
+      width: 160
+      heading: Assigned To
+
+- id: 3
   title: Answered
   parent_id: 1
-  flags: 0x03
+  flags: 0x2b
   root: T
   sort: 2
-  config: '[["isanswered","set",null]]'
+  sort_id: 4
+  config: '{"criteria":[["isanswered","set",null]],"conditions":[]}'
   columns:
     - column_id: 1
       bits: 1
@@ -87,7 +131,7 @@
       bits: 1
       sort: 2
       width: 150
-      heading: Last Update
+      heading: Last Updated
     - column_id: 3
       bits: 1
       sort: 3
@@ -108,13 +152,53 @@
       sort: 6
       width: 160
       heading: Assigned To
-  sorts:
-    - sort_id: 1
-    - sort_id: 2
-    - sort_id: 3
-    - sort_id: 4
 
-- id: 3
+- id: 4
+  title: Overdue
+  parent_id: 1
+  flags: 0x2b
+  root: T
+  sort: 3
+  sort_id: 4
+  config: '{"criteria":[["isoverdue","set",null]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 9
+      bits: 1
+      sort: 1
+      sort: 9
+      width: 150
+      heading: Due Date
+    - column_id: 3
+      bits: 1
+      sort: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 1
+      sort: 4
+      width: 185
+      heading: From
+    - column_id: 5
+      bits: 1
+      sort: 1
+      sort: 5
+      width: 85
+      heading: Priority
+    - column_id: 8
+      bits: 1
+      sort: 1
+      sort: 6
+      width: 160
+      heading: Assigned To
+
+- id: 5
   title: My Tickets
   parent_id: 0
   flags: 0x03
@@ -157,25 +241,27 @@
     - sort_id: 2
     - sort_id: 3
     - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
 
-- id: 4
-  title: Closed
-  flags: 0x03
-  sort: 4
+- id: 6
+  title: Assigned to Me
+  parent_id: 5
+  flags: 0x2b
   root: T
-  sort_id: 5
-  config: '[["status__state","includes",{"closed":"Closed"}]]'
+  sort: 1
+  config: '{"criteria":[["assignee","includes",{"M":"Me"}]],"conditions":[]}'
   columns:
     - column_id: 1
       bits: 1
       sort: 1
       width: 100
       heading: Ticket
-    - column_id: 7
+    - column_id: 10
       bits: 1
       sort: 2
       width: 150
-      heading: Date Closed
+      heading: Last Update
     - column_id: 3
       bits: 1
       sort: 3
@@ -188,28 +274,29 @@
       heading: From
     - column_id: 5
       bits: 1
-      sort: 1
       sort: 5
       width: 85
       heading: Priority
-    - column_id: 8
+    - column_id: 11
       bits: 1
-      sort: 1
       sort: 6
       width: 160
-      heading: Closed By
+      heading: Department
   sorts:
-    - sort_id: 5
     - sort_id: 1
     - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
 
-- id: 5
-  title: Assigned
-  parent_id: 1
-  flags: 0x03
+- id: 7
+  title: Assigned to Teams
+  parent_id: 5
+  flags: 0x2b
   root: T
-  sort: 3
-  config: '[["assignee","assigned",null]]'
+  sort: 2
+  config: '{"criteria":[["assignee","!includes",{"M":"Me"}]],"conditions":[]}'
   columns:
     - column_id: 1
       bits: 1
@@ -236,53 +323,323 @@
       sort: 5
       width: 85
       heading: Priority
+    - column_id: 14
+      bits: 1
+      sort: 6
+      width: 160
+      heading: Team
+  sorts:
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
+
+- id: 8
+  parent_id: 0
+  title: Closed
+  flags: 0x03
+  sort: 4
+  root: T
+  sort_id: 5
+  config: '{"criteria":[["status__state","includes",{"closed":"Closed"}]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 7
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Date Closed
+    - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
     - column_id: 8
       bits: 1
+      sort: 1
       sort: 6
       width: 160
-      heading: Assigned To
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
 
-- id: 6
-  title: Overdue
-  parent_id: 1
+- id: 9
+  parent_id: 8
+  title: Today
   flags: 0x2b
+  sort: 1
   root: T
-  sort: 4
-  sort_id: 4
-  config: '[["isoverdue","set",null]]'
+  sort_id: 5
+  config: '{"criteria":[["closed","period","td"]],"conditions":[]}'
   columns:
     - column_id: 1
       bits: 1
       sort: 1
       width: 100
       heading: Ticket
-    - column_id: 9
+    - column_id: 7
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Date Closed
+    - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
+    - column_id: 8
       bits: 1
       sort: 1
-      sort: 9
+      sort: 6
+      width: 160
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
+
+- id: 10
+  parent_id: 8
+  title: Yesterday
+  flags: 0x2b
+  sort: 2
+  root: T
+  sort_id: 5
+  config: '{"criteria":[["closed","period","yd"]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 7
+      bits: 1
+      sort: 2
       width: 150
-      heading: Due Date
+      heading: Date Closed
     - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
+    - column_id: 8
+      bits: 1
+      sort: 1
+      sort: 6
+      width: 160
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
+
+- id: 11
+  parent_id: 8
+  title: This Week
+  flags: 0x2b
+  sort: 3
+  root: T
+  sort_id: 5
+  config: '{"criteria":[["closed","period","tw"]],"conditions":[]}'
+  columns:
+    - column_id: 1
       bits: 1
       sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 7
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Date Closed
+    - column_id: 3
+      bits: 1
       sort: 3
       width: 300
       heading: Subject
     - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
+    - column_id: 8
       bits: 1
       sort: 1
+      sort: 6
+      width: 160
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
+
+- id: 12
+  parent_id: 8
+  title: This Month
+  flags: 0x2b
+  sort: 4
+  root: T
+  sort_id: 5
+  config: '{"criteria":[["closed","period","tm"]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 7
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Date Closed
+    - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
       sort: 4
       width: 185
       heading: From
-    - column_id: 5
+    - column_id: 8
       bits: 1
       sort: 1
-      sort: 5
-      width: 85
-      heading: Priority
+      sort: 6
+      width: 160
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
+
+- id: 13
+  parent_id: 8
+  title: This Quarter
+  flags: 0x2b
+  sort: 5
+  root: T
+  sort_id: 6
+  config: '{"criteria":[["closed","period","tq"]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 7
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Date Closed
+    - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
     - column_id: 8
       bits: 1
       sort: 1
       sort: 6
       width: 160
-      heading: Assigned To
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
+
+- id: 14
+  parent_id: 8
+  title: This Year
+  flags: 0x2b
+  sort: 6
+  root: T
+  sort_id: 7
+  config: '{"criteria":[["closed","period","ty"]],"conditions":[]}'
+  columns:
+    - column_id: 1
+      bits: 1
+      sort: 1
+      width: 100
+      heading: Ticket
+    - column_id: 7
+      bits: 1
+      sort: 2
+      width: 150
+      heading: Date Closed
+    - column_id: 3
+      bits: 1
+      sort: 3
+      width: 300
+      heading: Subject
+    - column_id: 4
+      bits: 1
+      sort: 4
+      width: 185
+      heading: From
+    - column_id: 8
+      bits: 1
+      sort: 1
+      sort: 6
+      width: 160
+      heading: Closed By
+  sorts:
+    - sort_id: 5
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
+    - sort_id: 6
+    - sort_id: 7
diff --git a/include/i18n/en_US/queue_column.yaml b/include/i18n/en_US/queue_column.yaml
index 6f7419a4af0e0074293b957b07f813668d1889e4..03250da19a83b3118bbd04eb4792218c232928dd 100644
--- a/include/i18n/en_US/queue_column.yaml
+++ b/include/i18n/en_US/queue_column.yaml
@@ -129,3 +129,10 @@
   truncate: "wrap"
   annotations: "[]"
   conditions: "[]"
+
+- id: 14
+  name: "Team"
+  primary: "team_id"
+  truncate: "wrap"
+  annotations: "[]"
+  conditions: "[]"
diff --git a/include/i18n/en_US/queue_sort.yaml b/include/i18n/en_US/queue_sort.yaml
index 09b0fb87f6530db783274f2716be91ff4f1cc8d3..35cd47d4a2dd1b12b3ff6af4931a733ba9172eea 100644
--- a/include/i18n/en_US/queue_sort.yaml
+++ b/include/i18n/en_US/queue_sort.yaml
@@ -27,3 +27,7 @@
 - id: 6
   name: Create Date
   columns: '["-created"]'
+
+- id: 7
+  name: Update Date
+  columns: '["-lastupdate"]'
diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php
index b936699df859013ba8706f9c27daba2c310a840e..fa9862bb830d4997296aad36fb3fbbbe2bc154b2 100644
--- a/include/staff/queue.inc.php
+++ b/include/staff/queue.inc.php
@@ -4,10 +4,11 @@
 if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
 
 $info = $qs = array();
-
+$parent = null;
 if (!$queue) {
     $queue = CustomQueue::create(array(
         'flags' => CustomQueue::FLAG_QUEUE,
+        'parent_id' => 0,
     ));
 }
 if ($queue->__new__) {
@@ -16,6 +17,7 @@ if ($queue->__new__) {
     $submit_text=__('Create');
 }
 else {
+    $parent = $queue->parent;
     $title=__('Manage Custom Queue');
     $action='update';
     $submit_text=__('Save Changes');
@@ -29,8 +31,6 @@ else {
   <input type="hidden" name="do" value="<?php echo $action; ?>">
   <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
   <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
-  <input type="hidden" name="root" value="<?php echo Format::htmlchars($_REQUEST['t']); ?>">
-
   <h2><a href="settings.php?t=tickets#queues"><?php echo __('Ticket Queues'); ?></a>
       <i class="icon-caret-right" style="color:rgba(0,0,0,.3);"></i> <?php echo $title; ?>
       <?php if (isset($queue->id)) { ?><small>
@@ -63,20 +63,33 @@ else {
         <br/>
         <div class="error"><?php echo $errors['queue-name']; ?></div>
         <br/>
+        <div>
+          <div><strong><?php echo __("Parent Queue"); ?>:</strong></div>
+          <select name="parent_id" id="parent-id">
+            <option value="0">— <?php echo __('Top-Level Queue'); ?> —</option>
+  <?php foreach (CustomQueue::queues() as $cq) {
+          // Queue cannot be a descendent of itself
+          if ($cq->id == $queue->id)
+              continue;
+          if (strpos($cq->path, "/{$queue->id}/") !== false)
+              continue;
+  ?>
+            <option value="<?php echo $cq->id; ?>"
+              <?php if ($cq->getId() == $queue->parent_id) echo 'selected="selected"'; ?>
+              ><?php echo $cq->getFullName(); ?></option>
+  <?php } ?>
+          </select>
+          <span class="error"><?php echo Format::htmlchars($errors['parent_id']); ?></span>
+        </div>
+        <div class="faded <?php echo $parent ? ' ': 'hidden'; ?>"
+            id="inherited-parent" style="margin-top: 1em;">
+          <div><strong><i class="icon-caret-down"></i>&nbsp; <?php echo  __('Inherited Criteria'); ?></strong></div>
+          <div id="parent-criteria">
+            <?php echo $parent ? nl2br(Format::htmlchars($parent->describeCriteria())) : ''; ?>
+          </div>
+        </div>
+        <hr/>
         <div><strong><?php echo __("Queue Search Criteria"); ?></strong></div>
-        <label class="checkbox" style="line-height:1.3em">
-          <input type="checkbox" class="checkbox" name="inherit" <?php
-            if ($queue->inheritCriteria()) echo 'checked="checked"';
-            ?>/>
-          <?php echo __('Include parent search criteria');
-          if ($queue->parent) { ?>
-            <span id="parent_q_crit" class="faded">
-            <i class="icon-caret-right"></i>
-            <br/><?php
-              echo nl2br(Format::htmlchars($queue->parent->describeCriteria()));
-            ?></span>
-<?php     } ?>
-        </label>
         <hr/>
         <div class="error"><?php echo $errors['criteria']; ?></div>
         <div class="advanced-search">
@@ -89,27 +102,6 @@ else {
         </div>
       </td>
       <td style="width:35%; padding-left:40px; vertical-align:top">
-        <div><strong><?php echo __("Parent Queue"); ?>:</strong></div>
-        <select name="parent_id" onchange="javascript:
-        $('#parent_q_crit').toggle($(this).find(':selected').val()
-          == <?php echo $queue->parent_id ?: 0; ?>);">
-          <option value="0">— <?php echo __('Top-Level Queue'); ?> —</option>
-<?php foreach (CustomQueue::queues() as $cq) {
-        // Queue cannot be a descendent of itself
-        if ($cq->id == $queue->id)
-            continue;
-        if (strpos($cq->path, "/{$queue->id}/") !== false)
-            continue;
-?>
-          <option value="<?php echo $cq->id; ?>"
-            <?php if ($cq->getId() == $queue->parent_id) echo 'selected="selected"'; ?>
-            ><?php echo $cq->getFullName(); ?></option>
-<?php } ?>
-        </select>
-        <div class="error"><?php echo Format::htmlchars($errors['parent_id']); ?></div>
-
-        <br/>
-        <br/>
         <div><strong><?php echo __("Quick Filter"); ?></strong></div>
         <hr/>
         <select name="filter">
@@ -309,6 +301,27 @@ var Q = setInterval(function() {
   );
 } ?>
 }, 25);
+$('select#parent-id').change(function() {
+    var form = $(this).closest('form');
+    var qid = parseInt($(this).val(), 10) || 0;
+
+    if (qid > 0) {
+        $.ajax({
+            type: "GET",
+            url: 'ajax.php/queue/'+qid,
+            dataType: 'json',
+            success: function(queue) {
+                $('#parent-name', form).html(queue.name);
+                $('#parent-criteria', form).html(queue.criteria);
+                $('#inherited-parent', form).fadeIn();
+                }
+            })
+            .done(function() { })
+            .fail(function() { });
+    } else {
+        $('#inherited-parent', form).fadeOut();
+    }
+});
 }();
 </script>
         </table>
diff --git a/include/staff/templates/advanced-search-criteria.tmpl.php b/include/staff/templates/advanced-search-criteria.tmpl.php
index 9348cf79be9a81e3cb762f1bcaf96604acedf437..309ce63cd117687c0a530cfe6935609c38e05729 100644
--- a/include/staff/templates/advanced-search-criteria.tmpl.php
+++ b/include/staff/templates/advanced-search-criteria.tmpl.php
@@ -11,7 +11,7 @@ if (($search instanceof SavedQueue) && !$search->checkOwnership($thisstaff)) {
     echo '<div class="faded">'.  nl2br(Format::htmlchars($search->describeCriteria())).
                     '</div><br>';
     // Show any supplemental filters
-    if ($matches  && count($info)) {
+    if ($matches) {
         ?>
         <div id="ticket-flags"
             style="padding:5px; border-top: 1px dotted #777;">
diff --git a/include/staff/templates/queue-navigation.tmpl.php b/include/staff/templates/queue-navigation.tmpl.php
index d1061c8d43da152d5686b428fe6ee0b50943f8f1..380e03af961d5f8f6901d9bf63eadb55bb7679a4 100644
--- a/include/staff/templates/queue-navigation.tmpl.php
+++ b/include/staff/templates/queue-navigation.tmpl.php
@@ -15,6 +15,9 @@ $selected = (!isset($_REQUEST['a'])  && $_REQUEST['queue'] == $this_queue->getId
   <div class="customQ-dropdown">
     <ul class="scroll-height">
       <!-- Add top-level queue (with count) -->
+
+      <?php
+      if (!$children) { ?>
       <li class="top-level">
         <span class="pull-right newItemQ queue-count"
           data-queue-id="<?php echo $q->id; ?>"><span class="faded-more">-</span>
@@ -25,9 +28,9 @@ $selected = (!isset($_REQUEST['a'])  && $_REQUEST['queue'] == $this_queue->getId
         <?php
           echo Format::htmlchars($q->getName()); ?>
         </a>
-        </h4>
       </li>
-
+      <?php
+      } ?>
       <!-- Start Dropdown and child queues -->
       <?php foreach ($childs as $_) {
           list($q, $children) = $_;
diff --git a/include/staff/templates/queue-savedsearches-nav.tmpl.php b/include/staff/templates/queue-savedsearches-nav.tmpl.php
index ef06cf06c91db2b6e5a32ea0395885cc8d05cd25..34a8aff168cb10326f1e8a927f506565d3de13fb 100644
--- a/include/staff/templates/queue-savedsearches-nav.tmpl.php
+++ b/include/staff/templates/queue-savedsearches-nav.tmpl.php
@@ -4,6 +4,10 @@
 // $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
+
+$searches = SavedQueue::getHierarchicalQueues($thisstaff);
+if ($queue && !$queue->parent_id && $queue->staff_id)
+    $child_selected = true;
 ?>
 <li class="primary-only item <?php if ($child_selected) echo 'active'; ?>">
 <?php
@@ -16,14 +20,9 @@
   <div class="customQ-dropdown">
     <ul class="scroll-height">
       <!-- 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) {
-        if ($q->checkAccess($thisstaff))
+      <?php foreach ($searches as $search) {
+          list($q, $children) = $search;
+          if ($q->checkAccess($thisstaff))
             include 'queue-subnavigation.tmpl.php';
       } ?>
      <?php
diff --git a/include/staff/templates/queue-sorting-edit.tmpl.php b/include/staff/templates/queue-sorting-edit.tmpl.php
index 0a2d98b0246433b59ea0f9cc1d575deb3a4ed41b..a001201a2beb94e6c3391f2965e6353882dfc8c3 100644
--- a/include/staff/templates/queue-sorting-edit.tmpl.php
+++ b/include/staff/templates/queue-sorting-edit.tmpl.php
@@ -5,6 +5,7 @@
  * $column - <QueueColumn> instance for this column
  */
 $sortid = $sort->getId();
+$advanced = in_array('extra', $sort::getMeta()->getFieldNames());
 ?>
 <h3 class="drag-handle"><?php echo __('Manage Sort Options'); ?> &mdash;
     <?php echo $sort->get('name') ?></h3>
@@ -14,10 +15,30 @@ $sortid = $sort->getId();
 <form method="post" action="#tickets/search/sort/edit/<?php
     echo $sortid; ?>">
 
+<?php if ($advanced) { ?>
+  <ul class="clean tabs">
+    <li class="active"><a href="#fields"><i class="icon-columns"></i>
+      <?php echo __('Fields'); ?></a></li>
+    <li><a href="#advanced"><i class="icon-cog"></i>
+      <?php echo __('Advanced'); ?></a></li>
+  </ul>
+
+  <div class="tab_content" id="fields">
+<?php } ?>
+
 <?php
 include 'queue-sorting.tmpl.php';
 ?>
 
+<?php if ($advanced) { ?>
+  </div>
+
+  <div class="hidden tab_content" id="advanced">
+    <?php echo $sort->getAdvancedConfigForm()->asTable(); ?>
+  </div>
+
+<?php } ?>
+
 <hr>
 <p class="full-width">
     <span class="buttons pull-left">
diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php
index 37d7cced74789b85fd6aa1f6cf6b0dc720eacc30..fe82ff9a21460eda9090a000f4fda147a99bc323 100644
--- a/include/staff/templates/queue-tickets.tmpl.php
+++ b/include/staff/templates/queue-tickets.tmpl.php
@@ -76,8 +76,20 @@ if (!$sorted && isset($sort['queuesort'])) {
 $page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
 $pageNav = new Pagenate(PHP_INT_MAX, $page, PAGE_LIMIT);
 $tickets = $pageNav->paginateSimple($tickets);
-$count = $tickets->total();
-$pageNav->setTotal($count);
+
+// Creative twist here. Create a new query copying the query criteria, sort, limit,
+// and offset. Then join this new query to the $tickets query and clear the
+// criteria, sort, limit, and offset from the outer query.
+$criteria = clone $tickets;
+$criteria->annotations = $criteria->related = $criteria->aggregated = [];
+$tickets->constraints = $tickets->extra = [];
+$tickets = $tickets->filter(['ticket_id__in' => $criteria->values_flat('ticket_id')])
+    ->limit(false)->offset(false)->order_by(false);
+# Index hint should be used on the $criteria query only
+$tickets->clearOption(QuerySet::OPT_INDEX_HINT);
+
+$count = $queue->getCount($thisstaff);
+$pageNav->setTotal($count, true);
 $pageNav->setURL('tickets.php', $args);
 ?>
 
diff --git a/scp/autocron.php b/scp/autocron.php
index 170ab3a8b421286bdfe1db6a648a13e7b971946d..cc124455f3a246ef97f37ce3fe7482d8d9797e93 100644
--- a/scp/autocron.php
+++ b/scp/autocron.php
@@ -45,6 +45,11 @@ if ($sec < 180 || !$ost || $ost->isUpgradePending())
 
 require_once(INCLUDE_DIR.'class.cron.php');
 
+// Run tickets count every 3rd run or so... force new count by skipping cached
+// results
+if ((mt_rand(1, 12) % 3) == 0)
+    SavedQueue::counts($thisstaff, array(), false);
+
 // Clear staff obj to avoid false credit internal notes & auto-assignment
 $thisstaff = null;
 
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 878333317fb1d3e17f90f6d87dab46254a3b00f9..f255ae506c045beacf0499e528f31b4b5adee4b1 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -3525,6 +3525,12 @@ table.grid.form caption {
   margin-bottom: 5px;
 }
 
+.grid.form .field > .field-hint-text {
+  font-style: italic;
+  margin: 0 10px 5px 10px;
+  opacity: 0.8;
+}
+
 #basic_search {
   background-color: #f4f4f4;
   margin: -10px 0;
diff --git a/scp/js/scp.js b/scp/js/scp.js
index c83673cd3ce944df8139dcef8f3561e7f6707b0c..2b346edcb469f9df5789ad3b591e6935b79bf473 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -1120,7 +1120,7 @@ if ($.support.pjax) {
     if (!$this.hasClass('no-pjax')
         && !$this.closest('.no-pjax').length
         && $this.attr('href').charAt(0) != '#')
-      $.pjax.click(event, {container: $this.data('pjaxContainer') || $('#pjax-container'), timeout: 2000});
+      $.pjax.click(event, {container: $this.data('pjaxContainer') || $('#pjax-container'), timeout: 30000});
   })
 }
 
diff --git a/scp/queues.php b/scp/queues.php
index f02f4efc8a5c3faa6d047d57d9962fa5fd0e86db..997fefa2678cae361a9654c6efecfaccf0199fe9 100644
--- a/scp/queues.php
+++ b/scp/queues.php
@@ -44,7 +44,7 @@ if ($_POST) {
         $queue = CustomQueue::create(array(
             'staff_id' => 0,
             'title' => $_POST['queue-name'],
-            'root' => $_POST['root'] ?: 'T'
+            'root' => 'T'
         ));
 
         if ($queue->update($_POST, $errors) && $queue->save(true)) {
diff --git a/scp/tickets.php b/scp/tickets.php
index d100f38d7b1af8e1505301922754e5925bc264dc..86fec81f28395a49905bcd3a342bfaf159be5d15 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -79,16 +79,20 @@ if (!$ticket) {
     elseif (isset($_GET['a']) && $_GET['a'] === 'search'
         && ($_GET['query'])
     ) {
-        $key = substr(md5($_GET['query']), -10);
-        if ($_GET['search-type'] == 'typeahead') {
-            // Use a faster index
-            $criteria = ['user__emails__address', 'equal', $_GET['query']];
-        }
-        else {
-            $criteria = [':keywords', null, $_GET['query']];
+        $wc = mb_str_wc($_GET['query']);
+        if ($wc < 4) {
+            $key = substr(md5($_GET['query']), -10);
+            if ($_GET['search-type'] == 'typeahead') {
+                // Use a faster index
+                $criteria = ['user__emails__address', 'equal', $_GET['query']];
+            } else {
+                $criteria = [':keywords', null, $_GET['query']];
+            }
+            $_SESSION['advsearch'][$key] = [$criteria];
+            $queue_id = "adhoc,{$key}";
+        } else {
+            $errors['err'] = __('Search term cannot have more than 3 keywords');
         }
-        $_SESSION['advsearch'][$key] = [$criteria];
-        $queue_id = "adhoc,{$key}";
     }
 
     $queue_key = sprintf('::Q:%s', ObjectModel::OBJECT_TYPE_TICKET);
@@ -462,7 +466,7 @@ foreach ($queues as $_) {
                 || false !== strpos($queue->getPath(), "/{$q->getId()}/"));
         include STAFFINC_DIR . 'templates/queue-navigation.tmpl.php';
 
-        return ($child_selected || $selected);
+        return $child_selected;
     });
 }
 
@@ -473,10 +477,7 @@ $nav->addSubMenu(function() use ($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 instanceof SavedSearch;
-    $searches = SavedSearch::forStaff($thisstaff)->getIterator();
-
     include STAFFINC_DIR . 'templates/queue-savedsearches-nav.tmpl.php';
-
     return ($child_selected || $selected);
 });