diff --git a/bootstrap.php b/bootstrap.php
index 2703395faa6a5548847cfa4590cbbf0f4067bc19..8b7ea7eb9e20e2e4e74ee554242ac110afc861ee 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -102,6 +102,7 @@ class Bootstrap {
         define('THREAD_COLLABORATOR_TABLE', $prefix.'thread_collaborator');
         define('TICKET_STATUS_TABLE', $prefix.'ticket_status');
         define('TICKET_PRIORITY_TABLE',$prefix.'ticket_priority');
+        define('EVENT_TABLE',$prefix.'event');
 
         define('TASK_TABLE', $prefix.'task');
         define('TASK_CDATA_TABLE', $prefix.'task__cdata');
@@ -293,6 +294,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 93da251e7d164e9548550dce3b35cf16051bbcf3..fabe2d810fdd8e694d566b418b656c028e94a916 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -135,7 +135,7 @@ class SearchAjaxAPI extends AjaxController {
             $_SESSION[$key] = $keep;
         }
     }
-    
+
     function _hashCriteria($criteria, $size=10) {
         $parts = array();
         foreach ($criteria as $C) {
@@ -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/api.tickets.php b/include/api.tickets.php
index c209086a9d57a682462f2d2966c5fd41db69b176..8d20e391cfa1aebb66ad5d154a16f2fd1510d1ef 100644
--- a/include/api.tickets.php
+++ b/include/api.tickets.php
@@ -14,7 +14,8 @@ class TicketApiController extends ApiController {
             "attachments" => array("*" =>
                 array("name", "type", "data", "encoding", "size")
             ),
-            "message", "ip", "priorityId"
+            "message", "ip", "priorityId",
+            "system_emails", "thread_entry_recipients"
         );
         # Fetch dynamic form field names for the given help topic and add
         # the names to the supported request structure
diff --git a/include/class.config.php b/include/class.config.php
index 004a1e8e8a44d2be9fdcc1cde54acd0a03300d73..d7518a91f947786ba15ba69a345f5ee2018a7281 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -30,10 +30,6 @@ class Config {
     # new settings and the corresponding default values.
     var $defaults = array();                # List of default values
 
-
-    # Items
-    var $items = null;
-
     function __construct($section=null, $defaults=array()) {
         if ($section)
             $this->section = $section;
@@ -133,18 +129,11 @@ class Config {
 
     function destroy() {
         unset($this->session);
-        if ($this->items)
-            $this->items->delete();
-
-        return true;
+        return $this->items()->delete() > 0;
     }
 
     function items() {
-
-        if (!isset($this->items))
-            $this->items = ConfigItem::items($this->section, $this->section_column);
-
-        return $this->items;
+        return ConfigItem::items($this->section, $this->section_column);
     }
 }
 
diff --git a/include/class.export.php b/include/class.export.php
index 4fc1bbf7bd1c2bcf00412ca74f1c45223fae4f2f..2ea048ae92996d72523d50f28849eb6a2cc85490 100644
--- a/include/class.export.php
+++ b/include/class.export.php
@@ -76,7 +76,7 @@ class Export {
                     ->aggregate(array('count' => SqlAggregate::COUNT('entries__attachments__id'))),
                 'reopen_count' => TicketThread::objects()
                     ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
-                    ->filter(array('events__annulled' => 0, 'events__state' => 'reopened'))
+                    ->filter(array('events__annulled' => 0, 'events__event_id' => Event::getIdByName('reopened')))
                     ->aggregate(array('count' => SqlAggregate::COUNT('events__id'))),
                 'thread_count' => TicketThread::objects()
                     ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
diff --git a/include/class.file.php b/include/class.file.php
index e61b5afe76516fcecfd880359bb6193c13164b4e..5bbd3d8859b1303648bc6464a9075040b8a1e3c9 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -258,7 +258,9 @@ class AttachmentFile extends VerySimpleModel {
         $type = $this->getType() ?: 'application/octet-stream';
         if (isset($_REQUEST['overridetype']))
             $type = $_REQUEST['overridetype'];
-        Http::download($name ?: $this->getName(), $type, null, 'inline');
+        elseif (!strcasecmp($disposition, 'attachment'))
+            $type = 'application/octet-stream';
+        Http::download($name ?: $this->getName(), $type, null, $disposition);
         header('Content-Length: '.$this->getSize());
         $this->sendData(false);
         exit();
diff --git a/include/class.format.php b/include/class.format.php
index a2d72340dcf02e3cba45990e4e455ab8146604e3..d3d9d7ae9ae68e7994e302fc70be0d079f6cae35 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -667,20 +667,20 @@ class Format {
             '%x', $timezone ?: $cfg->getTimezone(), $user);
     }
 
-    function datetime($timestamp, $fromDb=true, $timezone=false, $user=false) {
+    function datetime($timestamp, $fromDb=true, $format=false,  $timezone=false, $user=false) {
         global $cfg;
 
         return self::__formatDate($timestamp,
-                $cfg->getDateTimeFormat(), $fromDb,
+                $format ?: $cfg->getDateTimeFormat(), $fromDb,
                 IDF_SHORT, IDF_SHORT,
                 '%x %X', $timezone ?: $cfg->getTimezone(), $user);
     }
 
-    function daydatetime($timestamp, $fromDb=true, $timezone=false, $user=false) {
+    function daydatetime($timestamp, $fromDb=true, $format=false,  $timezone=false, $user=false) {
         global $cfg;
 
         return self::__formatDate($timestamp,
-                $cfg->getDayDateTimeFormat(), $fromDb,
+                $format ?: $cfg->getDayDateTimeFormat(), $fromDb,
                 IDF_FULL, IDF_SHORT,
                 '%x %X', $timezone ?: $cfg->getTimezone(), $user);
     }
diff --git a/include/class.forms.php b/include/class.forms.php
index 4fb4de4609802681c71b57d2c9b8009d66d0aa77..1b13c175f637adc8614e746aa10f522d4ade7d1c 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -1960,6 +1960,40 @@ class ChoiceField extends FormField {
     }
 }
 
+class NumericField extends FormField {
+
+    function getSearchMethods() {
+        return array(
+            'equal' =>   __('Equal'),
+            'greater' =>  __('Greater Than'),
+            'less' =>  __('Less Than'),
+        );
+    }
+
+    function getSearchMethodWidgets() {
+        return array(
+            'equal' => array('TextboxField', array(
+                    'configuration' => array(
+                        'validator' => 'number',
+                        'size' => 6
+                        ),
+            )),
+            'greater' => array('TextboxField', array(
+                    'configuration' => array(
+                        'validator' => 'number',
+                        'size' => 6
+                        ),
+            )),
+            'less' => array('TextboxField', array(
+                    'configuration' => array(
+                        'validator' => 'number',
+                        'size' => 6
+                        ),
+            )),
+        );
+    }
+}
+
 class DatetimeField extends FormField {
     static $widget = 'DatetimePickerWidget';
 
@@ -2043,7 +2077,7 @@ class DatetimeField extends FormField {
 
     function to_php($value) {
 
-        if (strtotime($value) <= 0)
+        if (!is_numeric($value) && strtotime($value) <= 0)
             return 0;
 
         return $value;
@@ -2056,8 +2090,9 @@ class DatetimeField extends FormField {
             return '';
 
         $config = $this->getConfiguration();
+        $format = $config['format'] ?: false;
         if ($config['gmt'])
-            return $this->format((int) $datetime->format('U'));
+            return $this->format((int) $datetime->format('U'), $format);
 
         // Force timezone if field has one.
         if ($config['timezone']) {
@@ -2066,10 +2101,10 @@ class DatetimeField extends FormField {
         }
 
         $value = $this->format($datetime->format('U'),
-                $datetime->getTimezone()->getName());
-
+                $datetime->getTimezone()->getName(),
+                $format);
         // No need to show timezone
-        if (!$config['time'])
+        if (!$config['time'] || $format)
             return $value;
 
         // Display is NOT timezone aware show entry's timezone.
@@ -2083,16 +2118,16 @@ class DatetimeField extends FormField {
         return ($timestamp > 0) ? $timestamp : '';
     }
 
-    function format($timestamp, $timezone=false) {
+    function format($timestamp, $timezone=false, $format=false) {
 
         if (!$timestamp || $timestamp <= 0)
             return '';
 
         $config = $this->getConfiguration();
         if ($config['time'])
-            $formatted = Format::datetime($timestamp, false, $timezone);
+            $formatted = Format::datetime($timestamp, false, $format,  $timezone);
         else
-            $formatted = Format::date($timestamp, false, false, $timezone);
+            $formatted = Format::date($timestamp, false, $format, $timezone);
 
         return $formatted;
     }
diff --git a/include/class.i18n.php b/include/class.i18n.php
index 17709bee89b112527e2158910641d18608c64555..ab692eb6292cd2a4181249920eadc04006054eeb 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -64,6 +64,7 @@ class Internationalization {
             'ticket_status.yaml' => 'TicketStatus::__create',
             // Role
             'role.yaml' =>          'Role::__create',
+            'event.yaml' =>         'Event::__create',
             'file.yaml' =>          'AttachmentFile::__create',
             'sequence.yaml' =>      'Sequence::__create',
             'queue_column.yaml' =>  'QueueColumn::__create',
diff --git a/include/class.list.php b/include/class.list.php
index e5c84fbd91b58cc2f8dc742bbec745bba1151882..f8055805c2e692eec4fb446db79c8585241260f8 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -1421,6 +1421,9 @@ implements CustomListItem, TemplateVariable, Searchable {
     }
 
     function display() {
+
+        return $this->getLocalName();
+
         return sprintf('<a class="preview" href="#"
                 data-preview="#list/%d/items/%d/preview">%s</a>',
                 $this->getListId(),
diff --git a/include/class.orm.php b/include/class.orm.php
index 106e11465db81e2093041086435f67bcbdb3a627..cd30ffa974c1ed1cb95137b0d357a25b1d1b7610 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')."&nbsp;";
         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 5d23711784dec4364be188ba6f1308c22108596a..819327074d1ed64219afa96794c47897ddad0d61 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);
@@ -574,7 +577,7 @@ class CustomQueue extends VerySimpleModel {
                 continue;
 
             $name = $f->get('name') ?: 'field_'.$f->get('id');
-            $key = 'cdata.'.$name;
+            $key = 'cdata__'.$name;
             $cdata[$key] = $f->getLocal('label');
         }
 
@@ -582,22 +585,22 @@ class CustomQueue extends VerySimpleModel {
         $fields = array(
                 'number' =>         __('Ticket Number'),
                 'created' =>        __('Date Created'),
-                'cdata.subject' =>  __('Subject'),
-                'user.name' =>      __('From'),
-                'user.default_email.address' => __('From Email'),
-                'cdata.:priority.priority_desc' => __('Priority'),
-                'dept::getLocalName' => __('Department'),
-                'topic::getName' => __('Help Topic'),
+                'cdata__subject' =>  __('Subject'),
+                'user__name' =>      __('From'),
+                'user__emails__address' => __('From Email'),
+                'cdata__priority' => __('Priority'),
+                'dept_id' => __('Department'),
+                'topic_id' => __('Help Topic'),
                 'source' =>         __('Source'),
-                'status::getName' =>__('Current Status'),
+                'status__id' =>__('Current Status'),
                 'lastupdate' =>     __('Last Updated'),
                 'est_duedate' =>    __('SLA Due Date'),
                 'duedate' =>        __('Due Date'),
                 'closed' =>         __('Closed Date'),
                 'isoverdue' =>      __('Overdue'),
                 'isanswered' =>     __('Answered'),
-                'staff::getName' => __('Agent Assigned'),
-                'team::getName' =>  __('Team Assigned'),
+                'staff_id' => __('Agent Assigned'),
+                'team_id' =>  __('Team Assigned'),
                 'thread_count' =>   __('Thread Count'),
                 'reopen_count' =>   __('Reopen Count'),
                 'attachment_count' => __('Attachment Count'),
@@ -629,6 +632,22 @@ class CustomQueue extends VerySimpleModel {
         return $fields;
     }
 
+    function getExportColumns($fields=array()) {
+        $columns = array();
+        $fields = $fields ?: $this->getExportFields();
+        $i = 0;
+        foreach ($fields as $path => $label) {
+            $c = QueueColumn::placeholder(array(
+                        'id' => $i++,
+                        'heading' => $label,
+                        'primary' => $path,
+                        ));
+            $c->setQueue($this);
+            $columns[$path] = $c;
+        }
+        return $columns;
+    }
+
     function getStandardColumns() {
         return $this->getColumns();
     }
@@ -775,14 +794,13 @@ class CustomQueue extends VerySimpleModel {
     }
 
     function export($options=array()) {
+        global $thisstaff;
 
-        if (!($query=$this->getBasicQuery()))
-            return false;
-
-        if (!($fields=$this->getExportFields()))
+        if (!$thisstaff
+                || !($query=$this->getBasicQuery())
+                || !($fields=$this->getExportFields()))
             return false;
 
-
         $filename = sprintf('%s Tickets-%s.csv',
                 $this->getName(),
                 strftime('%Y%m%d'));
@@ -799,14 +817,45 @@ class CustomQueue extends VerySimpleModel {
                     $filename ="$filename.csv";
             }
 
-            if (isset($opts['delimiter']))
+            if (isset($opts['delimiter']) && !$options['delimiter'])
                 $options['delimiter'] = $opts['delimiter'];
 
         }
 
+        // Apply columns
+        $columns = $this->getExportColumns($fields);
+        $headers = array(); // Reset fields based on validity of columns
+        foreach ($columns as $column) {
+            $query = $column->mangleQuery($query, $this->getRoot());
+            $headers[] = $column->getHeading();
+        }
+
+        // Apply visibility
+        if (!$this->ignoreVisibilityConstraints($thisstaff))
+            $query->filter($thisstaff->getTicketsVisibility());
+
+        // Render Util
+        $render = function ($row) use($columns) {
+            if (!$row) return false;
+
+            $record = array();
+            foreach ($columns as $path => $column) {
+                $record[] = (string) $column->from_query($row) ?:
+                    $row[$path] ?: '';
+            }
+            return $record;
+        };
 
-        return Export::saveTickets($query, $fields, $filename, 'csv',
-                $options);
+        $delimiter = $options['delimiter'] ?:
+            Internationalization::getCSVDelimiter();
+        $output = fopen('php://output', 'w');
+        Http::download($filename, "text/csv");
+        fputs($output, chr(0xEF) . chr(0xBB) . chr(0xBF));
+        fputcsv($output, $headers, $delimiter);
+        foreach ($query as $row)
+            fputcsv($output, $render($row), $delimiter);
+        fclose($output);
+        exit();
     }
 
     /**
@@ -952,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() {
@@ -1005,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() {
@@ -1035,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)
@@ -1130,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,
@@ -1252,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;
 
@@ -1270,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();
@@ -1290,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;
         };
 
@@ -1346,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;
     }
@@ -1427,7 +1547,7 @@ abstract class QueueColumnAnnotation {
     }
 
     // Add the annotation to a QuerySet
-    abstract function annotate($query);
+    abstract function annotate($query, $name);
 
     // Fetch some HTML to render the decoration on the page. This function
     // can return boolean FALSE to indicate no decoration should be applied
@@ -1462,6 +1582,17 @@ abstract class QueueColumnAnnotation {
     function isVisible($row) {
         return true;
     }
+
+    static function addToQuery($query, $name=false) {
+        $name = $name ?: static::$qname;
+        $annotation = new Static(array());
+        return $annotation->annotate($query, $name);
+    }
+
+    static function from_query($row, $name=false) {
+        $name = $name ?: static::$qname;
+        return $row[$name];
+    }
 }
 
 class TicketThreadCount
@@ -1470,9 +1601,10 @@ extends QueueColumnAnnotation {
     static $qname = '_thread_count';
     static $desc = /* @trans */ 'Thread Count';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
+        $name = $name ?: static::$qname;
         return $query->annotate(array(
-        static::$qname => TicketThread::objects()
+            $name => TicketThread::objects()
             ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
             ->exclude(array('entries__flags__hasbit' => ThreadEntry::FLAG_HIDDEN))
             ->aggregate(array('count' => SqlAggregate::COUNT('entries__id')))
@@ -1500,11 +1632,12 @@ extends QueueColumnAnnotation {
     static $qname = '_reopen_count';
     static $desc = /* @trans */ 'Reopen Count';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
+        $name = $name ?: static::$qname;
         return $query->annotate(array(
-        static::$qname => TicketThread::objects()
+            $name => TicketThread::objects()
             ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
-            ->filter(array('events__annulled' => 0, 'events__state' => 'reopened'))
+            ->filter(array('events__annulled' => 0, 'events__event_id' => Event::getIdByName('reopened')))
             ->aggregate(array('count' => SqlAggregate::COUNT('events__id')))
         ));
     }
@@ -1531,10 +1664,11 @@ extends QueueColumnAnnotation {
     static $qname = '_att_count';
     static $desc = /* @trans */ 'Attachment Count';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
         // TODO: Convert to Thread attachments
+        $name = $name ?: static::$qname;
         return $query->annotate(array(
-        static::$qname => TicketThread::objects()
+            $name => TicketThread::objects()
             ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
             ->filter(array('entries__attachments__inline' => 0))
             ->aggregate(array('count' => SqlAggregate::COUNT('entries__attachments__id')))
@@ -1561,9 +1695,10 @@ extends QueueColumnAnnotation {
     static $qname = '_collabs';
     static $desc = /* @trans */ 'Collaborator Count';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
+        $name = $name ?: static::$qname;
         return $query->annotate(array(
-        static::$qname => TicketThread::objects()
+            $name => TicketThread::objects()
             ->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
             ->aggregate(array('count' => SqlAggregate::COUNT('collaborators__id')))
         ));
@@ -1588,7 +1723,7 @@ extends QueueColumnAnnotation {
     static $icon = 'exclamation';
     static $desc = /* @trans */ 'Overdue Icon';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
         return $query->values('isoverdue');
     }
 
@@ -1607,7 +1742,7 @@ extends QueueColumnAnnotation {
     static $icon = 'phone';
     static $desc = /* @trans */ 'Ticket Source';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
         return $query->values('source');
     }
 
@@ -1622,7 +1757,7 @@ extends QueueColumnAnnotation {
     static $icon = "lock";
     static $desc = /* @trans */ 'Locked';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
         global $thisstaff;
 
         return $query
@@ -1649,7 +1784,7 @@ extends QueueColumnAnnotation {
     static $icon = "user";
     static $desc = /* @trans */ 'Assignee Avatar';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
         return $query->values('staff_id', 'team_id');
     }
 
@@ -1688,7 +1823,7 @@ extends QueueColumnAnnotation {
     static $icon = "user";
     static $desc = /* @trans */ 'User Avatar';
 
-    function annotate($query) {
+    function annotate($query, $name=false) {
         return $query->values('user_id');
     }
 
@@ -1984,6 +2119,7 @@ extends VerySimpleModel {
     var $_annotations;
     var $_conditions;
     var $_queue;            // Apparent queue if being inherited
+    var $_fields;
 
     function getId() {
         return $this->id;
@@ -2022,6 +2158,25 @@ extends VerySimpleModel {
         $this->_queue = $queue;
     }
 
+    function getFields() {
+        if (!isset($this->_fields)) {
+            $root = ($q = $this->getQueue()) ? $q->getRoot() : 'Ticket';
+            $fields = CustomQueue::getSearchableFields($root);
+            $primary = CustomQueue::getOrmPath($this->primary);
+            $secondary = CustomQueue::getOrmPath($this->secondary);
+            if (($F = $fields[$primary]) && (list(,$field) = $F))
+                $this->_fields[$primary] = $field;
+            if (($F = $fields[$secondary]) && (list(,$field) = $F))
+                $this->_fields[$secondary] = $field;
+        }
+        return $this->_fields;
+    }
+
+    function getField($path=null) {
+        $fields = $this->getFields();
+        return @$fields[$path ?: $this->primary];
+    }
+
     function getWidth() {
         return $this->width ?: 100;
     }
@@ -2089,29 +2244,36 @@ extends VerySimpleModel {
     }
 
     function renderBasicValue($row) {
-        $root = ($q = $this->getQueue()) ? $q->getRoot() : 'Ticket';
-        $fields = CustomQueue::getSearchableFields($root);
+        $fields = $this->getFields();
         $primary = CustomQueue::getOrmPath($this->primary);
         $secondary = CustomQueue::getOrmPath($this->secondary);
 
         // Return a lazily ::display()ed value so that the value to be
         // rendered by the field could be changed or display()ed when
         // converted to a string.
-
         if (($F = $fields[$primary])
-            && (list(,$field) = $F)
-            && ($T = $field->from_query($row, $primary))
+            && ($T = $F->from_query($row, $primary))
         ) {
-            return new LazyDisplayWrapper($field, $T);
+            return new LazyDisplayWrapper($F, $T);
         }
         if (($F = $fields[$secondary])
-            && (list(,$field) = $F)
-            && ($T = $field->from_query($row, $secondary))
+            && ($T = $F->from_query($row, $secondary))
         ) {
-            return new LazyDisplayWrapper($field, $T);
+            return new LazyDisplayWrapper($F, $T);
         }
 
-         return new LazyDisplayWrapper($field, '');
+         return new LazyDisplayWrapper($F, '');
+    }
+
+    function from_query($row) {
+        if (!($f = $this->getField($this->primary)))
+            return '';
+
+        $val = $f->to_php($f->from_query($row, $this->primary));
+        if (!is_string($val))
+            $val = $f->display($val);
+
+        return $val;
     }
 
     function applyTruncate($text, $row) {
@@ -2155,14 +2317,12 @@ extends VerySimpleModel {
 
     function mangleQuery($query, $root=null) {
         // Basic data
-        $fields = CustomQueue::getSearchableFields($root ?: $this->getQueue()->getRoot());
-        if ($primary = $fields[$this->primary]) {
-            list(,$field) = $primary;
+        $fields = $this->getFields();
+        if ($field = $fields[$this->primary]) {
             $query = $this->addToQuery($query, $field,
                 CustomQueue::getOrmPath($this->primary, $query));
         }
-        if ($secondary = $fields[$this->secondary]) {
-            list(,$field) = $secondary;
+        if ($field = $fields[$this->secondary]) {
             $query = $this->addToQuery($query, $field,
                 CustomQueue::getOrmPath($this->secondary, $query));
         }
@@ -2557,6 +2717,7 @@ extends VerySimpleModel {
     );
 
     var $_columns;
+    var $_extra;
 
     function getRoot($hint=false) {
         switch ($hint ?: $this->root) {
@@ -2574,6 +2735,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) {
@@ -2584,6 +2751,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;
     }
 
@@ -2617,6 +2788,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',
@@ -2652,6 +2828,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;
 
@@ -2911,3 +3092,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.report.php b/include/class.report.php
index 12d7322d3dbc6665fed18b86d8b738d49eb091ba..dbff0ba38f68714692c091d9214b079223351360 100644
--- a/include/class.report.php
+++ b/include/class.report.php
@@ -77,26 +77,31 @@ class OverviewReport {
 
     function getPlotData() {
         list($start, $stop) = $this->getDateRange();
+        $states = array("created", "closed", "reopened", "assigned", "overdue", "transferred");
+        $event_ids = Event::getIds();
 
         # Fetch all types of events over the timeframe
-        $res = db_query('SELECT DISTINCT(state) FROM '.THREAD_EVENT_TABLE
+        $res = db_query('SELECT DISTINCT(E.name) FROM '.THREAD_EVENT_TABLE
+            .' T JOIN '.EVENT_TABLE . ' E ON E.id = T.event_id'
             .' WHERE timestamp BETWEEN '.$start.' AND '.$stop
-            .' AND state IN ("created", "closed", "reopened", "assigned", "overdue", "transferred")'
+            .' AND T.event_id IN ('.implode(",",$event_ids).')'
             .' ORDER BY 1');
         $events = array();
         while ($row = db_fetch_row($res)) $events[] = $row[0];
 
         # TODO: Handle user => db timezone offset
         # XXX: Implement annulled column from the %ticket_event table
-        $res = db_query('SELECT state, DATE_FORMAT(timestamp, \'%Y-%m-%d\'), '
+        $res = db_query('SELECT H.name, DATE_FORMAT(timestamp, \'%Y-%m-%d\'), '
                 .'COUNT(DISTINCT T.id)'
             .' FROM '.THREAD_EVENT_TABLE. ' E '
+            . ' LEFT JOIN '.EVENT_TABLE. ' H
+                ON (E.event_id = H.id)'
             .' JOIN '.THREAD_TABLE. ' T
                 ON (T.id = E.thread_id AND T.object_type = "T") '
             .' WHERE E.timestamp BETWEEN '.$start.' AND '.$stop
             .' AND NOT annulled'
-            .' AND E.state IN ("created", "closed", "reopened", "assigned", "overdue", "transferred")'
-            .' GROUP BY E.state, DATE_FORMAT(E.timestamp, \'%Y-%m-%d\')'
+            .' AND E.event_id IN ('.implode(",",$event_ids).')'
+            .' GROUP BY E.event_id, DATE_FORMAT(E.timestamp, \'%Y-%m-%d\')'
             .' ORDER BY 2, 1');
         # Initialize array of plot values
         $plots = array();
@@ -139,6 +144,11 @@ class OverviewReport {
     function getTabularData($group='dept') {
         global $thisstaff;
 
+        $event_ids = Event::getIds();
+        $event = function ($name) use ($event_ids) {
+            return $event_ids[$name];
+        };
+
         list($start, $stop) = $this->getDateRange();
         $times = ThreadEvent::objects()
             ->constrain(array(
@@ -148,8 +158,8 @@ class OverviewReport {
                ))
             ->constrain(array(
                 'thread__events' => array(
-                    'thread__events__state' => 'created',
-                    'state' => 'closed',
+                    'thread__events__event_id' => $event('created'),
+                    'event_id' => $event('closed'),
                     'annulled' => 0,
                     ),
                 ))
@@ -174,27 +184,27 @@ class OverviewReport {
                 ->aggregate(array(
                     'Opened' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'created')), 1)
+                            ->when(new Q(array('event_id' => $event('created'))), 1)
                     ),
                     'Assigned' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'assigned')), 1)
+                            ->when(new Q(array('event_id' => $event('assigned'))), 1)
                     ),
                     'Overdue' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'overdue')), 1)
+                            ->when(new Q(array('event_id' => $event('overdue'))), 1)
                     ),
                     'Closed' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'closed')), 1)
+                            ->when(new Q(array('event_id' => $event('closed'))), 1)
                     ),
                     'Reopened' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'reopened')), 1)
+                            ->when(new Q(array('event_id' => $event('reopened'))), 1)
                     ),
                     'Deleted' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'deleted')), 1)
+                            ->when(new Q(array('event_id' => $event('deleted'))), 1)
                     ),
                 ));
 
diff --git a/include/class.search.php b/include/class.search.php
index 8729bffa8937ca9eef4dcd3c82fdef6679781fc7..6fa8e18c93e51e8e1e9e119f92fdc5b458c6c20a 100755
--- 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
@@ -1004,21 +1070,30 @@ class AdvancedSearchSelectionField extends ChoiceField {
 }
 
 class HelpTopicChoiceField extends AdvancedSearchSelectionField {
+    static $_topics;
+
     function hasIdValue() {
         return true;
     }
 
     function getChoices($verbose=false) {
-        return Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
+        if (!isset($this->_topics))
+            $this->_topics = Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
+
+        return $this->_topics;
     }
 }
 
 require_once INCLUDE_DIR . 'class.dept.php';
 class DepartmentChoiceField extends AdvancedSearchSelectionField {
-    var $_choices = null;
+    static $_depts;
+    var $_choices;
 
     function getChoices($verbose=false) {
-        return Dept::getDepartments();
+        if (!isset($this->_depts))
+            $this->_depts = Dept::getDepartments();
+
+        return $this->_depts;
     }
 
     function getQuickFilterChoices() {
@@ -1241,13 +1316,23 @@ trait ZeroMeansUnset {
 class AgentSelectionField extends AdvancedSearchSelectionField {
     use ZeroMeansUnset;
 
+    static $_agents;
+
     function getChoices($verbose=false) {
-        return array('M' => __('Me')) + Staff::getStaffMembers();
+        if (!isset($this->_agents)) {
+            $this->_agents = array('M' => __('Me')) +
+                Staff::getStaffMembers();
+        }
+        return $this->_agents;
     }
 
     function toString($value) {
+
         $choices =  $this->getChoices();
         $selection = array();
+        if (!is_array($value))
+            $value = array($value => $value);
+
         foreach ($value as $k => $v)
             if (isset($choices[$k]))
                 $selection[] = $choices[$k];
@@ -1278,9 +1363,13 @@ class AgentSelectionField extends AdvancedSearchSelectionField {
 }
 
 class DepartmentManagerSelectionField extends AgentSelectionField {
+    static $_members;
 
     function getChoices($verbose=false) {
-        return Staff::getStaffMembers();
+        if (isset($this->_members))
+            $this->_members = Staff::getStaffMembers();
+
+        return $this->_members;
     }
 
     function getSearchQ($method, $value, $name=false) {
@@ -1289,9 +1378,14 @@ class DepartmentManagerSelectionField extends AgentSelectionField {
 }
 
 class TeamSelectionField extends AdvancedSearchSelectionField {
+    static $_teams;
 
     function getChoices($verbose=false) {
-        return array('T' => __('One of my teams')) + Team::getTeams();
+        if (!isset($this->_teams))
+            $this->_teams = array('T' => __('One of my teams')) +
+                Team::getTeams();
+
+        return $this->_teams;
     }
 
     function getSearchQ($method, $value, $name=false) {
@@ -1315,6 +1409,19 @@ class TeamSelectionField extends AdvancedSearchSelectionField {
         $reverse = $reverse ? '-' : '';
         return $query->order_by("{$reverse}team__name");
     }
+
+    function toString($value) {
+        $choices =  $this->getChoices();
+        $selection = array();
+        if (!is_array($value))
+            $value = array($value => $value);
+        foreach ($value as $k => $v)
+            if (isset($choices[$k]))
+                $selection[] = $choices[$k];
+        return $selection ?  implode(',', $selection) :
+            parent::toString($value);
+    }
+
 }
 
 class TicketStateChoiceField extends AdvancedSearchSelectionField {
@@ -1395,6 +1502,7 @@ class OpenClosedTicketStatusList extends TicketStatusList {
         return $rv;
     }
 }
+
 class TicketStatusChoiceField extends SelectionField {
     static $widget = 'ChoicesWidget';
 
@@ -1427,6 +1535,50 @@ class TicketStatusChoiceField extends SelectionField {
     }
 }
 
+class TicketThreadCountField extends NumericField {
+
+    function addToQuery($query, $name=false) {
+        return TicketThreadCount::addToQuery($query, $name);
+    }
+
+    function from_query($row, $name=false) {
+         return TicketThreadCount::from_query($row, $name);
+    }
+}
+
+class TicketReopenCountField extends NumericField {
+
+    function addToQuery($query, $name=false) {
+        return TicketReopenCount::addToQuery($query, $name);
+    }
+
+    function from_query($row, $name=false) {
+         return TicketReopenCount::from_query($row, $name);
+    }
+}
+
+class ThreadAttachmentCountField extends NumericField {
+
+    function addToQuery($query, $name=false) {
+        return ThreadAttachmentCount::addToQuery($query, $name);
+    }
+
+    function from_query($row, $name=false) {
+         return ThreadAttachmentCount::from_query($row, $name);
+    }
+}
+
+class ThreadCollaboratorCountField extends NumericField {
+
+    function addToQuery($query, $name=false) {
+        return ThreadCollaboratorCount::addToQuery($query, $name);
+    }
+
+    function from_query($row, $name=false) {
+         return ThreadCollaboratorCount::from_query($row, $name);
+    }
+}
+
 interface Searchable {
     // Fetch an array of [ orm__path => Field() ] pairs. The field label is
     // used when this list is rendered in a dropdown, and the field search
diff --git a/include/class.thread.php b/include/class.thread.php
index 481a34835aed3ce179a8c83bf8e07b3ed3b84596..9488835a3d80c68f1a8a949f2d21094f4b414368 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -2054,16 +2054,80 @@ class ThreadEvent extends VerySimpleModel {
                     $subclasses[$class::$state] = $class;
             }
         }
+        $this->state = Event::getNameById($this->event_id);
         if (!($class = $subclasses[$this->state]))
             return $this;
         return new $class($this->ht);
     }
 }
 
+class Event extends VerySimpleModel {
+    static $meta = array(
+        'table' => EVENT_TABLE,
+        'pk' => array('id'),
+    );
+
+    function getInfo() {
+        return $this->ht;
+    }
+
+    function getId() {
+        return $this->id;
+    }
+
+    function getName() {
+        return $this->name;
+    }
+
+    function getDescription() {
+        return $this->description;
+    }
+
+    static function getNameById($id) {
+        return array_search($id, self::getIds());
+    }
+
+    static function getIdByName($name) {
+         $ids =  self::getIds();
+         return $ids[$name] ?: 0;
+    }
+
+    static function getIds() {
+        static $ids;
+
+        if (!isset($ids)) {
+            $ids = array();
+            $events = self::objects()->values_flat('id', 'name');
+            foreach ($events as $row) {
+                list($id, $name) = $row;
+                $ids[$name] = $id;
+            }
+        }
+
+        return $ids;
+    }
+
+    static function create($vars=false, &$errors=array()) {
+        $event = new static($vars);
+        return $event;
+    }
+
+    static function __create($vars, &$errors=array()) {
+        $event = self::create($vars);
+        $event->save();
+        return $event;
+    }
+
+    function save($refetch=false) {
+        return parent::save($refetch);
+    }
+}
+
 class ThreadEvents extends InstrumentedList {
     function annul($event) {
+        $event_id = Event::getIdByName($event);
         $this->queryset
-            ->filter(array('state' => $event))
+            ->filter(array('event_id' => $event_id))
             ->update(array('annulled' => 1));
     }
 
@@ -2118,7 +2182,7 @@ class ThreadEvents extends InstrumentedList {
             }
         }
         $event->username = $username;
-        $event->state = $state;
+        $event->event_id = Event::getIdByName($state);
 
         if ($data) {
             if (is_array($data))
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 43446e89e67ed43a885f61045ec3266f3f1efbc2..37c80c73c669fa1b9f59dbbebfed6eb563b3fc3d 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -2114,27 +2114,39 @@ implements RestrictedAccess, Threadable, Searchable {
             )),
             'created' => new DatetimeField(array(
                 'label' => __('Create Date'),
-                'configuration' => array('fromdb' => true),
+                'configuration' => array(
+                    'fromdb' => true, 'time' => true,
+                    'format' => 'y-MM-dd HH:mm:ss'),
             )),
             'duedate' => new DatetimeField(array(
                 'label' => __('Due Date'),
-                'configuration' => array('fromdb' => true),
+                'configuration' => array(
+                    'fromdb' => true, 'time' => true,
+                    'format' => 'y-MM-dd HH:mm:ss'),
             )),
             'est_duedate' => new DatetimeField(array(
                 'label' => __('SLA Due Date'),
-                'configuration' => array('fromdb' => true),
+                'configuration' => array(
+                    'fromdb' => true, 'time' => true,
+                    'format' => 'y-MM-dd HH:mm:ss'),
             )),
             'reopened' => new DatetimeField(array(
                 'label' => __('Reopen Date'),
-                'configuration' => array('fromdb' => true),
+                'configuration' => array(
+                    'fromdb' => true, 'time' => true,
+                    'format' => 'y-MM-dd HH:mm:ss'),
             )),
             'closed' => new DatetimeField(array(
                 'label' => __('Close Date'),
-                'configuration' => array('fromdb' => true),
+                'configuration' => array(
+                    'fromdb' => true, 'time' => true,
+                    'format' => 'y-MM-dd HH:mm:ss'),
             )),
             'lastupdate' => new DatetimeField(array(
                 'label' => __('Last Update'),
-                'configuration' => array('fromdb' => true),
+                'configuration' => array(
+                    'fromdb' => true, 'time' => true,
+                    'format' => 'y-MM-dd HH:mm:ss'),
             )),
             'assignee' => new AssigneeChoiceField(array(
                 'label' => __('Assignee'),
@@ -2171,6 +2183,18 @@ implements RestrictedAccess, Threadable, Searchable {
             'isassigned' => new AssignedField(array(
                         'label' => __('Assigned'),
             )),
+            'thread_count' => new TicketThreadCountField(array(
+                        'label' => __('Thread Count'),
+            )),
+            'attachment_count' => new ThreadAttachmentCountField(array(
+                        'label' => __('Attachment Count'),
+            )),
+            'collaborator_count' => new ThreadCollaboratorCountField(array(
+                        'label' => __('Collaborator Count'),
+            )),
+            'reopen_count' => new TicketReopenCountField(array(
+                        'label' => __('Reopen Count'),
+            )),
             'ip_address' => new TextboxField(array(
                 'label' => __('IP Address'),
                 'configuration' => array('validator' => 'ip'),
@@ -3176,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);
     }
@@ -4186,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/class.user.php b/include/class.user.php
index 0fa35e4a14d62514632bcf163f80fc25e216b49c..8d6bd1a7bb76f9197d1b6b1844271aec337121d2 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -635,11 +635,11 @@ implements TemplateVariable, Searchable {
     }
 
     function deleteAllTickets() {
-        $deleted = TicketStatus::lookup(array('state' => 'deleted'));
+        $status_id = TicketStatus::lookup(array('state' => 'deleted'));
         foreach($this->tickets as $ticket) {
             if (!$T = Ticket::lookup($ticket->getId()))
                 continue;
-            if (!$T->setStatus($deleted))
+            if (!$T->setStatus($status_id))
                 return false;
         }
         $this->tickets->reset();
diff --git a/include/cli/modules/upgrade.php b/include/cli/modules/upgrade.php
index 297d6d56f84d36dcdc1ad23c42410a09d483b165..383c47f5c66226ac32f6ae102f502e27fcbe1645 100644
--- a/include/cli/modules/upgrade.php
+++ b/include/cli/modules/upgrade.php
@@ -51,12 +51,12 @@ class CliUpgrader extends Module {
         $cfg = $ost->getConfig();
 
         while (true) {
-            if ($upgrader->getTask()) {
-                // If there's anythin in the model cache (like a Staff
-                // object or something), ensure that changes to the database
-                // model won't cause crashes
-                ModelInstanceManager::flushCache();
+            // If there's anythin in the model cache (like a Staff
+            // object or something), ensure that changes to the database
+            // model won't cause crashes
+            ModelInstanceManager::flushCache();
 
+            if ($upgrader->getTask()) {
                 // More pending tasks - doTasks returns the number of pending tasks
                 $this->stdout->write("... {$upgrader->getNextAction()}\n");
                 $upgrader->doTask();
diff --git a/include/client/templates/thread-entries.tmpl.php b/include/client/templates/thread-entries.tmpl.php
index f54fa4f6b228243fb3a1c20588fd127752929082..b3df17773728dbb2154d6c1707360e82f02fd2d1 100644
--- a/include/client/templates/thread-entries.tmpl.php
+++ b/include/client/templates/thread-entries.tmpl.php
@@ -1,6 +1,12 @@
 <?php
+$states = array('created', 'closed', 'reopened', 'edited', 'collab');
+$event_ids = array();
+foreach ($states as $state) {
+    $eid = Event::getIdByName($state);
+    $event_ids[] = $eid;
+}
 $events = $events
-    ->filter(array('state__in' => array('created', 'closed', 'reopened', 'edited', 'collab')))
+    ->filter(array('event_id__in' => $event_ids))
     ->order_by('id');
 $eventCount = count($events);
 $events = new IteratorIterator($events->getIterator());
diff --git a/include/i18n/en_US/event.yaml b/include/i18n/en_US/event.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7c9c8ed5bb93d531a50a21f6b84f6a4caf0f07ee
--- /dev/null
+++ b/include/i18n/en_US/event.yaml
@@ -0,0 +1,61 @@
+#
+# event.yaml
+#
+# Events initially inserted into the system.
+#
+---
+- id: 1
+  name: created
+  description:
+
+- id: 2
+  name: closed
+  description:
+
+- id: 3
+  name: reopened
+  description:
+
+- id: 4
+  name: assigned
+  description:
+
+- id: 5
+  name: released
+  description:
+
+- id: 6
+  name: transferred
+  description:
+
+- id: 7
+  name: referred
+  description:
+
+- id: 8
+  name: overdue
+  description:
+
+- id: 9
+  name: edited
+  description:
+
+- id: 10
+  name: viewed
+  description:
+
+- id: 11
+  name: error
+  description:
+
+- id: 12
+  name: collab
+  description:
+
+- id: 13
+  name: resent
+  description:
+
+- id: 14
+  name: deleted
+  description:
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/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index fbebb45e63ee1f85f4388d1ead81fa6b7403dd4f..00b20916567f6418d4c640ae0895c26b3499d242 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-26fd79dc5443f37779f9d2c4108058f4
+00c949a623b82848baaf3480b51307e3
diff --git a/include/upgrader/streams/core/0ca85857-86707325.patch.sql b/include/upgrader/streams/core/0ca85857-86707325.patch.sql
index 2962d23e40f284614bd877ee0317e1183f7a21bf..2daaf3b7cdab96ac80265205de51dbe6fc0926ef 100644
--- a/include/upgrader/streams/core/0ca85857-86707325.patch.sql
+++ b/include/upgrader/streams/core/0ca85857-86707325.patch.sql
@@ -17,10 +17,6 @@ CREATE TABLE `%TABLE_PREFIX%thread_referral` (
   KEY `thread_id` (`thread_id`)
 ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
-ALTER TABLE `%TABLE_PREFIX%thread_event`
-  CHANGE `state` `state` enum('created','closed','reopened','assigned','transferred', 'referred', 'overdue','edited','viewed','error','collab','resent', 'deleted') NOT NULL;
-
-
  -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
     SET `value` = '86707325fc571e56242fccc46fd24466'
diff --git a/include/upgrader/streams/core/26fd79dc-00c949a6.cleanup.sql b/include/upgrader/streams/core/26fd79dc-00c949a6.cleanup.sql
new file mode 100644
index 0000000000000000000000000000000000000000..50cad9d04f5cb4a2ee7b773c2841637a6659a2de
--- /dev/null
+++ b/include/upgrader/streams/core/26fd79dc-00c949a6.cleanup.sql
@@ -0,0 +1,3 @@
+-- Drop the state field from thread_events
+ALTER TABLE `%TABLE_PREFIX%thread_event`
+    DROP COLUMN `state`;
diff --git a/include/upgrader/streams/core/26fd79dc-00c949a6.patch.sql b/include/upgrader/streams/core/26fd79dc-00c949a6.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..774e248b961eee5cf30ee8459e3ca76496e27059
--- /dev/null
+++ b/include/upgrader/streams/core/26fd79dc-00c949a6.patch.sql
@@ -0,0 +1,45 @@
+/**
+* @signature 00c949a623b82848baaf3480b51307e3
+* @version v1.11.0
+* @title Database Optimization
+*
+* This patch is for optimizing our database to handle large amounts of data
+* more smoothly.
+*
+* 1. remove states in thread_event table and add them to their own event table
+*/
+
+-- Create a new table to store events
+CREATE TABLE `%TABLE_PREFIX%event` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(60) NOT NULL,
+  `description` varchar(60) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
+
+INSERT INTO `%TABLE_PREFIX%event` (`id`, `name`, `description`)
+VALUES
+	(1,'created',''),
+	(2,'closed',''),
+	(3,'reopened',''),
+	(4,'assigned',''),
+	(5,'released',''),
+	(6,'transferred',''),
+	(7,'referred',''),
+	(8,'overdue',''),
+	(9,'edited',''),
+	(10,'viewed',''),
+	(11,'error',''),
+	(12,'collab',''),
+	(13,'resent',''),
+	(14,'deleted','');
+
+-- Add event_id column to thread_events
+ALTER TABLE `%TABLE_PREFIX%thread_event`
+    ADD `event_id` int(11) unsigned AFTER `thread_id`;
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+   SET `value` = '00c949a623b82848baaf3480b51307e3', `updated` = NOW()
+   WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/26fd79dc-00c949a6.task.php b/include/upgrader/streams/core/26fd79dc-00c949a6.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..01895a67dc5f85e7cbb0df73d05a0f2599dba64a
--- /dev/null
+++ b/include/upgrader/streams/core/26fd79dc-00c949a6.task.php
@@ -0,0 +1,134 @@
+<?php
+
+class EventEnumRemoval extends MigrationTask {
+    var $description = "Remove the Enum 'state' field from ThreadEvents";
+    var $queue;
+    var $skipList;
+    var $errorList = array();
+    var $limit = 20000;
+
+    function sleep() {
+        return array('queue'=>$this->queue, 'skipList'=>$this->skipList);
+    }
+    function wakeup($stuff) {
+        $this->queue = $stuff['queue'];
+        $this->skipList = $stuff['skipList'];
+        while (!$this->isFinished())
+            $this->do_batch(30, $this->limit);
+    }
+
+    function run($max_time) {
+        $this->do_batch($max_time * 0.9, $this->limit);
+    }
+
+    function isFinished() {
+        return $this->getQueueLength() == 0;
+    }
+
+    function do_batch($time=30, $max=0) {
+        if(!$this->queueEvents($max) || !$this->getQueueLength())
+            return 0;
+
+        $this->setStatus("{$this->getQueueLength()} events remaining");
+
+        $count = 0;
+        $start = Misc::micro_time();
+        while ($this->getQueueLength() && (Misc::micro_time()-$start) < $time) {
+            if($this->next() && $max && ++$count>=$max) {
+                break;
+            }
+        }
+
+        return $this->queueEvents($max);
+    }
+
+    function queueEvents($limit=0){
+        global $cfg, $ost;
+
+        # Since the queue is persistent - we want to make sure we get to empty
+        # before we find more events.
+        if(($qc=$this->getQueueLength()))
+            return $qc;
+
+        $sql = "SELECT COUNT(t.id) FROM ".THREAD_EVENT_TABLE. " t
+            INNER JOIN ".EVENT_TABLE. " e ON (e.name=t.state)
+            WHERE t.event_id IS NULL";
+
+        //XXX: Do a hard fail or error querying the database?
+        if(!($res=db_query($sql)))
+            return $this->error('Unable to query DB for Thread Event migration!');
+
+        $count = db_result($res);
+
+        // Force the log message to the database
+        $ost->logDebug("Thread Event Migration", 'Found '.$count
+            .' events to migrate', true);
+
+        if($count == 0)
+            return 0;  //Nothing else to do!!
+
+        $start = db_result(db_query("SELECT id FROM ".THREAD_EVENT_TABLE. "
+            WHERE event_id IS NULL
+            ORDER BY id ASC LIMIT 1"));
+
+        $this->queue = array();
+        $info=array(
+            'count'        => $count,
+            'start'        => $start,
+            'end'          => $start + $limit
+        );
+        $this->enqueue($info);
+
+        return $this->getQueueLength();
+    }
+
+    function skip($eventId, $error) {
+        $this->skipList[] = $eventId;
+
+        return $this->error($error." (ID #$eventId)");
+    }
+
+    function error($what) {
+        global $ost;
+
+        $this->errors++;
+        $this->errorList[] = $what;
+        // Log the error but don't send the alert email
+        $ost->logError('Upgrader: Thread Event Migrater', $what, false);
+        # Assist in returning FALSE for inline returns with this method
+        return false;
+    }
+
+    function getErrors() {
+        return $this->errorList;
+    }
+
+    function getSkipList() {
+        return $this->skipList;
+    }
+
+    function enqueue($info) {
+        $this->queue[] = $info;
+    }
+
+    function getQueueLength() {
+        return count($this->queue);
+    }
+
+    function next() {
+        # Fetch next item -- use the last item so the array indices don't
+        # need to be recalculated for every shift() operation.
+        $info = array_pop($this->queue);
+
+        $sql = "UPDATE ".THREAD_EVENT_TABLE. " t
+            INNER JOIN ".EVENT_TABLE. " e ON (e.name=t.state)
+            SET t.event_id = e.id
+            WHERE t.event_id IS NULL AND t.id <= ". $info['end'];
+
+        db_query($sql);
+
+        return true;
+    }
+}
+return 'EventEnumRemoval';
+?>
diff --git a/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql b/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql
index f3bc7e0b201ea76bb59c6ff4de8f1ed272740d82..fd5953f5b6870f0cd53ffd5e42f8e36ea0019670 100644
--- a/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql
+++ b/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql
@@ -5,10 +5,6 @@
 *
 * This patch is for final revisions needed for v1.11
 */
-
-ALTER TABLE `%TABLE_PREFIX%thread_event`
-    CHANGE `state` `state` enum('created','closed','reopened','assigned', 'released', 'transferred', 'referred', 'overdue','edited','viewed','error','collab','resent', 'deleted') NOT NULL;
-
 ALTER TABLE `%TABLE_PREFIX%attachment`
     ADD INDEX `file_object` (`file_id`,`object_id`);
 
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);
 });
 
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index fbbb6e45050facca4ad2c6390657073d14b23c96..d023e83ca506ac49e41578256a0a7ead0cb41766 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -711,15 +711,24 @@ CREATE TABLE `%TABLE_PREFIX%lock` (
   KEY `staff_id` (`staff_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%event`;
+CREATE TABLE `%TABLE_PREFIX%event` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(60) NOT NULL,
+  `description` varchar(60) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_event`;
 CREATE TABLE `%TABLE_PREFIX%thread_event` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `thread_id` int(11) unsigned NOT NULL default '0',
+  `event_id` int(11) unsigned DEFAULT NULL,
   `staff_id` int(11) unsigned NOT NULL,
   `team_id` int(11) unsigned NOT NULL,
   `dept_id` int(11) unsigned NOT NULL,
   `topic_id` int(11) unsigned NOT NULL,
-  `state` enum('created','closed','reopened','assigned','released','transferred', 'referred', 'overdue','edited','viewed','error','collab','resent', 'deleted') NOT NULL,
   `data` varchar(1024) DEFAULT NULL COMMENT 'Encoded differences',
   `username` varchar(128) NOT NULL default 'SYSTEM',
   `uid` int(11) unsigned DEFAULT NULL,
@@ -727,8 +736,8 @@ CREATE TABLE `%TABLE_PREFIX%thread_event` (
   `annulled` tinyint(1) unsigned NOT NULL default '0',
   `timestamp` datetime NOT NULL,
   PRIMARY KEY (`id`),
-  KEY `ticket_state` (`thread_id`, `state`, `timestamp`),
-  KEY `ticket_stats` (`timestamp`, `state`)
+  KEY `ticket_state` (`thread_id`, `event_id`, `timestamp`),
+  KEY `ticket_stats` (`timestamp`, `event_id`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_referral`;