diff --git a/WHATSNEW.md b/WHATSNEW.md
index 9bbfbbb64682f294d5e07e842916fcbecbb20173..7bc3553b8031dd6bec76213c5daa8b1050114eea 100644
--- a/WHATSNEW.md
+++ b/WHATSNEW.md
@@ -1,19 +1,85 @@
+osTicket 1.11.0
+==================
+## Major New Features
+- Custom Columns/Custom Queues
+- Inline Edit
+- Ticket Referral
+- CC/BCC
+- Export Agent CSV
+- Department Access CSV
+- Archive Help Topics/Departments
+- Nested Knowledgebase Categories
+
+### Enhancements
+- Dashboard Statistics
+- Fix Vimeo iFrames
+- Fix randNumber()
+- Section Break Hint
+- List & Choice Searching (#3703, #3493, #2625)
+- Adds osTicket Favicons (#4112)
+- Fix Most Redactor Issues (#3849)
+- Send Login Errors Still Sends (#4073)
+- Private FAQs In Sidebar Search
+- User Password Reset (#4030)
+- Disabled & Private Help Topic (#3538)
+- Helpdesk Status Help Tip
+- Local Names In Validation Errors
+- User Registration Form (#4043)
+- Organization User List Pages Link (#4116)
+- Ticket Edit Internal Note (#4028)
+- Disable Canned Responses On New Ticket (#3971)
+- Canned Response Margin
+- Ticket Preview Custom Fields
+- Help Topic SLA (#3979)
+- Fix Agent Identity Masking (#2955, #3524)
+- Force Keys For Choice Field Options (#4071)
+- Check Missing Required Fields
+- Task Action Button Styling
+- Add Fullscreen To Embedded Videos
+- Fix Serbian Flag Icon (#3952)
+- Optimize Lock Table
+- Fix Outdated Alerts Link (#3935)
+- Fix Default Dept. Private Error (#3934)
+- Mailto TLD Length (#4063)
+- Remove Primary Contacts (#3903)
+- Fix Reset Button(s) (#3670)
+- Newsletter Link
+- Offline Page Images (#3869)
+- User Login Page Translation (#3860)
+- Translate Special Characters (#3842)
+- Custom Form Deletion (#3542, #4059)
+- Client Side Long FAQ Title (#3380)
+- Client FAQ Last Updated Time (#3475)
+- Email Banlist Sorting (#3452)
+- Fix New Ticket Cancel Button (#2624, #2881)
+- SQL Error Unknown column 'relevance' (#2655)
+- Fixes issue with last_update ticket variable
+- Ticket Notice Alert
+- Fix CSRF fail + shake effect (#3928, #3546)
+- Issue/ticket preview collabs
+- Allowing translation of copyrights in footers
+- User/Organization are not translated (#3650)
+- Fix DatePicker on client side (#3625, #3817, #3804, 0fbc09a)
+- Add Custom Forms to Ticket Filter Data
+- Fix for LDAP/AD auth plugin (#4198, #3460, #3544, #3549)
+
+
 osTicket v1.10.1
 ================
 ### Enhancements
-- Users: Support search by phone number
-- i18n: Fix getPrimaryLanguage() on non-object (#3799)
-- Add TimezoneField (#3786)
-- Chunk long text body (#3757, 7b68c994)
-- Spyc: convert hex strings to INTs under PHP 7 (#3621)
-- forms: Proper Field Deletion
-- Move orphaned tasks on department deletion to the default department (42e2c55a)
-- List: Save List Item Abbreviation (8513f137)
+* Users: Support search by phone number
+* i18n: Fix getPrimaryLanguage() on non-object (#3799)
+* Add TimezoneField (#3786)
+* Chunk long text body (#3757, 7b68c994)
+* Spyc: convert hex strings to INTs under PHP 7 (#3621)
+* forms: Proper Field Deletion
+* Move orphaned tasks on department deletion to the default department (42e2c55a)
+* List: Save List Item Abbreviation (8513f137)
 
 ### Performance and Security
-- XSS: Encode html entities of advanced search title (#3919)
-- XSS: Encode html entities of cached form data (#3960, bcd58e8)
-- ORM: Addresses an SQL injection vulnerability in ORM lookup function (#3959, 1eaa6910)
+* XSS: Encode html entities of advanced search title (#3919)
+* XSS: Encode html entities of cached form data (#3960, bcd58e8)
+* ORM: Addresses an SQL injection vulnerability in ORM lookup function (#3959, 1eaa6910)
 
 
 osTicket v1.10
diff --git a/bootstrap.php b/bootstrap.php
index 794613840f288b9f3dde80176777593f5c4243c2..4b64227a839c1e9cd864db6cc74a1930e9906f98 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -139,6 +139,7 @@ class Bootstrap {
         define('QUEUE_SORT_TABLE', $prefix.'queue_sort');
         define('QUEUE_SORTING_TABLE', $prefix.'queue_sorts');
         define('QUEUE_EXPORT_TABLE', $prefix.'queue_export');
+        define('QUEUE_CONFIG_TABLE', $prefix.'queue_config');
 
         define('API_KEY_TABLE',$prefix.'api_key');
         define('TIMEZONE_TABLE',$prefix.'timezone');
diff --git a/file.php b/file.php
index 33ffec5ff1cf038dbfb999b64ce8af5d1c1db3b7..994b77a0c2256a0218a27ffebb01b1c7d77a344b 100644
--- a/file.php
+++ b/file.php
@@ -26,21 +26,34 @@ if (!$_GET['key']
     Http::response(404, __('Unknown or invalid file'));
 }
 
-// Enforce security settings
-if ($cfg->isAuthRequiredForFiles() && !$thisclient) {
-    if (!($U = StaffAuthenticationBackend::getUser())) {
-        // Try and determine if a staff is viewing this page
-        if (strpos($_SERVER['HTTP_REFERRER'], ROOT_PATH .  'scp/') !== false) {
-            $_SESSION['_staff']['auth']['dest'] =
-                '/' . ltrim($_SERVER['REQUEST_URI'], '/');
-            Http::redirect(ROOT_PATH.'scp/login.php');
-        }
-        else {
-            require 'secure.inc.php';
-        }
+// Get the object type the file is attached to
+$type = '';
+if ($_GET['id']
+        && ($a=$file->attachments->findFirst(array(
+                    'id' => $_GET['id']))))
+    $type = $a->type;
+
+// Enforce security settings if enabled.
+if ($cfg->isAuthRequiredForFiles()
+        // FAQ & Page files allowed without login.
+        && !in_array($type, ['P', 'F'])
+        // Check user login
+        && !$thisuser
+        // Check staff login
+        && !StaffAuthenticationBackend::getUser()
+        ) {
+
+    // Try and determine if an agent is viewing the page / file
+    if (strpos($_SERVER['HTTP_REFERRER'], ROOT_PATH .  'scp/') !== false) {
+        $_SESSION['_staff']['auth']['dest'] =
+            '/' . ltrim($_SERVER['REQUEST_URI'], '/');
+        Http::redirect(ROOT_PATH.'scp/login.php');
+    } else {
+        require 'secure.inc.php';
     }
 }
 
+
 // Validate session access hash - we want to make sure the link is FRESH!
 // and the user has access to the parent ticket!!
 if ($file->verifySignature($_GET['signature'], $_GET['expires'])) {
diff --git a/include/ajax.draft.php b/include/ajax.draft.php
index e1bb78a0435b619f9bba99973b806543e6fd4a23..43e7f98b8651001ac5416b35c6f0563f84821f49 100644
--- a/include/ajax.draft.php
+++ b/include/ajax.draft.php
@@ -133,7 +133,8 @@ class DraftAjaxAPI extends AjaxController {
             'content_id' => 'cid:'.$f->getKey(),
             // Return draft_id to connect the auto draft creation
             'draft_id' => $draft->getId(),
-            'filelink' => $f->getDownloadUrl(false, 'inline'),
+            'filelink' => $f->getDownloadUrl(
+                ['type' => 'D', 'deposition' => 'inline']),
         ));
     }
 
@@ -339,14 +340,14 @@ class DraftAjaxAPI extends AjaxController {
             && ($object = $thread->getObject())
             && ($thisstaff->canAccess($object))
         ) {
-            $union = ' UNION SELECT f.id, a.`type`, a.`name` FROM '.THREAD_TABLE.' t
+            $union = ' UNION SELECT f.id, a.id as aid, a.`type`, a.`name` FROM '.THREAD_TABLE.' t
                 JOIN '.THREAD_ENTRY_TABLE.' th ON (th.thread_id = t.id)
                 JOIN '.ATTACHMENT_TABLE.' a ON (a.object_id = th.id AND a.`type` = \'H\')
                 JOIN '.FILE_TABLE.' f ON (a.file_id = f.id)
                 WHERE a.`inline` = 1 AND t.id='.db_input($_GET['threadId']);
         }
 
-        $sql = 'SELECT distinct f.id, COALESCE(a.type, f.ft), a.`name` FROM '.FILE_TABLE
+        $sql = 'SELECT distinct f.id, a.id as aid, COALESCE(a.type, f.ft), a.`name` FROM '.FILE_TABLE
             .' f LEFT JOIN '.ATTACHMENT_TABLE.' a ON (a.file_id = f.id)
             WHERE ((a.`type` IN (\'C\', \'F\', \'T\', \'P\') AND a.`inline` = 1) OR f.ft = \'L\')'
                 .' AND f.`type` LIKE \'image/%\'';
@@ -354,9 +355,11 @@ class DraftAjaxAPI extends AjaxController {
             Http::response(500, 'Unable to lookup files');
 
         $files = array();
-        while (list($id, $type, $name) = db_fetch_row($res)) {
-            $f = AttachmentFile::lookup((int) $id);
-            $url = $f->getDownloadUrl();
+        while (list($id, $aid, $type, $name) = db_fetch_row($res)) {
+            if (!($f = AttachmentFile::lookup((int) $id)))
+                continue;
+
+            $url = $f->getDownloadUrl(['id' => $aid]);
             $files[] = array(
                 // Don't send special sizing for thread items 'cause they
                 // should be cached already by the client
diff --git a/include/ajax.forms.php b/include/ajax.forms.php
index 41506c872f076bcc66d8582e1ef38bb4a462e4c0..9ca601e33020d9c0f3ea1e669bbc4d405217df5e 100644
--- a/include/ajax.forms.php
+++ b/include/ajax.forms.php
@@ -15,6 +15,9 @@ class DynamicFormsAjaxAPI extends AjaxController {
     }
 
     function getFormsForHelpTopic($topic_id, $client=false) {
+        if (!$_SERVER['HTTP_REFERER'])
+            Http::response(403, 'Forbidden.');
+
         if (!($topic = Topic::lookup($topic_id)))
             Http::response(404, 'No such help topic');
 
diff --git a/include/ajax.search.php b/include/ajax.search.php
index 80ebd621ca61503df97026c1f196d8c7e199ba87..d8ce8dbab1b4ef7443b5a3b664cdac63f3ce4425 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -28,8 +28,9 @@ class SearchAjaxAPI extends AjaxController {
         if (!$thisstaff)
             Http::response(403, 'Agent login required');
 
-        $search = new SavedSearch(array(
+        $search = new AdhocSearch(array(
             'root' => 'T',
+            'staff_id' => $thisstaff->getId(),
             'parent_id' => @$_GET['parent_id'] ?: 0,
         ));
         if ($search->parent_id) {
@@ -39,7 +40,7 @@ class SearchAjaxAPI extends AjaxController {
         if (isset($_SESSION[$context]) && $key && $_SESSION[$context][$key])
             $search->config = $_SESSION[$context][$key];
 
-        $this->_tryAgain($search, $search->getForm());
+        $this->_tryAgain($search);
     }
 
     function editSearch($id) {
@@ -51,7 +52,7 @@ class SearchAjaxAPI extends AjaxController {
         elseif (!$search || !$search->checkAccess($thisstaff))
             Http::response(404, 'No such saved search');
 
-        $this->_tryAgain($search, $search->getForm());
+        $this->_tryAgain($search);
     }
 
     function addField($name) {
@@ -60,7 +61,9 @@ class SearchAjaxAPI extends AjaxController {
         if (!$thisstaff)
             Http::response(403, 'Agent login required');
 
-        $search = new SavedSearch(array('root'=>'T'));
+        $search = new SavedSearch(array(
+                    'root'=>'T'
+                    ));
         $searchable = $search->getSupportedMatches();
         if (!($F = $searchable[$name]))
             Http::response(404, 'No such field: ', print_r($name, true));
@@ -82,7 +85,15 @@ class SearchAjaxAPI extends AjaxController {
     }
 
     function doSearch() {
-        $search = new SavedSearch(array('root' => 'T'));
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login is required');
+
+        $search = new AdhocSearch(array(
+                    'root' => 'T',
+                    'staff_id' => $thisstaff->getId()));
+
         $form = $search->getForm($_POST);
         if (false === $this->_setupSearch($search, $form)) {
             return;
@@ -139,11 +150,28 @@ class SearchAjaxAPI extends AjaxController {
             -$size);
     }
 
-    function _tryAgain($search, $form, $errors=array(), $info=array()) {
-        $matches = $search->getSupportedMatches();
+    function _tryAgain($search, $form=null, $errors=array(), $info=array()) {
+        if (!$form)
+            $form = $search->getForm();
         include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
     }
 
+    function createSearch() {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login is required');
+
+
+        $search = SavedSearch::create(array(
+                    'title' => __('Add Queue'),
+                    'root' => 'T',
+                    'staff_id' => $thisstaff->getId(),
+                    'parent_id' =>  $_GET['pid'],
+                    ));
+        $this->_tryAgain($search);
+    }
+
     function saveSearch($id=0) {
         global $thisstaff;
 
@@ -151,15 +179,16 @@ class SearchAjaxAPI extends AjaxController {
             Http::response(403, 'Agent login is required');
 
         if ($id) { //  update
-            $search = SavedSearch::lookup($id);
+            if (!($search = SavedSearch::lookup($id))
+                    || !$search->checkAccess($thisstaff))
+                Http::response(404, 'No such saved search');
         } else { // new search
-            $search = SavedSearch::create(array('root' => 'T'));
-            $search->staff_id = $thisstaff->getId();
+            $search = SavedSearch::create(array(
+                        'root' => 'T',
+                        'staff_id' => $thisstaff->getId()
+                        ));
         }
 
-        if (!$search || !$search->checkAccess($thisstaff))
-            Http::response(404, 'No such saved search');
-
         if (false === $this->_saveSearch($search))
             return;
 
@@ -169,16 +198,23 @@ class SearchAjaxAPI extends AjaxController {
                     $id ? __('updated') : __('created'),
                     __('successfully')),
                 );
-
-        $this->_tryAgain($search, $search->getForm(), null, $info);
+        $this->_tryAgain($search, null, null, $info);
     }
 
     function _saveSearch(SavedSearch $search) {
+
+        // Validate the form.
         $form = $search->getForm($_POST);
+        if ($this->_hasErrors($search, $form))
+            return false;
+
         $errors = array();
         if (!$search->update($_POST, $errors)
-            || !$search->save(true)
-        ) {
+                || !$search->save(true)) {
+
+            $form->addError(sprintf(
+                        __('Unable to update %s. Correct error(s) below and try again.'),
+                        __('queue')));
             $this->_tryAgain($search, $form, $errors);
             return false;
         }
@@ -352,43 +388,15 @@ class SearchAjaxAPI extends AjaxController {
     function collectQueueCounts($ids=null) {
         global $thisstaff;
 
-        if (!$thisstaff) {
+        if (!$thisstaff)
             Http::response(403, 'Agent login is required');
-        }
-
-        $queues = CustomQueue::objects()
-            ->filter(Q::any(array(
-                'flags__hasbit' => CustomQueue::FLAG_PUBLIC,
-                'staff_id' => $thisstaff->getId(),
-            )));
 
+        $criteria = array();
         if ($ids && is_array($ids))
-            $queues->filter(array('id__in' => $ids));
-
-        $query = Ticket::objects();
-
-        // Visibility contraints ------------------
-        // TODO: Consider SavedSearch::ignoreVisibilityConstraints()
-        $visibility = $thisstaff->getTicketsVisibility();
-        $query->filter($visibility);
-        foreach ($queues as $queue) {
-            $Q = $queue->getBasicQuery();
-            if (count($Q->extra) || $Q->isWindowed()) {
-                // XXX: This doesn't work
-                $query->annotate(array(
-                    'q'.$queue->id => $Q->values_flat()
-                        ->aggregate(array('count' => SqlAggregate::COUNT('ticket_id')))
-                ));
-            }
-            else {
-                $expr = SqlCase::N()->when(new SqlExpr(new Q($Q->constraints)), new SqlField('ticket_id'));
-                $query->aggregate(array(
-                    'q'.$queue->id => SqlAggregate::COUNT($expr, true)
-                ));
-            }
-        }
+            $criteria = array('id__in' => $ids);
 
+        $counts = SavedQueue::ticketsCount($thisstaff, $criteria, 'q');
         Http::response(200, false, 'application/json');
-        return $this->encode($query->values()->one());
+        return $this->encode($counts);
     }
 }
diff --git a/include/ajax.thread.php b/include/ajax.thread.php
index a66062e0b933607397803545f98c384d46b5b53c..0369548ed5deb1b855c6f2f9c3127d4eb9b4ff22 100644
--- a/include/ajax.thread.php
+++ b/include/ajax.thread.php
@@ -74,11 +74,11 @@ class ThreadAjaxAPI extends AjaxController {
         if (!$user_info)
             $info['error'] = __('Unable to find user in directory');
 
-        return self::_addcollaborator($thread, null, $form, $info);
+        return self::_addcollaborator($thread, null, $form, 'addcc', $info);
     }
 
     //Collaborators utils
-    function addCollaborator($tid, $uid=0) {
+    function addCollaborator($tid, $type=null, $uid=0) {
         global $thisstaff;
 
         if (!($thread=Thread::lookup($tid))
@@ -90,7 +90,7 @@ class ThreadAjaxAPI extends AjaxController {
 
         //If not a post then assume new collaborator form
         if(!$_POST)
-            return self::_addcollaborator($thread, $user);
+            return self::_addcollaborator($thread, $user, null, $type);
 
         $user = $form = null;
         if (isset($_POST['id']) && $_POST['id']) { //Existing user/
@@ -107,7 +107,7 @@ class ThreadAjaxAPI extends AjaxController {
                             array(), $errors))) {
                 $info = array('msg' => sprintf(__('%s added as a collaborator'),
                             Format::htmlchars($c->getName())));
-                $c->setCc();
+                $type == 'addbcc' ? $c->setBcc() : $c->setCc();
                 $c->save();
                 return self::_collaborators($thread, $info);
             }
@@ -119,7 +119,7 @@ class ThreadAjaxAPI extends AjaxController {
             $info +=array('error' =>__('Unable to add collaborator.').' '.__('Internal error occurred'));
         }
 
-        return self::_addcollaborator($thread, $user, $form, $info);
+        return self::_addcollaborator($thread, $user, $form, $type, $info);
     }
 
     function updateCollaborator($tid, $cid) {
@@ -194,15 +194,15 @@ class ThreadAjaxAPI extends AjaxController {
         return $resp;
     }
 
-    function _addcollaborator($thread, $user=null, $form=null, $info=array()) {
+    function _addcollaborator($thread, $user=null, $form=null, $type=null, $info=array()) {
         global $thisstaff;
 
         $info += array(
                     'title' => __('Add a collaborator'),
-                    'action' => sprintf('#thread/%d/add-collaborator',
-                        $thread->getId()),
-                    'onselect' => sprintf('ajax.php/thread/%d/add-collaborator/',
-                        $thread->getId()),
+                    'action' => sprintf('#thread/%d/add-collaborator/%s',
+                        $thread->getId(), $type),
+                    'onselect' => sprintf('ajax.php/thread/%d/add-collaborator/%s/',
+                        $thread->getId(), $type),
                     );
 
         ob_start();
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 3f53acd180bb79283d90de1c7766e674ec20c55b..6825556a98ad07028029130e195d7eaa3d7447d5 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -523,6 +523,10 @@ function refer($tid, $target=null) {
                           __($field->getLabel())
                           )
                       );
+
+              $impl = $field->getImpl();
+              if ($impl instanceof FileUploadField)
+                  $field->save();
               Http::response(201, $field->getClean());
           }
           $form->addErrors($errors);
@@ -938,7 +942,7 @@ function refer($tid, $target=null) {
                 || !$ticket->checkStaffPerm($thisstaff))
             Http::response(404, 'Unknown ticket #');
 
-        $role = $thisstaff->getRole($ticket->getDeptId());
+        $role = $ticket->getRole($thisstaff);
 
         $info = array();
         $state = null;
@@ -991,7 +995,7 @@ function refer($tid, $target=null) {
         elseif ($status->getId() == $ticket->getStatusId())
             $errors['err'] = sprintf(__('Ticket already set to %s status'),
                     __($status->getName()));
-        elseif (($role = $thisstaff->getRole($ticket->getDeptId()))) {
+        elseif (($role = $ticket->getRole($thisstaff))) {
             // Make sure the agent has permission to set the status
             switch(mb_strtolower($status->getState())) {
                 case 'open':
diff --git a/include/ajax.users.php b/include/ajax.users.php
index d42349924e89430df12309c7159b1c7eba839fa6..328b93cf0e1d1167797ad29fcda7a0d6a1b8da8b 100644
--- a/include/ajax.users.php
+++ b/include/ajax.users.php
@@ -73,12 +73,16 @@ class UsersAjaxAPI extends AjaxController {
                     return $this->search($type, $fulltext);
                 }
             } else {
-                $users->filter(Q::any(array(
+                $filter = Q::any(array(
                     'emails__address__contains' => $q,
                     'name__contains' => $q,
                     'org__name__contains' => $q,
-                    'cdata__phone__contains' => $q,
-                )));
+                    'account__username__contains' => $q,
+                ));
+                if (UserForm::getInstance()->getField('phone'))
+                    $filter->add(array('cdata__phone__contains' => $q));
+
+                $users->filter($filter);
             }
 
             // Omit already-imported remote users
diff --git a/include/api.tickets.php b/include/api.tickets.php
index e2ee066aefff68921a26dc4bfbd28b62008b7a8e..c209086a9d57a682462f2d2966c5fd41db69b176 100644
--- a/include/api.tickets.php
+++ b/include/api.tickets.php
@@ -40,7 +40,7 @@ class TicketApiController extends ApiController {
         if(!strcasecmp($format, 'email')) {
             $supported = array_merge($supported, array('header', 'mid',
                 'emailId', 'to-email-id', 'ticketId', 'reply-to', 'reply-to-name',
-                'in-reply-to', 'references', 'thread-type',
+                'in-reply-to', 'references', 'thread-type', 'system_emails',
                 'mailflags' => array('bounce', 'auto-reply', 'spam', 'viral'),
                 'recipients' => array('*' => array('name', 'email', 'source'))
                 ));
diff --git a/include/class.category.php b/include/class.category.php
index 3eeedfd144b09721f4004a31a3aa26c9be2e5108..c4b1ff0d1ab8f2d2c40db18c8ff5fc5980facccc 100644
--- a/include/class.category.php
+++ b/include/class.category.php
@@ -55,7 +55,9 @@ class Category extends VerySimpleModel {
         return $count;
     }
     function getDescription() { return $this->description; }
-    function getDescriptionWithImages() { return Format::viewableImages($this->description); }
+    function getDescriptionWithImages() {
+        return Format::viewableImages($this->description);
+    }
     function getNotes() { return $this->notes; }
     function getCreateDate() { return $this->created; }
     function getUpdateDate() { return $this->updated; }
diff --git a/include/class.dept.php b/include/class.dept.php
index a1a87164f924da3965f1ff77be5e31cbaf4fc4ed..4eb0b8e2229d906bbce2031f6690c8d54af23778 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -107,7 +107,7 @@ implements TemplateVariable, Searchable {
             'name' => new TextboxField(array(
                 'label' => __('Name'),
             )),
-            'manager' => new AgentSelectionField(array(
+            'manager' => new DepartmentManagerSelectionField(array(
                 'label' => __('Manager'),
             )),
         );
diff --git a/include/class.export.php b/include/class.export.php
index 6a25afc4fe760931e1b41f72689cd0063c45e883..3a47bcc942e358d51fa6615bbd9d065a887182ba 100644
--- a/include/class.export.php
+++ b/include/class.export.php
@@ -390,7 +390,7 @@ class ResultSetExporter {
     function dump() {
         # Useful for debug output
         while ($row=$this->nextArray()) {
-            var_dump($row);
+            var_dump($row); //nolint
         }
     }
 }
diff --git a/include/class.faq.php b/include/class.faq.php
index 4eb1c2d83df46c4953ed0712c1cb03c5a8d913ba..555fd3aee7f6f01fb485259e338c13c394dc826b 100644
--- a/include/class.faq.php
+++ b/include/class.faq.php
@@ -74,7 +74,7 @@ class FAQ extends VerySimpleModel {
     function getQuestion() { return $this->question; }
     function getAnswer() { return $this->answer; }
     function getAnswerWithImages() {
-        return Format::viewableImages($this->answer);
+        return Format::viewableImages($this->answer, ['type' => 'F']);
     }
     function getTeaser() {
         return Format::truncate(Format::striptags($this->answer), 150);
@@ -194,7 +194,8 @@ class FAQ extends VerySimpleModel {
         return $this->_getLocal('answer', $lang);
     }
     function getLocalAnswerWithImages($lang=false) {
-        return Format::viewableImages($this->getLocalAnswer($lang));
+        return Format::viewableImages($this->getLocalAnswer($lang),
+                ['type' => 'F']);
     }
     function _getLocal($what, $lang=false) {
         if (!$lang) {
diff --git a/include/class.file.php b/include/class.file.php
index bcdb9e0ed68f0ce7a944a9ae866ea5626dd4b90b..d880840861b09065a34cd275ebb726773d82e105 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -184,25 +184,31 @@ class AttachmentFile extends VerySimpleModel {
         exit();
     }
 
-    function getDownloadUrl($minage=false, $disposition=false, $handler=false) {
-        // XXX: Drop this when AttachmentFile goes to ORM
+    function getDownloadUrl($options=array()) {
+        // Add attachment ref id if object type is set
+        if (isset($options['type'])
+                && !isset($options['id'])
+                && ($a=$this->attachments->findFirst(array(
+                            'type' => $options['type']))))
+            $options['id'] = $a->getId();
+
         return static::generateDownloadUrl($this->getId(),
-            strtolower($this->getKey()), $this->getSignature(), $minage,
-            $disposition, $handler);
+                strtolower($this->getKey()), $this->getSignature(),
+                $options);
     }
 
-    static function generateDownloadUrl($id, $key, $hash, $minage=false,
-        $disposition=false, $handler=false
-    ) {
-        // Expire at the nearest midnight, allowing at least 12 hours access
-        $minage = $minage ?: 43200;
-        $gmnow = Misc::gmtime() + $minage;
+    static function generateDownloadUrl($id, $key, $hash, $options = array()) {
+
+        // Expire at the nearest midnight, allow at least12 hrs access
+        $minage = @$options['minage'] ?: 43200;
+        $gmnow = Misc::gmtime() +  $options['minage'];
         $expires = $gmnow + 86400 - ($gmnow % 86400);
 
         // Generate a signature based on secret content
         $signature = static::_genUrlSignature($id, $key, $hash, $expires);
 
-        $handler = $handler ?: ROOT_PATH . 'file.php';
+        // Handler / base url
+        $handler = @$options['handler'] ?: ROOT_PATH . 'file.php';
 
         // Return sanitized query string
         $args = array(
@@ -211,10 +217,13 @@ class AttachmentFile extends VerySimpleModel {
             'signature' => $signature,
         );
 
-        if ($disposition)
-            $args['disposition'] = $disposition;
+        if (isset($options['disposition']))
+            $args['disposition'] =  $options['disposition'];
+
+        if (isset($options['id']))
+            $args['id'] =  $options['id'];
 
-        return $handler . '?' . http_build_query($args);
+        return sprintf('%s?%s', $handler, http_build_query($args));
     }
 
     function verifySignature($signature, $expires) {
@@ -635,7 +644,7 @@ class AttachmentFile extends VerySimpleModel {
             ->filter(array(
                 'attachments__object_id__isnull' => true,
                 'ft' => 'T',
-                'created__gt' => new DateTime('now -1 day'),
+                'created__lt' => SqlFunction::NOW()->minus(SqlInterval::DAY(1)),
             ));
 
         foreach ($files as $f) {
diff --git a/include/class.filter.php b/include/class.filter.php
index 7178aece5eba1d0540020b9898f2602bdcdd55df..1827342284a3bab796bc1db2244dd53c527e0e0b 100644
--- a/include/class.filter.php
+++ b/include/class.filter.php
@@ -471,20 +471,28 @@ class Filter {
     }
 
     function save($id,$vars,&$errors) {
+      //get current filter actions (they're validated before saving)
+      self::save_actions($id, $vars, $errors);
+
       if ($this) {
         foreach ($this->getActions() as $A) {
-          if ($A->type == 'dept')
-              $dept = Dept::lookup($A->parseConfiguration($vars)['dept_id']);
-
-          if ($A->type == 'topic')
-              $topic = Topic::lookup($A->parseConfiguration($vars)['topic_id']);
+          $config = JsonDataParser::parse($A->configuration);
+          if ($A->type == 'dept') {
+            $dept = Dept::lookup($config['dept_id']);
+            $dept_action = $A->getId();
+          }
+
+          if ($A->type == 'topic') {
+            $topic = Topic::lookup($config['topic_id']);
+            $topic_action = $A->getId();
+          }
         }
       }
 
-      if($dept && !$dept->isActive())
+      if($dept && !$dept->isActive() && (is_array($vars['actions']) && !in_array('D' . $dept_action,$vars['actions'])))
         $errors['err'] = sprintf(__('%s selected for %s must be active'), __('Department'), __('Filter Action'));
 
-      if($topic && !$topic->isActive())
+      if($topic && !$topic->isActive() && (is_array($vars['actions']) && !in_array('D' . $topic_action,$vars['actions'])))
         $errors['err'] = sprintf(__('%s selected for %s must be active'), __('Help Topic'), __('Filter Action'));
 
         if(!$vars['execorder'])
@@ -522,7 +530,7 @@ class Filter {
             .',execorder='.db_input($vars['execorder'])
             .',email_id='.db_input($emailId)
             .',match_all_rules='.db_input($vars['match_all_rules'])
-            .',stop_onmatch='.db_input(isset($vars['stop_onmatch'])?1:0)
+            .',stop_onmatch='.db_input($vars['stop_onmatch'])
             .',notes='.db_input(Format::sanitize($vars['notes']));
 
         if($id) {
@@ -543,7 +551,6 @@ class Filter {
         # Don't care about errors stashed in $xerrors
         $xerrors = array();
         self::save_rules($id,$vars,$xerrors);
-        self::save_actions($id, $vars, $errors);
 
         return count($errors) == 0;
     }
@@ -551,20 +558,31 @@ class Filter {
     function validate_actions($action) {
       $errors = array();
       $config = json_decode($action->ht['configuration'], true);
-      if ($action->ht['type'] == 'dept') {
-        $dept = Dept::lookup($config['dept_id']);
-        if (!$dept || !$dept->isActive()) {
-          $errors['err'] = sprintf(__('Unable to save: Please choose an active %s'), 'Department');
-          return $errors;
-        }
-      }
-
-      if ($action->ht['type'] == 'topic') {
-        $topic = Topic::lookup($config['topic_id']);
-        if (!$topic || !$topic->isActive()) {
-          $errors['err'] = sprintf(__('Unable to save: Please choose an active %s'), 'Help Topic');
-          return $errors;
-        }
+      switch ($action->ht['type']) {
+        case 'dept':
+          $dept = Dept::lookup($config['dept_id']);
+          if (!$dept || !$dept->isActive()) {
+            $errors['err'] = sprintf(__('Unable to save: Please choose an active %s'), 'Department');
+            return $errors;
+          }
+          break;
+
+        case 'topic':
+          $topic = Topic::lookup($config['topic_id']);
+          if (!$topic || !$topic->isActive()) {
+            $errors['err'] = sprintf(__('Unable to save: Please choose an active %s'), 'Help Topic');
+            return $errors;
+          }
+          break;
+
+        default:
+          foreach ($config as $key => $value) {
+            if (!$value) {
+              $errors['err'] = sprintf(__('Unable to save: Please insert a value for %s'), ucfirst($action->ht['type']));
+              return $errors;
+            }
+          }
+          break;
       }
 
       return false;
@@ -593,7 +611,6 @@ class Filter {
                     'sort' => (int) $sort,
                 ));
                 $I->setConfiguration($errors, $vars);
-                $config = json_decode($I->ht['configuration'], true);
 
                 $invalid = self::validate_actions($I);
                 if ($invalid) {
@@ -607,8 +624,6 @@ class Filter {
                 if ($I = FilterAction::lookup($info)) {
                     $I->setConfiguration($errors, $vars);
 
-                    $config = json_decode($I->ht['configuration'], true);
-
                     $invalid = self::validate_actions($I);
                     if ($invalid) {
                       $errors['err'] = sprintf($invalid['err']);
diff --git a/include/class.filter_action.php b/include/class.filter_action.php
index c963e355c1ab0905f49d10dcf6926718851a8c71..59bafbf57ebfe40205adbfe2dbecd60b33525083 100644
--- a/include/class.filter_action.php
+++ b/include/class.filter_action.php
@@ -41,8 +41,7 @@ class FilterAction extends VerySimpleModel {
         return $this->_config;
     }
 
-    function parseConfiguration($source, &$errors=array())
-    {
+    function parseConfiguration($source, &$errors=array()) {
       if (!$source)
         return $this->getConfiguration();
 
diff --git a/include/class.format.php b/include/class.format.php
index 50c8a7fdf144c12d23f9f59ff4c1b3dc87cd4609..2463271c875b24e85b6a50039690096cddfb2127 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -306,8 +306,10 @@ class Format {
                   ':<!DOCTYPE[^>]+>:',          # <!DOCTYPE ... >
                   ':<\?[^>]+>:',                # <?xml version="1.0" ... >
                   ':<html[^>]+:i',              # drop html attributes
+                  ':<(a|span) (name|style)="(mso-bookmark\:)?_MailEndCompose">(.+)?<\/(a|span)>:', # Drop _MailEndCompose
+                  ':<div dir=(3D)?"ltr">(.*?)<\/div>(.*):is', # drop Gmail "ltr" attributes
             ),
-            array('', '', '', '', '<html'),
+            array('', '', '', '', '<html', '$4', '$2 $3'),
             $html);
 
         // HtmLawed specific config only
@@ -455,14 +457,17 @@ class Format {
     }
 
 
-    function viewableImages($html, $script=false) {
+    function viewableImages($html, $options=array()) {
         $cids = $images = array();
+        $options +=array(
+                'deposition' => 'inline');
         return preg_replace_callback('/"cid:([\w._-]{32})"/',
-        function($match) use ($script, $images) {
+        function($match) use ($options, $images) {
             if (!($file = AttachmentFile::lookup($match[1])))
                 return $match[0];
+
             return sprintf('"%s" data-cid="%s"',
-                $file->getDownloadUrl(false, 'inline', $script), $match[1]);
+                $file->getDownloadUrl($options), $match[1]);
         }, $html);
     }
 
diff --git a/include/class.forms.php b/include/class.forms.php
index 2f8582855c64596e1f364476b4a449f0b0185d04..9ef27efb5e8385b313159ef4505edd625ffa14b0 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -1650,6 +1650,15 @@ class BooleanField extends FormField {
         );
     }
 
+    function describeSearchMethod($method) {
+
+        $methods = $this->get('descsearchmethods');
+        if (isset($methods[$method]))
+            return $methods[$method];
+
+        return parent::describeSearchMethod($method);
+    }
+
     function getSearchMethodWidgets() {
         return array(
             'set' => null,
@@ -2690,15 +2699,12 @@ class DepartmentField extends ChoiceField {
           else {
             $current_id = $selected->value;
             $current_name = Dept::getNameById($current_id);
-            $addNew = true;
           }
         }
 
-        $active_depts = array();
-        if($current_id)
-          $active_depts = Dept::objects()
-            ->filter(array('flags__hasbit' => Dept::FLAG_ACTIVE))
-            ->values('id', 'name');
+        $active_depts = Dept::objects()
+          ->filter(array('flags__hasbit' => Dept::FLAG_ACTIVE))
+          ->values('id', 'name');
 
         $choices = array();
         if ($depts = Dept::getDepartments(null, true, Dept::DISPLAY_DISABLED)) {
@@ -2708,18 +2714,17 @@ class DepartmentField extends ChoiceField {
             $active[$dept['id']] = $dept['name'];
 
           //add selected dept to list
-          $active[$current_id] = $current_name;
-
+          if($current_id)
+            $active[$current_id] = $current_name;
+          else
+            return $active;
 
           foreach ($depts as $id => $name) {
             $choices[$id] = $name;
             if(!array_key_exists($id, $active) && $current_id)
-              unset($choices[$id]);
+                unset($choices[$id]);
           }
-
         }
-        if($addNew)
-          $choices[':new:'] = '— '.__('Add New').' —';
 
         return $choices;
     }
@@ -3933,6 +3938,8 @@ class ChoicesWidget extends Widget {
                         $values[$v] = $choices[$v];
                     elseif (($i=$this->field->lookupChoice($v)))
                         $values += $i;
+                    elseif (!$k && $v)
+                      return $v;
                 }
             }
         }
@@ -4513,13 +4520,15 @@ class FreeTextWidget extends Widget {
         if (($attachments = $this->field->getFiles()) && count($attachments)) { ?>
             <section class="freetext-files">
             <div class="title"><?php echo __('Related Resources'); ?></div>
-            <?php foreach ($attachments as $attach) { ?>
+            <?php foreach ($attachments as $attach) {
+                $filename = Format::htmlchars($attach->getFilename());
+                ?>
                 <div class="file">
                 <a href="<?php echo $attach->file->getDownloadUrl(); ?>"
-                    target="_blank" download="<?php echo $attach->file->getDownloadUrl();
-                    ?>" class="truncate no-pjax">
+                    target="_blank" download="<?php echo $filename; ?>"
+                    class="truncate no-pjax">
                     <i class="icon-file"></i>
-                    <?php echo Format::htmlchars($attach->getFilename()); ?>
+                    <?php echo $filename; ?>
                 </a>
                 </div>
             <?php } ?>
@@ -4914,7 +4923,7 @@ class ReferralForm extends Form {
                               ),
                             )
                 ),
-            'dept' => new ChoiceField(array(
+            'dept' => new DepartmentField(array(
                     'id'=>4,
                     'label' => '',
                     'flags' => hexdec(0X450F3),
diff --git a/include/class.json.php b/include/class.json.php
index ad5ac65c12e3da2c939faecdb67dfec3f049c635..66edb7623e4944cc4ab30b763740993294652f5a 100644
--- a/include/class.json.php
+++ b/include/class.json.php
@@ -21,25 +21,39 @@
 include_once "JSON.php";
 
 class JsonDataParser {
-    function parse($stream) {
+    function parse($stream, $tidy=false) {
         if (is_resource($stream)) {
             $contents = '';
             while (!feof($stream))
                 $contents .= fread($stream, 8192);
         } else
             $contents = $stream;
+
+        if ($contents && $tidy)
+            $contents = self::tidy($contents);
+
         return self::decode($contents);
     }
 
-    function decode($contents) {
-        if (function_exists("json_decode")) {
-            return json_decode($contents, true);
-        } else {
-            # Create associative arrays rather than 'objects'
-            $decoder = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-            return $decoder->decode($contents);
-        }
+    static function decode($contents, $assoc=true) {
+        if (function_exists("json_decode"))
+            return json_decode($contents, $assoc);
+
+        $decoder = new Services_JSON($assoc ? SERVICES_JSON_LOOSE_TYPE : 0);
+        return $decoder->decode($contents);
     }
+
+    static function tidy($content) {
+
+        // Clean up doubly quoted JSON
+        $content = str_replace(
+                array(':"{', '}"', '\"'),
+                array(':{', '}', '"'),
+                $content);
+        // return trimmed content.
+        return trim($content);
+    }
+
     function lastError() {
         if (function_exists("json_last_error")) {
             $errors = array(
diff --git a/include/class.orm.php b/include/class.orm.php
index 08c2f209c324ab32f7272435d29e6855abfa3e64..acb94853467f21c5442447029bc9b3f354d2b3ae 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1737,7 +1737,8 @@ implements ArrayAccess {
     function sort($key=false, $reverse=false) {
         // Fetch all records into the cache
         $this->asArray();
-        return parent::sort($key, $reverse);
+        parent::sort($key, $reverse);
+        return $this;
     }
 
     /**
diff --git a/include/class.page.php b/include/class.page.php
index 3ea5ca0f98839dc5fcdc4638c5de515b492bc245..a430fc2e66cd20da6b5a1711b1739094bbc8fdbc 100644
--- a/include/class.page.php
+++ b/include/class.page.php
@@ -70,7 +70,7 @@ class Page extends VerySimpleModel {
         return $this->_getLocal('body', $lang);
     }
     function getBodyWithImages() {
-        return Format::viewableImages($this->getLocalBody());
+        return Format::viewableImages($this->getLocalBody(), ['type' => 'P']);
     }
 
     function _getLocal($what, $lang=false) {
diff --git a/include/class.pdf.php b/include/class.pdf.php
index c12d071e82dd48058513fad2d1a89dcf5efb25a7..cc395f388999d1230b0da195747e9a9990ee22df 100644
--- a/include/class.pdf.php
+++ b/include/class.pdf.php
@@ -45,6 +45,10 @@ class mPDFWithLocalImages extends mPDF {
         );
         return call_user_func_array(array('parent', 'WriteHtml'), $args);
     }
+
+    function output($name, $dest) {
+        return parent::Output($name, $dest);
+    }
 }
 
 class Ticket2PDF extends mPDFWithLocalImages
diff --git a/include/class.queue.php b/include/class.queue.php
index b037d4eee0769d5e7ad4d291e1cc5e24d1b9537e..189a7a84654a2bda283c60e0a49eae443ab142e8 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -28,6 +28,7 @@ class CustomQueue extends VerySimpleModel {
             ),
             'columns' => array(
                 'reverse' => 'QueueColumnGlue.queue',
+                'constrain' => array('staff_id' =>'QueueColumnGlue.staff_id'),
                 'broker' => 'QueueColumnListBroker',
             ),
             'sorts' => array(
@@ -111,6 +112,10 @@ class CustomQueue extends VerySimpleModel {
         return $this->path ?: $this->buildPath();
     }
 
+    function criteriaRequired() {
+        return true;
+    }
+
     function getCriteria($include_parent=false) {
         if (!isset($this->criteria)) {
             $old = @$this->config[0] === '{';
@@ -123,7 +128,9 @@ class CustomQueue extends VerySimpleModel {
                 && !isset($this->criteria['conditions'])
             ) {
                 // TODO: Upgrade old ORM path names
-                $this->criteria = $this->isolateCriteria($this->criteria);
+                // Parse criteria out of JSON if any.
+                $this->criteria = self::isolateCriteria($this->criteria,
+                        $this->getRoot());
             }
         }
         $criteria = $this->criteria ?: array();
@@ -162,38 +169,40 @@ class CustomQueue extends VerySimpleModel {
      * Parameters:
      * $search - <array> Request parameters ($_POST) used to update the
      *      search beyond the current configuration of the search criteria
+     * $searchables - search fields - default to current if not provided
      */
-    function getForm($source=null) {
-        $searchable = $this->getCurrentSearchFields($source);
-        $fields = array(
-            ':keywords' => new TextboxField(array(
-                'id' => 3001,
-                'configuration' => array(
-                    'size' => 40,
-                    'length' => 400,
-                    'autofocus' => true,
-                    'classes' => 'full-width headline',
-                    'placeholder' => __('Keywords — Optional'),
-                ),
-            )),
-        );
-        foreach ($searchable as $path=>$field) {
-            $fields = array_merge($fields, static::getSearchField($field, $path));
+    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,
+                    'configuration' => array(
+                        'size' => 40,
+                        'length' => 400,
+                        'autofocus' => true,
+                        'classes' => 'full-width headline',
+                        'placeholder' => __('Keywords — Optional'),
+                    ),
+                )),
+            );
         }
 
+        foreach ($searchable as $path=>$field)
+            $fields = array_merge($fields, static::getSearchField($field, $path));
+
         $form = new AdvancedSearchForm($fields, $source);
-        $form->addValidator(function($form) {
-            $selected = 0;
-            foreach ($form->getFields() as $F) {
-                if (substr($F->get('name'), -7) == '+search' && $F->getClean())
-                    $selected += 1;
-                // Consider keyword searches
-                elseif ($F->get('name') == ':keywords' && $F->getClean())
-                    $selected += 1;
-            }
-            if (!$selected)
-                $form->addError(__('No fields selected for searching'));
-        });
+
+        // Field selection validator
+        if ($this->criteriaRequired()) {
+            $form->addValidator(function($form) {
+                    if (!$form->getNumFieldsSelected())
+                        $form->addError(__('No fields selected for searching'));
+                });
+        }
 
         // Load state from current configuraiton
         if (!$source) {
@@ -233,7 +242,7 @@ class CustomQueue extends VerySimpleModel {
      *      to contain a list extra fields by ORM path, of newly added
      *      fields not yet saved in this object's getCriteria().
      */
-    function getCurrentSearchFields($source=array()) {
+    function getCurrentSearchFields($source=array(), $criteria=array()) {
         static $basic = array(
             'Ticket' => array(
                 'status__state',
@@ -255,7 +264,7 @@ class CustomQueue extends VerySimpleModel {
                     $core[$path] = $all[$path];
 
         // Add others from current configuration
-        foreach ($this->getCriteria() as $C) {
+        foreach ($criteria ?: $this->getCriteria() as $C) {
             list($path) = $C;
             if (isset($all[$path]))
                 $core[$path] = $all[$path];
@@ -269,6 +278,27 @@ class CustomQueue extends VerySimpleModel {
         return $core;
     }
 
+    /**
+    * Fetch all supported ORM fields filterable by this search object.
+    */
+    function getSupportedFilters() {
+        return static::getFilterableFields($this->getRoot());
+    }
+
+
+    /**
+     * Get get supplemental matches for public queues.
+     *
+     */
+
+    function getSupplementalMatches() {
+        return array();
+    }
+
+    function getSupplementalCriteria() {
+        return array();
+    }
+
     /**
      * Fetch all supported ORM fields searchable by this search object. The
      * returned list represents searchable fields, keyed by the ORM path.
@@ -374,6 +404,20 @@ class CustomQueue extends VerySimpleModel {
         return $fields;
     }
 
+  /**
+     * Fetch all searchable fileds, for the base object  which support quick filters.
+     */
+    function getFilterableFields($object) {
+        $filters = array();
+        foreach (static::getSearchableFields($object) as $p => $f) {
+            list($label, $field) = $f;
+            if ($field && $field->supportsQuickFilter())
+                $filters[$p] = $f;
+        }
+
+        return $filters;
+    }
+
     /**
      * Fetch the FormField instances used when for configuring a searchable
      * field in the user interface. This is the glue between a field
@@ -460,11 +504,13 @@ class CustomQueue extends VerySimpleModel {
      * field name being search, the method used for searhing, and the method-
      * specific data entered in the UI.
      */
-    function isolateCriteria($criteria, $root=null) {
-        $searchable = static::getSearchableFields($root ?: $this->getRoot());
-        $items = array();
+    static function isolateCriteria($criteria, $base='Ticket') {
+
         if (!is_array($criteria))
             return null;
+
+        $items = array();
+        $searchable = static::getSearchableFields($base);
         foreach ($criteria as $k=>$v) {
             if (substr($k, -7) === '+method') {
                 list($name,) = explode('+', $k, 2);
@@ -477,9 +523,8 @@ class CustomQueue extends VerySimpleModel {
 
                 // Lookup the field to search this condition
                 list($label, $field) = $searchable[$name];
-
-                // Get the search method and value
-                $method = $v;
+                // Get the search method
+                $method = is_array($v) ? key($v) : $v;
                 // Not all search methods require a value
                 $value = $criteria["{$name}+{$method}"];
 
@@ -574,6 +619,10 @@ class CustomQueue extends VerySimpleModel {
         return $fields;
     }
 
+    function getStandardColumns() {
+        return $this->getColumns();
+    }
+
     function getColumns($use_template=false) {
         if ($this->columns_id
             && ($q = CustomQueue::lookup($this->columns_id))
@@ -818,6 +867,7 @@ class CustomQueue extends VerySimpleModel {
      * array retrieved in ::getSearchFields()
      */
     function describeField($info, $name=false) {
+        $name = $name ?: $info['field']->get('label');
         return $info['field']->describeSearch($info['method'], $info['value'], $name);
     }
 
@@ -862,17 +912,25 @@ class CustomQueue extends VerySimpleModel {
     }
 
     function checkAccess(Staff $agent) {
-        return $agent->getId() == $this->staff_id
-            || $this->hasFlag(self::FLAG_PUBLIC);
+        return $this->isPublic() || $this->checkOwnership($agent);
     }
 
-    function ignoreVisibilityConstraints() {
-        global $thisstaff;
+    function checkOwnership(Staff $agent) {
+
+        return ($agent->getId() == $this->staff_id &&
+                !$this->isAQueue());
+    }
+
+    function isOwner(Staff $agent) {
+        return $agent && $this->isPrivate() && $this->checkOwnership($agent);
+    }
 
-        // For saved searches (not queues), staff can have a permission to
+    function ignoreVisibilityConstraints(Staff $agent) {
+        // For saved searches (not queues), some staff can have a permission to
         // see all records
-        return !$this->isAQueue()
-            && $thisstaff->hasPerm(SearchBackend::PERM_EVERYTHING);
+        return (!$this->isASubQueue()
+                && $this->isOwner($agent)
+                && $agent->canSearchEverything());
     }
 
     function inheritCriteria() {
@@ -883,6 +941,10 @@ class CustomQueue extends VerySimpleModel {
         return $this->hasFlag(self::FLAG_INHERIT_COLUMNS);
     }
 
+    function useStandardColumns() {
+        return !count($this->columns);
+    }
+
     function inheritExport() {
         return ($this->hasFlag(self::FLAG_INHERIT_EXPORT) ||
                 !count($this->exports));
@@ -911,12 +973,22 @@ class CustomQueue extends VerySimpleModel {
         return $base;
     }
 
+    function isASubQueue() {
+        return $this->parent ? $this->parent->isASubQueue() :
+            $this->isAQueue();
+    }
+
     function isAQueue() {
         return $this->hasFlag(self::FLAG_QUEUE);
     }
 
     function isPrivate() {
-        return !$this->isAQueue() && !$this->hasFlag(self::FLAG_PUBLIC);
+        return !$this->isAQueue() && !$this->isPublic() &&
+            $this->staff_id;
+    }
+
+    function isPublic() {
+        return $this->hasFlag(self::FLAG_PUBLIC);
     }
 
     protected function hasFlag($flag) {
@@ -995,18 +1067,19 @@ class CustomQueue extends VerySimpleModel {
     }
 
     function update($vars, &$errors=array()) {
+
         // Set basic search information
-        if (!$vars['name'])
-            $errors['name'] = __('A title is required');
+        if (!$vars['queue-name'])
+            $errors['queue-name'] = __('A title is required');
         elseif (($q=CustomQueue::lookup(array(
-                        'title' => $vars['name'],
+                        'title' => $vars['queue-name'],
                         'parent_id' => $vars['parent_id'] ?: 0,
                         'staff_id'  => $this->staff_id)))
                 && $q->getId() != $this->id
                 )
-            $errors['name'] = __('Saved queue with same name exists');
+            $errors['queue-name'] = __('Saved queue with same name exists');
 
-        $this->title = $vars['name'];
+        $this->title = $vars['queue-name'];
         $this->parent_id = @$vars['parent_id'] ?: 0;
         if ($this->parent_id && !$this->parent)
             $errors['parent_id'] = __('Select a valid queue');
@@ -1038,7 +1111,7 @@ class CustomQueue extends VerySimpleModel {
         $this->setFlag(self::FLAG_INHERIT_CRITERIA,
             $this->parent_id > 0 && isset($vars['inherit']));
         $this->setFlag(self::FLAG_INHERIT_COLUMNS,
-            $this->parent_id > 0 && isset($vars['inherit-columns']));
+            isset($vars['inherit-columns']));
         $this->setFlag(self::FLAG_INHERIT_EXPORT,
             $this->parent_id > 0 && isset($vars['inherit-exports']));
         $this->setFlag(self::FLAG_INHERIT_SORTING,
@@ -1049,37 +1122,17 @@ class CustomQueue extends VerySimpleModel {
             // No columns -- imply column inheritance
             $this->setFlag(self::FLAG_INHERIT_COLUMNS);
         }
-        if (isset($vars['columns']) && !$this->hasFlag(self::FLAG_INHERIT_COLUMNS)) {
-            $new = $vars['columns'];
-            $order = array_keys($new);
-            foreach ($this->columns as $col) {
-                $key = $col->column_id;
-                if (!isset($vars['columns'][$key])) {
-                    $this->columns->remove($col);
-                    continue;
-                }
-                $info = $vars['columns'][$key];
-                $col->set('sort', array_search($key, $order));
-                $col->set('heading', $info['heading']);
-                $col->set('width', $info['width']);
-                $col->setSortable($info['sortable']);
-                unset($new[$key]);
-            }
-            // Add new columns
-            foreach ($new as $info) {
-                $glue = new QueueColumnGlue(array(
-                    'column_id' => $info['column_id'],
-                    'sort' => array_search($info['column_id'], $order),
-                    'heading' => $info['heading'],
-                    'width' => $info['width'] ?: 100,
-                    'bits' => $info['sortable'] ?  QueueColumn::FLAG_SORTABLE : 0,
-                ));
-                $glue->queue = $this;
-                $this->columns->add(
-                    QueueColumn::lookup($info['column_id']), $glue);
-            }
-            // Re-sort the in-memory columns array
-            $this->columns->sort(function($c) { return $c->sort; });
+
+
+        if ($this->getId()
+                && isset($vars['columns'])
+                && !$this->hasFlag(self::FLAG_INHERIT_COLUMNS)) {
+
+
+            if ($this->columns->update($vars['columns'], $errors, array(
+                                'queue_id' => $this->getId(),
+                                'staff_id' => $this->staff_id)))
+                $this->columns->reset();
         }
 
         // Update export fields for the queue
@@ -1154,7 +1207,8 @@ class CustomQueue extends VerySimpleModel {
         }
         else {
             $this->config = JsonDataEncoder::encode([
-                'criteria' => $this->isolateCriteria($form->getClean()),
+                'criteria' => self::isolateCriteria($form->getClean(),
+                    $this->getRoot()),
                 'conditions' => $conditions,
             ]);
             // Clear currently set criteria.and conditions.
@@ -1228,13 +1282,10 @@ class CustomQueue extends VerySimpleModel {
 
 
     static function create($vars=false) {
-        global $thisstaff;
 
         $queue = new static($vars);
         $queue->created = SqlFunction::NOW();
         $queue->setFlag(self::FLAG_QUEUE);
-        if ($thisstaff)
-            $queue->staff_id = $thisstaff->getId();
 
         return $queue;
     }
@@ -1650,8 +1701,8 @@ class QueueColumnCondition {
      * field name being search, the method used for searhing, and the method-
      * specific data entered in the UI.
      */
-    static function isolateCriteria($criteria, $root='Ticket') {
-        $searchable = CustomQueue::getSearchableFields($root);
+    static function isolateCriteria($criteria, $base='Ticket') {
+        $searchable = CustomQueue::getSearchableFields($base);
         foreach ($criteria as $k=>$v) {
             if (substr($k, -7) === '+method') {
                 list($name,) = explode('+', $k, 2);
@@ -2166,6 +2217,71 @@ extends VerySimpleModel {
     }
 }
 
+
+class QueueConfig
+extends VerySimpleModel {
+    static $meta = array(
+        'table' => QUEUE_CONFIG_TABLE,
+        'pk' => array('queue_id', 'staff_id'),
+        'joins' => array(
+            'queue' => array(
+                'constraint' => array(
+                    'queue_id' => 'CustomQueue.id'),
+            ),
+            'staff' => array(
+                'constraint' => array(
+                    'staff_id' => 'Staff.staff_id',
+                )
+            ),
+            'columns' => array(
+                'reverse' => 'QueueColumnGlue.config',
+                'constrain' => array('staff_id' =>'QueueColumnGlue.staff_id'),
+                'broker' => 'QueueColumnListBroker',
+            ),
+        ),
+    );
+
+    function getSettings() {
+        return JsonDataParser::decode($this->setting);
+    }
+
+
+    function update($vars, &$errors) {
+
+        // settings of interest
+        $setting = array(
+                'sort_id' => (int) $vars['sort_id'],
+                'filter' => $vars['filter'],
+                'inherit-columns' => isset($vars['inherit-columns']),
+                'criteria' => $vars['criteria'] ?: array(),
+                );
+
+        if (!$setting['inherit-columns'] && $vars['columns']) {
+            if (!$this->columns->update($vars['columns'], $errors, array(
+                                'queue_id' => $this->queue_id,
+                                'staff_id' => $this->staff_id)))
+                $setting['inherit-columns'] = true;
+            $this->columns->reset();
+        }
+
+        $this->setting =  JsonDataEncoder::encode($setting);
+
+        return $this->save();
+    }
+
+    function save($refetch=false) {
+        if ($this->dirty)
+            $this->updated = SqlFunction::NOW();
+        return parent::save($refetch || $this->dirty);
+    }
+
+    static function create($vars=false) {
+        $inst = new static($vars);
+        return $inst;
+    }
+}
+
+
 class QueueExport
 extends VerySimpleModel {
     static $meta = array(
@@ -2203,16 +2319,23 @@ class QueueColumnGlue
 extends VerySimpleModel {
     static $meta = array(
         'table' => QUEUE_COLUMN_TABLE,
-        'pk' => array('queue_id', 'column_id'),
+        'pk' => array('queue_id', 'staff_id', 'column_id'),
         'joins' => array(
             'column' => array(
                 'constraint' => array('column_id' => 'QueueColumn.id'),
             ),
             'queue' => array(
-                'constraint' => array('queue_id' => 'CustomQueue.id'),
+                'constraint' => array(
+                    'queue_id' => 'CustomQueue.id',
+                    'staff_id' => 'CustomQueue.staff_id'),
+            ),
+            'config' => array(
+                'constraint' => array(
+                    'queue_id' => 'QueueConfig.queue_id',
+                    'staff_id' => 'QueueConfig.staff_id'),
             ),
         ),
-        'select_related' => array('column', 'queue'),
+        'select_related' => array('column'),
         'ordering' => array('sort'),
     );
 }
@@ -2244,6 +2367,44 @@ extends InstrumentedList {
         parent::add($anno, false);
         return $anno;
     }
+
+    function update($columns, &$errors, $options=array()) {
+
+
+        $new = $columns;
+        $order = array_keys($new);
+        foreach ($this as $col) {
+            $key = $col->column_id;
+            if (!isset($columns[$key])) {
+                $this->remove($col);
+                continue;
+            }
+            $info = $columns[$key];
+            $col->set('sort', array_search($key, $order));
+            $col->set('heading', $info['heading']);
+            $col->set('width', $info['width']);
+            $col->setSortable($info['sortable']);
+            unset($new[$key]);
+        }
+        // Add new columns
+        foreach ($new as $info) {
+            $glue = new QueueColumnGlue(array(
+                'staff_id' => $options['staff_id'] ?: 0 ,
+                'queue_id' => $options['queue_id'] ?: 0,
+                'column_id' => $info['column_id'],
+                'sort' => array_search($info['column_id'], $order),
+                'heading' => $info['heading'],
+                'width' => $info['width'] ?: 100,
+                'bits' => $info['sortable'] ?  QueueColumn::FLAG_SORTABLE : 0,
+            ));
+
+            $this->add(QueueColumn::lookup($info['column_id']), $glue);
+        }
+        // Re-sort the in-memory columns array
+        $this->sort(function($c) { return $c->sort; });
+
+        return $this->saveAll();
+    }
 }
 
 class QueueSort
diff --git a/include/class.report.php b/include/class.report.php
index 999ae22fe0ac6c6e12a70edbb1bf6fc340f2611c..c39ec47cc5ea595aaddcaf46d25323929d58c1cc 100644
--- a/include/class.report.php
+++ b/include/class.report.php
@@ -211,9 +211,10 @@ class OverviewReport {
             $headers = array(__('Help Topic'));
             $header = function($row) { return Topic::getLocalNameById($row['topic_id'], $row['topic__topic']); };
             $pk = 'topic_id';
+            $topics = Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
             $stats = $stats
                 ->values('topic_id', 'topic__topic', 'topic__flags')
-                ->filter(array('dept_id__in' => $thisstaff->getDepts(), 'topic_id__gt' => 0));
+                ->filter(array('dept_id__in' => $thisstaff->getDepts(), 'topic_id__gt' => 0, 'topic_id__in' => array_keys($topics)));
             $times = $times
                 ->values('topic_id')
                 ->filter(array('topic_id__gt' => 0));
@@ -223,7 +224,10 @@ class OverviewReport {
             $header = function($row) { return new AgentsName(array(
                 'first' => $row['staff__firstname'], 'last' => $row['staff__lastname'])); };
             $pk = 'staff_id';
-            $stats = $stats->values('staff_id', 'staff__firstname', 'staff__lastname');
+            $staff = Staff::getStaffMembers();
+            $stats = $stats
+                ->values('staff_id', 'staff__firstname', 'staff__lastname')
+                ->filter(array('staff_id__in' => array_keys($staff)));
             $times = $times->values('staff_id');
             $depts = $thisstaff->getManagedDepartments();
             if ($thisstaff->hasPerm(ReportModel::PERM_AGENTS))
diff --git a/include/class.search.php b/include/class.search.php
index d305ab16fee05a17d2de4c0c808d9f37230582f1..8b6ec005915d1055f1543ec0526363f34d163b9a 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -646,14 +646,29 @@ MysqlSearchBackend::register();
 // Saved search system
 
 /**
- * A special case of the custom queues used to represent an advanced search.
+ * Custom Queue truly represent a saved advanced search.
  */
-class SavedSearch extends CustomQueue {
+class SavedQueue extends CustomQueue {
     // Override the ORM relationship to force no children
     private $children = false;
+    private $_config;
+    private $_criteria;
+    private $_columns;
+    private $_settings;
+    private $_form;
 
-    function isSaved() {
-        return true;
+
+
+    function __onload() {
+        global $thisstaff;
+
+        // Load custom settings for this staff
+        if ($thisstaff) {
+            $this->_config = QueueConfig::lookup(array(
+                         'queue_id' => $this->getId(),
+                         'staff_id' => $thisstaff->getId())
+                    );
+        }
     }
 
     static function forStaff(Staff $agent) {
@@ -664,14 +679,216 @@ class SavedSearch extends CustomQueue {
         ->exclude(array('flags__hasbit'=>self::FLAG_QUEUE));
     }
 
+    private function getSettings() {
+        if (!isset($this->_settings)) {
+            $this->_settings = array();
+            if ($this->_config)
+                $this->_settings = $this->_config->getSettings();
+        }
+
+        return  $this->_settings;
+    }
+
+    private function getCustomColumns() {
+
+        if (!isset($this->_columns)) {
+            $this->_columns = array();
+            if ($this->_config
+                    && $this->_config->columns->count())
+                $this->_columns = $this->_config->columns;
+        }
+
+        return $this->_columns;
+    }
+
+    /**
+     * 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 = array_intersect_key($this->getCurrentSearchFields($source),
+                    $this->getSupplementalMatches());
+
+        return parent::getForm($source, $searchable);
+    }
+
+   /**
+     * Get get supplemental matches for public queues.
+     *
+     */
+    function getSupplementalMatches() {
+        // Target flags
+        $flags = array('isoverdue', 'isassigned', 'isreopened', 'isanswered');
+        $current = array();
+        // Check for closed state - whih disables above flags
+        foreach (parent::getCriteria() as $c) {
+            if (!strcasecmp($c[0], 'status__state')
+                    && isset($c[2]['closed']))
+                return array();
+
+            $current[] = $c[0];
+        }
+
+        // Filter out fields already in criteria
+        $matches = array_intersect_key($this->getSupportedMatches(),
+                array_flip(array_diff($flags, $current)));
+
+        return $matches;
+    }
+
+    function criteriaRequired() {
+        return !$this->isAQueue();
+    }
+
+    function describeCriteria($criteria=false){
+        $criteria = $criteria ?: parent::getCriteria();
+        return parent::describeCriteria($criteria);
+    }
+
+    function getCriteria($include_parent=true) {
+
+        if (!isset($this->_criteria)) {
+            $this->getSettings();
+            $this->_criteria = $this->_settings['criteria'] ?: array();
+        }
+
+        $criteria = $this->_criteria;
+        if ($include_parent)
+            $criteria = array_merge($criteria,
+                    parent::getCriteria($include_parent));
+
+
+        return $criteria;
+    }
+
+    function getSupplementalCriteria() {
+        return $this->getCriteria(false);
+    }
+
+    function useStandardColumns() {
+
+        $this->getSettings();
+        if ($this->getCustomColumns()
+                && isset($this->_settings['inherit-columns']))
+            return $this->_settings['inherit-columns'];
+
+        // owner?? edit away.
+        if ($this->_config
+                && $this->_config->staff_id == $this->staff_id)
+            return false;
+
+        return parent::useStandardColumns();
+    }
+
+    function getStandardColumns() {
+        return parent::getColumns(is_null($this->parent));
+    }
+
+    function getColumns($use_template=false) {
+
+        if (!$this->useStandardColumns() && ($columns=$this->getCustomColumns()))
+            return $columns;
+
+        return parent::getColumns($use_template);
+    }
+
     function update($vars, &$errors=array()) {
-        if (!parent::update($vars, $errors))
+        global $thisstaff;
+
+        if (!$this->checkAccess($thisstaff))
             return false;
 
-        // Personal queues _always_ inherit from their parent
-        $this->setFlag(self::FLAG_INHERIT_CRITERIA, $this->parent_id > 0);
+        if ($this->checkOwnership($thisstaff)) {
+            // Owner of the queue - can update everything
+            if (!parent::update($vars, $errors))
+                return false;
+
+            // Personal queues _always_ inherit from their parent
+            $this->setFlag(self::FLAG_INHERIT_CRITERIA, $this->parent_id >
+                    0);
+
+            return true;
+        }
+
+        // Agent's config for public queue.
+        if (!$this->_config)
+            $this->_config = QueueConfig::create(array(
+                        'queue_id' => $this->getId(),
+                        'staff_id' => $thisstaff->getId()));
+
+        //  Validate & isolate supplemental criteria (if any)
+        $vars['criteria'] = array();
+        if (isset($vars['fields'])) {
+           $form = $this->getForm($vars, $thisstaff);
+            if ($form->isValid()) {
+                $criteria = self::isolateCriteria($form->getClean(),
+                        $this->getRoot());
+                $allowed = $this->getSupplementalMatches();
+                foreach ($criteria as $k => $c)
+                    if (!isset($allowed[$c[0]]))
+                        unset($criteria[$k]);
+
+                $vars['criteria'] = $criteria ?: array();
+            } else {
+                $errors['criteria'] = __('Validation errors exist on supplimental criteria');
+            }
+        }
+
+        if (!$errors && $this->_config->update($vars, $errors))
+            $this->_settings = $this->_criteria = null;
 
-        return count($errors) === 0;
+        return (!$errors);
+    }
+
+    static function ticketsCount($agent, $criteria=array(),
+            $prefix='') {
+
+        if (!$agent instanceof Staff)
+            return array();
+
+        $queues = SavedQueue::objects()
+            ->filter(Q::any(array(
+                'flags__hasbit' => CustomQueue::FLAG_PUBLIC,
+                'staff_id' => $agent->getId(),
+            )));
+
+        if ($criteria)
+            $queues->filter($criteria);
+
+        $query = Ticket::objects();
+        // Apply tickets visibility for the agent
+        $query = $agent->applyVisibility($query);
+        // Aggregate constraints
+        foreach ($queues as $queue) {
+            $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)
+            ));
+        }
+
+        return $query->values()->one();
+    }
+
+    static function lookup($criteria) {
+        $queue = parent::lookup($criteria);
+        // Annoted cusom settings (if any)
+        if (($c=$queue->_config)) {
+            $queue->_settings = $c->getSettings() ?: array();
+            $queue = AnnotatedModel::wrap($queue,
+                        array_intersect_key($queue->_settings,
+                            array_flip(array('sort_id', 'filter'))));
+            $queue->_config = $c;
+        }
+
+        return $queue;
     }
 
     static function create($vars=false) {
@@ -681,6 +898,13 @@ class SavedSearch extends CustomQueue {
     }
 }
 
+class SavedSearch extends SavedQueue {
+
+    function isSaved() {
+        return (!$this->__new__);
+    }
+}
+
 class AdhocSearch
 extends SavedSearch {
 
@@ -717,13 +941,63 @@ extends SavedSearch {
     }
 }
 
+// AdvacedSearchForm
 class AdvancedSearchForm extends SimpleForm {
     static $id = 1337;
+
+    function getNumFieldsSelected() {
+        $selected = 0;
+        foreach ($this->getFields() as $F) {
+            if (substr($F->get('name'), -7) == '+search'
+                    && $F->getClean())
+                $selected += 1;
+            // Consider keyword searches
+            elseif ($F->get('name') == ':keywords'
+                    && $F->getClean())
+                $selected += 1;
+        }
+        return $selected;
+    }
 }
 
 // Advanced search special fields
 
-class HelpTopicChoiceField extends ChoiceField {
+class AdvancedSearchSelectionField extends ChoiceField {
+
+    function hasIdValue() {
+        return false;
+    }
+
+    function getSearchQ($method, $value, $name=false) {
+        switch ($method) {
+            case 'includes':
+            case '!includes':
+                $Q = new Q();
+                if (count($value) > 1)
+                    $Q->add(array("{$name}__in" => array_keys($value)));
+                else
+                    $Q->add(array($name => key($value)));
+
+                if ($method == '!includes')
+                    $Q->negate();
+                return $Q;
+                break;
+            // osTicket commonly uses `0` to represent an unset state, so
+            // the set and unset checks need to check for both not null and
+            // nonzero
+            case 'nset':
+                return new Q([$name => 0]);
+            case 'set':
+                return Q::not([$name => 0]);
+            default:
+                return parent::getSearchQ($method, $value, $name);
+        }
+
+    }
+
+}
+
+class HelpTopicChoiceField extends AdvancedSearchSelectionField {
     function hasIdValue() {
         return true;
     }
@@ -734,7 +1008,7 @@ class HelpTopicChoiceField extends ChoiceField {
 }
 
 require_once INCLUDE_DIR . 'class.dept.php';
-class DepartmentChoiceField extends ChoiceField {
+class DepartmentChoiceField extends AdvancedSearchSelectionField {
     var $_choices = null;
 
     function getChoices($verbose=false) {
@@ -764,6 +1038,7 @@ class DepartmentChoiceField extends ChoiceField {
     }
 }
 
+
 class AssigneeChoiceField extends ChoiceField {
     function getChoices($verbose=false) {
         global $thisstaff;
@@ -877,11 +1152,30 @@ class AssigneeChoiceField extends ChoiceField {
 
     function applyOrderBy($query, $reverse=false, $name=false) {
         $reverse = $reverse ? '-' : '';
-        return $query->order_by("{$reverse}staff__firstname",
-            "{$reverse}staff__lastname", "{$reverse}team__name");
+        return Staff::nsort($query, $reverse);
     }
 }
 
+class AssignedField extends AssigneeChoiceField {
+
+    function getSearchMethods() {
+        return array(
+            'assigned' =>   __('assigned'),
+            '!assigned' =>  __('unassigned'),
+        );
+    }
+
+    function addToQuery($query, $name=false) {
+        return $query->values('staff_id', 'team_id');
+    }
+
+    function from_query($row, $name=false) {
+        return ($row['staff_id'] || $row['staff_id'])
+            ? __('Yes') : __('No');
+    }
+
+}
+
 /**
  * Simple trait which changes the SQL for "has a value" and "does not have a
  * value" to check for zero or non-zero. Useful for not nullable fields.
@@ -902,11 +1196,11 @@ trait ZeroMeansUnset {
     }
 }
 
-class AgentSelectionField extends ChoiceField {
+class AgentSelectionField extends AdvancedSearchSelectionField {
     use ZeroMeansUnset;
 
     function getChoices($verbose=false) {
-        return Staff::getStaffMembers();
+        return array('M' => __('Me')) + Staff::getStaffMembers();
     }
 
     function toString($value) {
@@ -920,31 +1214,52 @@ class AgentSelectionField extends ChoiceField {
             parent::toString($value);
     }
 
-    function applyOrderBy($query, $reverse=false, $name=false) {
-        global $cfg;
+    function getSearchQ($method, $value, $name=false) {
+        global $thisstaff;
+        // unpack me
+        if (isset($value['M']) && $thisstaff) {
+            $value[$thisstaff->getId()] = $thisstaff->getName();
+            unset($value['M']);
+        }
+
+        return parent::getSearchQ($method, $value, $name);
+    }
+
 
+    function applyOrderBy($query, $reverse=false, $name=false) {
         $reverse = $reverse ? '-' : '';
-        switch ($cfg->getAgentNameFormat()) {
-        case 'last':
-        case 'lastfirst':
-        case 'legal':
-            $query->order_by("{$reverse}staff__lastname",
-                "{$reverse}staff__firstname");
-            break;
+        return Staff::nsort($query, $reverse);
+    }
+}
 
-        default:
-            $query->order_by("{$reverse}staff__firstname",
-                "{$reverse}staff__lastname");
-        }
-        return $query;
+class DepartmentManagerSelectionField extends AgentSelectionField {
+
+    function getChoices($verbose=false) {
+        return Staff::getStaffMembers();
+    }
+
+    function getSearchQ($method, $value, $name=false) {
+        return parent::getSearchQ($method, $value, 'dept__manager_id');
     }
 }
 
-class TeamSelectionField extends ChoiceField {
-    use ZeroMeansUnset;
+class TeamSelectionField extends AdvancedSearchSelectionField {
 
     function getChoices($verbose=false) {
-        return Team::getTeams();
+        return array('T' => __('One of my teams')) + Team::getTeams();
+    }
+
+    function getSearchQ($method, $value, $name=false) {
+        global $thisstaff;
+
+        // Unpack my teams
+        if (isset($value['T']) && $thisstaff
+                && ($teams = $thisstaff->getTeams())) {
+            unset($value['T']);
+            $value = $value + array_flip($teams);
+        }
+
+        return parent::getSearchQ($method, $value, $name);
     }
 
     function applyOrderBy($query, $reverse=false, $name=false) {
@@ -953,7 +1268,7 @@ class TeamSelectionField extends ChoiceField {
     }
 }
 
-class TicketStateChoiceField extends ChoiceField {
+class TicketStateChoiceField extends AdvancedSearchSelectionField {
     function getChoices($verbose=false) {
         return array(
             'open' => __('Open'),
diff --git a/include/class.staff.php b/include/class.staff.php
index 965b7d429910614015045bf49ead08db9e616237..0c35a0c1c4b5fb33f9b327a811d68d4aa624e4d4 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -359,7 +359,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
             $depts = array();
             if (($res=db_query($sql)) && db_num_rows($res)) {
                 while(list($id)=db_fetch_row($res))
-                    $depts[] = $id;
+                    $depts[] = (int) $id;
             }
 
             /* ORM method — about 2.0ms slower
@@ -459,13 +459,21 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         return $this->_roles;
     }
 
-    function getRole($dept=null) {
-        $deptId = is_object($dept) ? $dept->getId() : $dept;
+    function getRole($dept=null, $assigned=false) {
+
+        if (is_null($dept))
+            return $this->role;
+
+        if ((!$dept instanceof Dept) && !($dept=Dept::lookup($dept)))
+            return null;
+
+        $deptId = $dept->getId();
         $roles = $this->getRoles();
         if (isset($roles[$deptId]))
             return $roles[$deptId];
 
-        if ($this->usePrimaryRoleOnAssignment())
+        // Default to primary role.
+        if ($assigned && $this->usePrimaryRoleOnAssignment())
             return $this->role;
 
         // View only access
@@ -483,6 +491,10 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         return false;
     }
 
+    function canSearchEverything() {
+        return $this->hasPerm(SearchBackend::PERM_EVERYTHING);
+    }
+
     function canManageTickets() {
         return $this->hasPerm(Ticket::PERM_DELETE, false)
                 || $this->hasPerm(Ticket::PERM_TRANSFER, false)
@@ -534,8 +546,13 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         return ($teamId && in_array($teamId, $this->getTeams()));
     }
 
-    function canAccessDept($deptId) {
-        return ($deptId && in_array($deptId, $this->getDepts()) && !$this->isAccessLimited());
+    function canAccessDept($dept) {
+
+        if (!$dept instanceof Dept)
+            return false;
+
+        return (!$this->isAccessLimited()
+                && in_array($dept->getId(), $this->getDepts()));
     }
 
     function getTeams() {
@@ -543,7 +560,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         if (!isset($this->_teams)) {
             $this->_teams = array();
             foreach ($this->teams as $team)
-                 $this->_teams[] = $team->team_id;
+                 $this->_teams[] = (int) $team->team_id;
         }
 
         return $this->_teams;
@@ -559,7 +576,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         $assigned->add(array('thread__referrals__agent__staff_id' => $this->getId()));
 
         // -- Open and assigned to a team of mine
-        if ($teams = array_filter($this->getTeams())) {
+        if (($teams = array_filter($this->getTeams()))) {
             $assigned->add(array('team_id__in' => $teams));
             $assigned->add(array('thread__referrals__team__team_id__in' => $teams));
         }
@@ -575,6 +592,10 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         return $visibility;
     }
 
+    function applyVisibility($query) {
+        return $query->filter($this->getTicketsVisibility());
+    }
+
     /* stats */
     function resetStats() {
         $this->stats = array();
diff --git a/include/class.task.php b/include/class.task.php
index 1a5860b0b87595d6a045058a511f79a78c903ef6..5e8ae0fe45c6af4d5fca52895d54e6eecd0f87f5 100644
--- a/include/class.task.php
+++ b/include/class.task.php
@@ -267,7 +267,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
             return false;
 
         // Check access based on department or assignment
-        if (!$staff->canAccessDept($this->getDeptId())
+        if (!$staff->canAccessDept($this->getDept())
                 && $this->isOpen()
                 && $staff->getId() != $this->getStaffId()
                 && !$staff->isTeamMember($this->getTeamId()))
@@ -279,7 +279,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
             return true;
 
         // Permission check requested -- get role.
-        if (!($role=$staff->getRole($this->getDeptId())))
+        if (!($role=$staff->getRole($this->getDept())))
             return false;
 
         // Check permission based on the effective role
@@ -995,7 +995,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
 
         $pdf = new Task2PDF($this, $options);
         $name = 'Task-'.$this->getNumber().'.pdf';
-        Http::download($name, 'application/pdf', $pdf->Output($name, 'S'));
+        Http::download($name, 'application/pdf', $pdf->output($name, 'S'));
         //Remember what the user selected - for autoselect on the next print.
         $_SESSION['PAPER_SIZE'] = $options['psize'];
         exit;
@@ -1330,7 +1330,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
         $task->logEvent('created', null, $thisstaff);
 
         // Get role for the dept
-        $role = $thisstaff->getRole($task->dept_id);
+        $role = $thisstaff->getRole($task->getDept());
         // Assignment
         $assignee = $vars['internal_formdata']['assignee'];
         if ($assignee
diff --git a/include/class.team.php b/include/class.team.php
index d6963eb9213272959dc5fe0da0d319b59376a784..a4fd4f8c7af6eb8cbac70a3d27f2f82488cc59ea 100644
--- a/include/class.team.php
+++ b/include/class.team.php
@@ -208,13 +208,19 @@ implements TemplateVariable {
           }
           $member->setAlerts($alerts);
       }
-      if (!$errors && $dropped) {
+
+      if ($errors)
+          return false;
+
+      $this->members->saveAll();
+      if ($dropped) {
           $this->members
               ->filter(array('staff_id__in' => array_keys($dropped)))
               ->delete();
           $this->members->reset();
       }
-      return !$errors;
+
+      return true;
     }
 
     function save($refetch=false) {
diff --git a/include/class.thread.php b/include/class.thread.php
index b06bd0432458f9e062c0a7877274da191d0fb26e..017d7db353c3fbd5740935138f2fc26222a03dcb 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -284,7 +284,7 @@ implements Searchable {
 
     function isReferred($to=null, $strict=false) {
 
-        if (is_null($to))
+        if (is_null($to) || !$this->referrals)
             return ($this->referrals);
 
         switch (true) {
@@ -299,12 +299,12 @@ implements Searchable {
                 return false;
 
             // Referred to staff's department
-            if ($this->referrals->findFirst(array(
+            if ($to->getDepts() && $this->referrals->filter(array(
                             'object_id__in' => $to->getDepts(),
                             'object_type'   => ObjectModel::OBJECT_TYPE_DEPT)))
                 return true;
             // Referred to staff's  team
-            if ($this->referrals->findFirst(array(
+            if ($to->getTeams() && $this->referrals->filter(array(
                             'object_id__in' => $to->getTeams(),
                             'object_type'   => ObjectModel::OBJECT_TYPE_TEAM)))
                 return true;
@@ -778,6 +778,18 @@ implements TemplateVariable {
         ),
     );
 
+    // Thread entry types
+    static protected $types = array(
+            'M' => 'message',
+            'R' => 'response',
+            'N' => 'note',
+            'B' => 'bccmessage',
+    );
+
+    function getTypeName() {
+      return self::$types[$this->type];
+    }
+
     function postEmail($mailinfo) {
         global $ost;
 
@@ -1720,6 +1732,10 @@ implements TemplateVariable {
     static function getPermissions() {
         return self::$perms;
     }
+
+    static function getTypes() {
+        return self::$types;
+    }
 }
 
 RolePermission::register(/* @trans */ 'Tickets', ThreadEntry::getPermissions());
diff --git a/include/class.ticket.php b/include/class.ticket.php
index a6ed0915df61141e13fd89f210f7a69805cf785e..167703aaf49e2d1ae32d405f33210892b29b548c 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -207,9 +207,8 @@ implements RestrictedAccess, Threadable, Searchable {
     }
 
     function isReopenable() {
-        return ($this->getStatus()->isReopenable()
-          && $this->getDept()->allowsReopen()
-        && $this->getTopic()->allowsReopen());
+        return ($this->getStatus()->isReopenable() && $this->getDept()->allowsReopen()
+        && ($this->getTopic() ? $this->getTopic()->allowsReopen() : null));
     }
 
     function isClosed() {
@@ -248,7 +247,7 @@ implements RestrictedAccess, Threadable, Searchable {
         if (!$this->isOpen())
             return false;
 
-        if (!$to)
+        if (is_null($to))
             return ($this->getStaffId() || $this->getTeamId());
 
         switch (true) {
@@ -276,30 +275,36 @@ implements RestrictedAccess, Threadable, Searchable {
         return null !== $this->getLock();
     }
 
+    function getRole($staff) {
+        if (!$staff instanceof Staff)
+            return null;
+
+        return $staff->getRole($this->getDept(), $this->isAssigned($staff));
+    }
+
     function checkStaffPerm($staff, $perm=null) {
+
         // Must be a valid staff
-        if (!$staff instanceof Staff && !($staff=Staff::lookup($staff)))
+        if ((!$staff instanceof Staff) && !($staff=Staff::lookup($staff)))
             return false;
 
-        // Check access based on department or assignment
-        if (($staff->showAssignedOnly()
-            || !$staff->canAccessDept($this->getDeptId()))
-            // only open tickets can be considered assigned
-            && $this->isOpen()
-            && $staff->getId() != $this->getStaffId()
-            && !$staff->isTeamMember($this->getTeamId())
-            && !$this->thread->isReferred($staff)
-        ) {
+        // check department access first
+        if (!$staff->canAccessDept($this->getDept())
+                // no restrictions
+                && !$staff->isAccessLimited()
+                // check assignment
+                && !$this->isAssigned($staff)
+                // check referral
+                && !$this->thread->isReferred($staff))
             return false;
-        }
 
         // At this point staff has view access unless a specific permission is
         // requested
         if ($perm === null)
             return true;
 
-        // Permission check requested -- get role.
-        if (!($role=$staff->getRole($this->getDeptId())))
+        // Permission check requested -- get role if any
+        if (!($role=$this->getRole($staff)))
             return false;
 
         // Check permission based on the effective role
@@ -962,7 +967,7 @@ implements RestrictedAccess, Threadable, Searchable {
                         'name' => "{$fid}_id",
                         'label' => __('Help Topic'),
                         'default' => $this->getTopicId(),
-                        'choices' => Topic::getHelpTopics()
+                        'choices' => Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED)
                         ));
             break;
         case 'source':
@@ -1022,16 +1027,22 @@ implements RestrictedAccess, Threadable, Searchable {
         }
     }
 
-    static function getMissingRequiredFields($ticket) {
+    //if ids passed, function returns only the ids of fields disabled by help topic
+    static function getMissingRequiredFields($ticket, $ids=false) {
         // Check for fields disabled by Help Topic
         $disabled = array();
-        foreach ($ticket->entries as $entry) {
-            $extra = JsonDataParser::decode($entry->extra);
+        foreach (($ticket->getTopic() ? $ticket->getTopic()->forms : $ticket->entries) as $f) {
+            $extra = JsonDataParser::decode($f->extra);
+
             if (!empty($extra['disable']))
                 $disabled[] = $extra['disable'];
         }
+
         $disabled = !empty($disabled) ? call_user_func_array('array_merge', $disabled) : NULL;
 
+        if ($ids)
+          return $disabled;
+
         $criteria = array(
                     'answers__field__flags__hasbit' => DynamicFormField::FLAG_ENABLED,
                     'answers__field__flags__hasbit' => DynamicFormField::FLAG_CLOSE_REQUIRED,
@@ -1042,7 +1053,7 @@ implements RestrictedAccess, Threadable, Searchable {
         if ($disabled)
             array_push($criteria, Q::not(array('answers__field__id__in' => $disabled)));
 
-        return $ticket->getDynamicFields($criteria, $ticket);
+        return $ticket->getDynamicFields($criteria);
     }
 
     function getMissingRequiredField() {
@@ -1237,7 +1248,7 @@ implements RestrictedAccess, Threadable, Searchable {
     function setStatus($status, $comments='', &$errors=array(), $set_closing_agent=true) {
         global $thisstaff;
 
-        if ($thisstaff && !($role = $thisstaff->getRole($this->getDeptId())))
+        if ($thisstaff && !($role=$this->getRole($thisstaff)))
             return false;
 
         if ($status && is_numeric($status))
@@ -1603,7 +1614,6 @@ implements RestrictedAccess, Threadable, Searchable {
             if(get_class($recipient) == 'Collaborator') {
               if ($recipient->isCc()) {
                 $collabsCc[] = $recipient->getEmail()->address;
-                $cnotice = $this->replaceVars($msg, array('recipient.name.first' => __('Collaborator'), 'recipient' => $recipient));
               }
               else
                 $collabsBcc[] = $recipient;
@@ -1614,11 +1624,14 @@ implements RestrictedAccess, Threadable, Searchable {
             }
          }
 
-         foreach ($collabsBcc as $recipient) {
-           $notice = $this->replaceVars($msg, array('recipient' => $recipient));
-           if ($posterEmail != $recipient->getEmail()->address)
-             $email->send($recipient, $notice['subj'], $notice['body'], $attachments,
-                 $options);
+         //send bcc messages seperately for privacy
+         if ($collabsBcc) {
+           foreach ($collabsBcc as $recipient) {
+             $notice = $this->replaceVars($msg, array('recipient' => $recipient));
+             if ($posterEmail != $recipient->getEmail()->address)
+               $email->send($recipient, $notice['subj'], $notice['body'], $attachments,
+                   $options);
+           }
          }
 
         foreach ($collabsCc as $cc) {
@@ -1628,14 +1641,33 @@ implements RestrictedAccess, Threadable, Searchable {
             $collaborators[] = $cc;
         }
 
+        //the ticket user is a recipient
         if ($owner->getEmail()->address != $poster->getEmail()->address && !in_array($owner->getEmail()->address, $skip))
-          $collaborators[] = $owner->getEmail()->address;
+          $owner_recip = $owner->getEmail()->address;
 
         $collaborators['cc'] = $collaborators;
 
         //collaborator email sent out
-        $email->send('', $cnotice['subj'], $cnotice['body'], $attachments,
-            $options, $collaborators);
+        if ($collaborators['cc']  || $owner_recip) {
+          //say dear collaborator if the ticket user is not a recipient
+          if (!$owner_recip) {
+            $nameFormats = array_keys(PersonsName::allFormats());
+            $names = array();
+            foreach ($nameFormats as $key => $value) {
+              $names['recipient.name.' . $value] = __('Collaborator');
+            }
+            $names = array_merge($names, array('recipient' => $recipient));
+            $cnotice = $this->replaceVars($msg, $names);
+          }
+
+          //otherwise address email to ticket user
+          else
+            $cnotice = $this->replaceVars($msg, array('recipient' => $owner));
+
+          //if the ticket user is a recipient, put them in to address otherwise, cc all recipients
+          $email->send($owner_recip ? $owner_recip : '', $cnotice['subj'], $cnotice['body'], $attachments,
+              $options, $collaborators);
+        }
     }
 
     function onMessage($message, $autorespond=true, $reopen=true) {
@@ -1663,7 +1695,7 @@ implements RestrictedAccess, Threadable, Searchable {
                     // Is agent on vacation ?
                     && $staff->isAvailable()
                     // Does the agent have access to dept?
-                    && $staff->canAccessDept($dept->getId()))
+                    && $staff->canAccessDept($dept))
                 $this->setStaffId($staff->getId());
             else
                 $this->setStaffId(0); // Clear assignment
@@ -2059,10 +2091,14 @@ implements RestrictedAccess, Threadable, Searchable {
                 'label' => __('Create Date'),
                 'configuration' => array('fromdb' => true),
             )),
-            'est_duedate' => new DatetimeField(array(
+            'duedate' => new DatetimeField(array(
                 'label' => __('Due Date'),
                 'configuration' => array('fromdb' => true),
             )),
+            'est_duedate' => new DatetimeField(array(
+                'label' => __('SLA Due Date'),
+                'configuration' => array('fromdb' => true),
+            )),
             'reopened' => new DatetimeField(array(
                 'label' => __('Reopen Date'),
                 'configuration' => array('fromdb' => true),
@@ -2095,9 +2131,20 @@ implements RestrictedAccess, Threadable, Searchable {
             )),
             'isoverdue' => new BooleanField(array(
                 'label' => __('Overdue'),
+                'descsearchmethods' => array(
+                    'set' => '%s',
+                    'nset' => 'Not %s'
+                    ),
             )),
             'isanswered' => new BooleanField(array(
                 'label' => __('Answered'),
+                'descsearchmethods' => array(
+                    'set' => '%s',
+                    'nset' => 'Not %s'
+                    ),
+            )),
+            'isassigned' => new AssignedField(array(
+                        'label' => __('Assigned'),
             )),
             'ip_address' => new TextboxField(array(
                 'label' => __('IP Address'),
@@ -2920,47 +2967,40 @@ implements RestrictedAccess, Threadable, Searchable {
         $user = $this->getOwner();
         if (($email=$email)
             && ($tpl = $dept->getTemplate())
-            && ($msg=$tpl->getReplyMsgTemplate())
-        ) {
+            && ($msg=$tpl->getReplyMsgTemplate())) {
+
             $msg = $this->replaceVars($msg->asArray(),
                 $variables + array('recipient' => $user)
             );
-            $attachments = $cfg->emailAttachments()?$response->getAttachments():array();
-          }
 
-          if ($vars['emailcollab'] == 1) {
+            $attachments = $cfg->emailAttachments() ? $response->getAttachments() : array();
             //Cc collaborators
-            if($vars['ccs']) {
-              $collabsCc = array();
-              $collabsCc[] = Collaborator::getCollabList($vars['ccs']);
-              $collabsCc['cc'] = $collabsCc;
-              $email->send($user, $msg['subj'], $msg['body'], $attachments,
-                    $options, $collabsCc);
-            }
-            else {
-              $email->send($user, $msg['subj'], $msg['body'], $attachments,
-                  $options);
+            $collabsCc = array();
+            if ($vars['ccs'] && $vars['emailcollab']) {
+                $collabsCc[] = Collaborator::getCollabList($vars['ccs']);
+                $collabsCc['cc'] = $collabsCc[0];
             }
 
+            $email->send($user, $msg['subj'], $msg['body'], $attachments,
+                    $options, $collabsCc);
+
             //Bcc Collaborators
-            if($vars['bccs']) {
-              foreach ($vars['bccs'] as $uid) {
-                $recipient = User::lookup($uid);
-                if (($bcctpl = $dept->getTemplate())
+            if ($vars['bccs']
+                    && $vars['emailcollab']
+                    && ($bcctpl = $dept->getTemplate())
                     && ($bccmsg=$bcctpl->getReplyMsgTemplate())) {
-                  $bccmsg = $this->replaceVars($bccmsg->asArray(), $variables +
-                      array('recipient' => $user, 'recipient.name.first' => $recipient->getName()->getFirst())
-                  );
+                foreach ($vars['bccs'] as $uid) {
+                    if (!($recipient = User::lookup($uid)))
+                        continue;
 
-                  $email->send($recipient, $bccmsg['subj'], $bccmsg['body'], $attachments,
-                      $options);
+                    $extraVars = UsersName::getNameFormats($recipient, 'recipient');
+                    $extraVars = array_merge($extraVars, array('recipient' => $user));
+                    $msg = $this->replaceVars($bccmsg->asArray(), $variables + $extraVars);
+
+                    $email->send($recipient, $msg['subj'], $msg['body'], $attachments, $options);
                 }
-              }
             }
-          }
-          else
-            $email->send($user, $msg['subj'], $msg['body'], $attachments,
-                $options);
+        }
 
         return $response;
     }
@@ -3070,7 +3110,7 @@ implements RestrictedAccess, Threadable, Searchable {
 
         $pdf = new Ticket2PDF($this, $psize, $notes);
         $name = 'Ticket-'.$this->getNumber().'.pdf';
-        Http::download($name, 'application/pdf', $pdf->Output($name, 'S'));
+        Http::download($name, 'application/pdf', $pdf->output($name, 'S'));
         //Remember what the user selected - for autoselect on the next print.
         $_SESSION['PAPER_SIZE'] = $psize;
         exit;
@@ -3942,7 +3982,8 @@ implements RestrictedAccess, Threadable, Searchable {
             return false;
 
         if ($vars['deptId']
-            && ($role = $thisstaff->getRole($vars['deptId']))
+            && ($dept=Dept::lookup($vars['deptId']))
+            && ($role = $thisstaff->getRole($dept))
             && !$role->hasPerm(Ticket::PERM_CREATE)
         ) {
             $errors['err'] = sprintf(__('You do not have permission to create a ticket in %s'), __('this department'));
@@ -4017,7 +4058,7 @@ implements RestrictedAccess, Threadable, Searchable {
         $vars['msgId']=$ticket->getLastMsgId();
 
         // Effective role for the department
-        $role = $thisstaff->getRole($ticket->getDeptId());
+        $role = $ticket->getRole($thisstaff);
 
         // post response - if any
         $response = null;
@@ -4095,15 +4136,13 @@ implements RestrictedAccess, Threadable, Searchable {
                       && ($bccmsg=$tpl->getNewTicketNoticeMsgTemplate())
                       && ($email=$dept->getEmail())
                   )
-                  $bccmsg = $ticket->replaceVars($bccmsg->asArray(),
-                      array(
-                          'message'   => $message,
-                          'signature' => $signature,
-                          'response'  => ($response) ? $response->getBody() : '',
-                          'recipient' => $ticket->getOwner(),
-                          'recipient.name.first' => $recipient->getName()->getFirst(),
-                      )
-                  );
+                  $extraVars = UsersName::getNameFormats($recipient, 'recipient');
+                  $extraVars = array_merge($extraVars, array(
+                    'message'   => $message,
+                    'signature' => $signature,
+                    'response'  => ($response) ? $response->getBody() : '',
+                    'recipient' => $ticket->getOwner()));
+                  $bccmsg = $ticket->replaceVars($bccmsg->asArray(), $extraVars);
 
                   $email->send($recipient, $bccmsg['subj'], $bccmsg['body'], $attachments,
                       $options);
diff --git a/include/class.topic.php b/include/class.topic.php
index 84a1c9d47d85892d62ef4c44976ade79c33afcee..245b3c39f88535ab0afe34a24e8d189d126ca01e 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -191,8 +191,7 @@ implements TemplateVariable, Searchable {
         return $this->isActive();
     }
 
-    function isActive()
-    {
+    function isActive() {
       return !!($this->flags & self::FLAG_ACTIVE);
     }
 
diff --git a/include/class.upgrader.php b/include/class.upgrader.php
index 87fb63c89be95b0cab01f8beb838a111e1b06841..562dcb27957c948a639942cf654fd1450e899d3c 100644
--- a/include/class.upgrader.php
+++ b/include/class.upgrader.php
@@ -362,6 +362,10 @@ class StreamUpgrader extends SetupWizard {
         if(!($max_time = ini_get('max_execution_time')))
             $max_time = 300; //Apache/IIS defaults.
 
+        // Drop any model meta cache to ensure model changes do not cause
+        // crashes
+        ModelMeta::flushModelCache();
+
         // Apply up to five patches at a time
         foreach (array_slice($patches, 0, 5) as $patch) {
             //TODO: check time used vs. max execution - break if need be
diff --git a/include/class.user.php b/include/class.user.php
index dcd04b9c5d59f2bc6c02f4e54c8e928a26c647d5..083e0d5bf2b1853e82d6804a53641d353dbf8fd9 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -813,6 +813,16 @@ implements TemplateVariable {
         return $this;
     }
 
+    function getNameFormats($user, $type) {
+      $nameFormats = array();
+
+      foreach (PersonsName::allFormats() as $format => $func) {
+          $nameFormats[$type . '.name.' . $format] = $user->getName()->$func[1]();
+      }
+
+      return $nameFormats;
+    }
+
     function asVar() {
         return $this->__toString();
     }
diff --git a/include/client/faq.inc.php b/include/client/faq.inc.php
index 9bc723a6e61c4dfbdf9571ce215c8f132e22b0e7..176ae429a5065d545fb4361faee019b015232971 100644
--- a/include/client/faq.inc.php
+++ b/include/client/faq.inc.php
@@ -43,7 +43,8 @@ $category=$faq->getCategory();
     <strong><?php echo __('Attachments');?>:</strong>
 <?php foreach ($attachments as $att) { ?>
     <div>
-    <a href="<?php echo $att->file->getDownloadUrl(); ?>" class="no-pjax">
+    <a href="<?php echo $att->file->getDownloadUrl(['id' => $att->getId()]);
+    ?>" class="no-pjax">
         <i class="icon-file"></i>
         <?php echo Format::htmlchars($att->getFilename()); ?>
     </a>
diff --git a/include/client/templates/thread-entry.tmpl.php b/include/client/templates/thread-entry.tmpl.php
index fed92a5c657b6bba249f6f365cae8462b62c895d..e49b3b0389bf1c9d2f7c330ebac9039b536c0fc9 100644
--- a/include/client/templates/thread-entry.tmpl.php
+++ b/include/client/templates/thread-entry.tmpl.php
@@ -1,6 +1,6 @@
 <?php
 global $cfg;
-$entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note', 'B' => 'bccmessage');
+$entryTypes = ThreadEntry::getTypes();
 $user = $entry->getUser() ?: $entry->getStaff();
 if ($entry->staff && $cfg->hideStaffName())
     $name = __('Staff');
@@ -11,13 +11,14 @@ if ($cfg->isAvatarsEnabled() && $user)
     $avatar = $user->getAvatar();
 ?>
 <?php
-  if ($entryTypes[$entry->type] == 'note') {
-    $entryTypes[$entry->type] = 'bccmessage';
+$type = $entryTypes[$entry->type];
+if ($type == 'note') {
+    $type =  'bccmessage';
     $entry->type = 'B';
-  }
+}
 ?>
 
-<div class="thread-entry <?php echo $entryTypes[$entry->type]; ?> <?php if ($avatar) echo 'avatar'; ?>">
+<div class="thread-entry <?php echo $type; ?> <?php if ($avatar) echo 'avatar'; ?>">
 <?php if ($avatar) { ?>
     <span class="<?php echo ($entry->type == 'M' || $entry->type == 'B') ? 'pull-left' : 'pull-right'; ?> avatar">
 <?php echo $avatar; ?>
@@ -60,7 +61,8 @@ if ($cfg->isAvatarsEnabled() && $user)
 ?>
         <span class="attachment-info">
         <i class="icon-paperclip icon-flip-horizontal"></i>
-        <a class="no-pjax truncate filename" href="<?php echo $A->file->getDownloadUrl();
+        <a  class="no-pjax truncate filename"
+            href="<?php echo $A->file->getDownloadUrl(['id' => $A->getId()]);
             ?>" download="<?php echo Format::htmlchars($A->getFilename()); ?>"
             target="_blank"><?php echo Format::htmlchars($A->getFilename());
         ?></a><?php echo $size;?>
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index bc39255ab0e02367b43f151e1e1b9a05c5c41bd7..fa48bdc3eaf2bb3aaf44ca37690fa64ff509eed3 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -208,7 +208,7 @@ foreach (AttachmentFile::objects()->filter(array(
     'attachments__inline' => true,
 )) as $file) {
     $urls[strtolower($file->getKey())] = array(
-        'download_url' => $file->getDownloadUrl(),
+        'download_url' => $file->getDownloadUrl(['type' => 'H']),
         'filename' => $file->name,
     );
 } ?>
diff --git a/include/i18n/en_US/department.yaml b/include/i18n/en_US/department.yaml
index 3de7de70b6a2965679f39a870e80460516d04206..aa0e6ddc71d8b62befb546d69e390cce165a2f93 100644
--- a/include/i18n/en_US/department.yaml
+++ b/include/i18n/en_US/department.yaml
@@ -3,6 +3,7 @@
 #
 # Fields:
 # id - (int:optional) id number in the database
+# flags - (bitmask: 0x0004 Active 0x0008 Archived)
 # name - (string) Short name of the department
 # signature - (string) Descriptive name of the department
 #
@@ -17,6 +18,7 @@
   signature: |
     Support Department
   ispublic: 1
+  flags: 0x0004
   group_membership: 1
 
 - id: 2
@@ -24,6 +26,7 @@
   signature: |
     Sales and Customer Retention
   ispublic: 1
+  flags: 0x0004
   sla_id: 1
   group_membership: 1
 
@@ -32,4 +35,5 @@
   signature: |
     Maintenance Department
   ispublic: 0
+  flags: 0x0004
   group_membership: 0
diff --git a/include/i18n/en_US/help_topic.yaml b/include/i18n/en_US/help_topic.yaml
index 76faba0c0e3638a0dc4b4b6b8b8f6deb9262dc70..acf674a3b0cd9f0e8162955e4dda8b1346acf595 100644
--- a/include/i18n/en_US/help_topic.yaml
+++ b/include/i18n/en_US/help_topic.yaml
@@ -4,7 +4,7 @@
 # Fields:
 # id - (int:optional) id number in the database
 # topic - (string) descriptive name of the help topic
-# isactive - (bool:0|1) if the help topic should be initially usable
+# flags - (bitmask: Active | Disabled | Archived)
 # ispublic - (bool:0|1) true or false if end users should be able to see the
 #       help topic. In other words, true or false if the help topic is _not_
 #       for internal use only
@@ -19,7 +19,7 @@
 #
 ---
 - topic_id: 1
-  isactive: 1
+  flags: 0x02
   ispublic: 1
   priority_id: 2
   forms: [2]
@@ -27,7 +27,8 @@
   notes: |
     Questions about products or services
 
-- isactive: 1
+- topic_id: 2
+  flags: 0x02
   ispublic: 1
   priority_id: 1
   forms: [2]
@@ -36,7 +37,7 @@
     Tickets that primarily concern the sales and billing departments
 
 - topic_id: 10
-  isactive: 1
+  flags: 0x02
   ispublic: 1
   dept_id: 3
   priority_id: 2
@@ -46,7 +47,7 @@
     Product, service, or equipment related issues
 
 - topic_pid: 10
-  isactive: 1
+  flags: 0x02
   ispublic: 1
   sla_id: 1
   priority_id: 3
diff --git a/include/i18n/en_US/queue.yaml b/include/i18n/en_US/queue.yaml
index 0d572c5c7d3ea24779aceaa3b6fc3ae07312ff2b..ab2b1a4bbb3453e14c7843b81a75d65751b94d9e 100644
--- a/include/i18n/en_US/queue.yaml
+++ b/include/i18n/en_US/queue.yaml
@@ -75,7 +75,7 @@
   parent_id: 1
   flags: 0x03
   root: T
-  sort: 4
+  sort: 2
   config: '[["isanswered","set",null]]'
   columns:
     - column_id: 1
@@ -114,8 +114,6 @@
     - sort_id: 3
     - sort_id: 4
 
-
-
 - id: 3
   title: My Tickets
   parent_id: 0
@@ -205,55 +203,12 @@
     - sort_id: 1
     - sort_id: 2
 
-- title: Unassigned
-  parent_id: 1
-  flags: 0x2b
-  root: T
-  sort: 1
-  config: '[["assignee","!assigned",null]]'
-  columns:
-    - column_id: 1
-      bits: 1
-      sort: 1
-      sort: 1
-      width: 100
-      heading: Ticket
-    - column_id: 10
-      bits: 1
-      sort: 1
-      sort: 2
-      width: 150
-      heading: Last Update
-    - 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: 11
-      bits: 1
-      sort: 1
-      sort: 6
-      width: 160
-      heading: Department
-
-- title: Assigned
+- id: 5
+  title: Assigned
   parent_id: 1
   flags: 0x03
   root: T
-  sort: 2
+  sort: 3
   config: '[["assignee","assigned",null]]'
   columns:
     - column_id: 1
@@ -287,11 +242,12 @@
       width: 160
       heading: Assigned To
 
-- title: Overdue
+- id: 6
+  title: Overdue
   parent_id: 1
   flags: 0x2b
   root: T
-  sort: 3
+  sort: 4
   sort_id: 4
   config: '[["isoverdue","set",null]]'
   columns:
@@ -300,7 +256,7 @@
       sort: 1
       width: 100
       heading: Ticket
-    - column_id: 10
+    - column_id: 9
       bits: 1
       sort: 1
       sort: 9
@@ -330,18 +286,3 @@
       sort: 6
       width: 160
       heading: Assigned To
-
-- title: Personal Tickets
-  parent_id: 3
-  flags: 0x2b
-  root: T
-  sort: 1
-  config: '{"criteria":[["assignee","includes",{"M":"Me"}]]}'
-
-- title: Teams Tickets
-  parent_id: 3
-  flags: 0x2b
-  root: T
-  sort: 2
-  config: '{"criteria":[["team_id","set",null]],"conditions":[]}'
-  filter: team_id
diff --git a/include/i18n/en_US/queue_column.yaml b/include/i18n/en_US/queue_column.yaml
index 1c1d011f5df1cef9b1cc08eb3f002c7124778113..6f7419a4af0e0074293b957b07f813668d1889e4 100644
--- a/include/i18n/en_US/queue_column.yaml
+++ b/include/i18n/en_US/queue_column.yaml
@@ -92,7 +92,8 @@
 
 - id: 9
   name: "Due Date"
-  primary: "est_duedate"
+  primary: "duedate"
+  secondary: "est_duedate"
   filter: "date:human"
   truncate: "wrap"
   annotations: "[]"
diff --git a/include/i18n/en_US/templates/email/ticket.autoreply.yaml b/include/i18n/en_US/templates/email/ticket.autoreply.yaml
index 39e8e4aa3fc7a1e56f1903096d5fa7a7a91b0cd9..824e2e5e1e862ce3fd238ae1d94c033a0354e323 100644
--- a/include/i18n/en_US/templates/email/ticket.autoreply.yaml
+++ b/include/i18n/en_US/templates/email/ticket.autoreply.yaml
@@ -33,7 +33,7 @@ body: |
     <hr>
     <div style="color: rgb(127, 127, 127); font-size: small;"><em>We hope
     this response has sufficiently answered your questions.  If you wish to
-    provide additional comments or informatione, please reply to this email
+    provide additional comments or information, please reply to this email
     or <a href="%{recipient.ticket_link}"><span
     style="color: rgb(84, 141, 212);" >login to your account</span></a> for
     a complete archive of your support requests.</em></div>
diff --git a/include/mysqli.php b/include/mysqli.php
index 998d286590d08ff8f8ea6c78ad072b28ee7600f2..efb459f334b9d8a6ffc7899fca51caf08d2f25cc 100644
--- a/include/mysqli.php
+++ b/include/mysqli.php
@@ -234,7 +234,7 @@ function db_fetch_field($res) {
     return ($res) ? $res->fetch_field() : NULL;
 }
 
-function db_assoc_array($res, $mode=false) {
+function db_assoc_array($res, $mode=MYSQLI_ASSOC) {
     $result = array();
     if($res && db_num_rows($res)) {
         while ($row=db_fetch_array($res, $mode))
diff --git a/include/staff/faq-view.inc.php b/include/staff/faq-view.inc.php
index a5dc4e56010c41ca059d7701d1bd19345c533661..531fbc2cd44bb603b860f5de9e4e833bcbaf2a88 100644
--- a/include/staff/faq-view.inc.php
+++ b/include/staff/faq-view.inc.php
@@ -41,7 +41,8 @@ if ($thisstaff->hasPerm(FAQ::PERM_MANAGE)) { ?>
 <?php foreach ($attachments as $att) { ?>
 <div>
     <i class="icon-paperclip pull-left"></i>
-    <a target="_blank" href="<?php echo $att->file->getDownloadUrl(); ?>"
+    <a target="_blank" href="<?php echo $att->file->getDownloadUrl(['id' =>
+    $att->getId()]); ?>"
         class="attachment no-pjax">
         <?php echo Format::htmlchars($att->getFilename()); ?>
     </a>
diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php
index ba0c06ce58ccdd35b8dadcdc9001362477da7b83..5816b4bf388cb147566688be145c9d5f8d72e6c8 100644
--- a/include/staff/filter.inc.php
+++ b/include/staff/filter.inc.php
@@ -240,18 +240,16 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 if ($filter) { foreach ($filter->getActions() as $A) {
                     $_warn = '';
                     $existing[] = $A->type;
+                    $config = JsonDataParser::parse($A->configuration);
                     if($A->type == 'dept') {
                       $errors['topic_id'] = '';
-                      // $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
-                      $dept_config = $A->parseConfiguration($_POST);
-                      $dept = Dept::lookup($dept_config['dept_id']);
+                      $dept = Dept::lookup($config['dept_id']);
                       if($dept && !$dept->isActive())
                         $_warn = sprintf(__('%s must be active'), __('Department'));
                     }
                     elseif($A->type == 'topic') {
                       $errors['dept_id'] = '';
-                      $topic_config = $A->parseConfiguration($_POST);
-                      $topic = Topic::lookup($topic_config['topic_id']);
+                      $topic = Topic::lookup($config['topic_id']);
                       if($topic && !$topic->isActive())
                         $_warn = sprintf(__('%s must be active'), __('Help Topic'));
                     }
diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php
index bf41bd1a48b42204c95fbec0b7fda987229c032a..8506fbee9d1ce3d78b429fe74c969f7f2771f158 100644
--- a/include/staff/queue.inc.php
+++ b/include/staff/queue.inc.php
@@ -57,11 +57,11 @@ else {
     <table class="table">
       <td style="width:60%; vertical-align:top">
         <div><strong><?php echo __('Queue Name'); ?>:</strong></div>
-        <input type="text" name="name" value="<?php
+        <input type="text" name="queue-name" value="<?php
           echo Format::htmlchars($queue->getName()); ?>"
           style="width:100%" />
-
         <br/>
+        <div class="error"><?php echo $errors['queue-name']; ?></div>
         <br/>
         <div><strong><?php echo __("Queue Search Criteria"); ?></strong></div>
         <label class="checkbox" style="line-height:1.3em">
@@ -120,10 +120,8 @@ else {
             if ($queue->parent
                 && ($qf = $queue->parent->getQuickFilterField()))
                 echo sprintf(' (%s)', $qf->getLabel()); ?> —</option>
-<?php foreach (CustomQueue::getSearchableFields('Ticket') as $path=>$f) {
+<?php foreach ($queue->getSupportedFilters() as $path => $f) {
         list($label, $field) = $f;
-        if (!$field->supportsQuickFilter())
-          continue;
 ?>
           <option value="<?php echo $path; ?>"
             <?php if ($path == $queue->filter) echo 'selected="selected"'; ?>
@@ -314,8 +312,8 @@ var Q = setInterval(function() {
 }();
 </script>
         </table>
-    </div>    
-    
+    </div>
+
     <div class="hidden tab_content" id="preview-tab">
     <div id="preview">
     </div>
@@ -339,7 +337,7 @@ var Q = setInterval(function() {
 
   <div class="hidden tab_content" id="conditions-tab">
     <div style="margin-bottom: 15px"><?php echo __("Conditions are used to change the view of the data in a row based on some conditions of the data. For instance, a column might be shown bold if some condition is met.");
-      ?> <?php echo __("These conditions apply to an entire row in the queue."); 
+      ?> <?php echo __("These conditions apply to an entire row in the queue.");
     ?></div>
     <div class="conditions">
 <?php
diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php
index 254d1559d6407d5e15d522185e2c62778ff567f4..9362db27d55bd3022505be2f6900907e3dd21a81 100644
--- a/include/staff/team.inc.php
+++ b/include/staff/team.inc.php
@@ -231,7 +231,7 @@ $(document).on('click', 'a.drop-membership', function() {
 });
 
 <?php
-if ($team) {
+if ($team && $team->members) {
     foreach ($team->members->sort(function($a) { return $a->staff->getName(); }) as $member) {
         echo sprintf('addMember(%d, %s, %d, %s);',
             $member->staff_id,
diff --git a/include/staff/templates/advanced-search-criteria.tmpl.php b/include/staff/templates/advanced-search-criteria.tmpl.php
index 136c7d139bb7a35d382757fe2e534230f79d997a..9348cf79be9a81e3cb762f1bcaf96604acedf437 100644
--- a/include/staff/templates/advanced-search-criteria.tmpl.php
+++ b/include/staff/templates/advanced-search-criteria.tmpl.php
@@ -1,9 +1,29 @@
 <?php
-foreach ($form->errors(true) ?: array() as $message) {
-    ?><div class="error-banner"><?php echo $message;?></div><?php
+// Display errors if any
+foreach ($form->errors(true) ?: array() as $message)
+    echo sprintf('<div class="error-banner">%s</div>',
+            Format::htmlchars($message));
+// Current search fields.
+$info = $search->getSearchFields($form) ?: array();
+if (($search instanceof SavedQueue) && !$search->checkOwnership($thisstaff)) {
+    $matches = $search->getSupplementalMatches();
+    // Uneditable core criteria for the queue
+    echo '<div class="faded">'.  nl2br(Format::htmlchars($search->describeCriteria())).
+                    '</div><br>';
+    // Show any supplemental filters
+    if ($matches  && count($info)) {
+        ?>
+        <div id="ticket-flags"
+            style="padding:5px; border-top: 1px dotted #777;">
+            <strong><i class="icon-caret-down"></i>&nbsp;<?php
+                echo __('Supplemental Filters'); ?></strong>
+        </div>
+<?php
+    }
+} else {
+    $matches = $search->getSupportedMatches();
 }
 
-$info = $search->getSearchFields($form);
 foreach (array_keys($info) as $F) {
     ?><input type="hidden" name="fields[]" value="<?php echo $F; ?>"/><?php
 }
@@ -51,8 +71,7 @@ foreach ($form->getFields() as $name=>$field) {
     $this.closest('.adv-search-field-container').find('.adv-search-field-body').slideDown('fast');
     $this.find('span.faded').hide();
     $this.find('i').removeClass('icon-caret-right').addClass('icon-caret-down');
-    return false;
-"><i class="icon-caret-right"></i>
+    return false; "><i class="icon-caret-right"></i>
             <span class="faded"><?php echo $search->describeField($info[$name]); ?></span>
             </a>
             </span>
@@ -62,21 +81,20 @@ foreach ($form->getFields() as $name=>$field) {
         } ?>
     </fieldset>
     <?php if ($name[0] == ':' && substr($name, -7) == '+search') {
-        list($N,) = explode('+', $name, 2);
-?>
+        list($N,) = explode('+', $name, 2); ?>
     <input type="hidden" name="fields[]" value="<?php echo $N; ?>"/>
     <?php }
 }
 if (!$first_field)
     echo '</div></div>';
-?>
+
+if ($matches && is_array($matches)) { ?>
 <div id="extra-fields"></div>
 <hr/>
 <i class="icon-plus-sign"></i>
 <select id="search-add-new-field" name="new-field" style="max-width: 300px;">
     <option value="">— <?php echo __('Add Other Field'); ?> —</option>
 <?php
-if (is_array($matches)) {
 foreach ($matches as $path => $F) {
     # Skip fields already listed above the drop-down
     if (isset($already_listed[$path]))
@@ -86,7 +104,7 @@ foreach ($matches as $path => $F) {
         if (isset($state[$path])) echo 'disabled="disabled"';
         ?>><?php echo $label; ?></option>
 <?php }
-} ?>
+?>
 </select>
 <script>
 $(function() {
@@ -100,9 +118,12 @@ $(function() {
         if (!json.success)
           return false;
         $(that).find(':selected').prop('disabled', true);
+        $(that).find('option:eq("")').prop('selected', true);
         $('#extra-fields').append($(json.html));
       }
     });
   });
 });
 </script>
+<?php
+} ?>
diff --git a/include/staff/templates/advanced-search.tmpl.php b/include/staff/templates/advanced-search.tmpl.php
index 71d2deedcb0314a1dd4355bad643fb42a68d363d..0fe2b99dcb420a7d6297e18e8c8426afbed7b727 100644
--- a/include/staff/templates/advanced-search.tmpl.php
+++ b/include/staff/templates/advanced-search.tmpl.php
@@ -1,11 +1,15 @@
 <?php
+global $thisstaff;
+
 $parent_id = $_REQUEST['parent_id'] ?: $search->parent_id;
 if ($parent_id
-    && (!($parent = CustomQueue::lookup($parent_id)))
+    && is_numeric($parent_id)
+    && (!($parent = SavedQueue::lookup($parent_id)))
 ) {
     $parent_id = 0;
 }
 
+$editable = $search->checkOwnership($thisstaff);
 $queues = array();
 foreach (CustomQueue::queues() as  $q)
     $queues[$q->id] = $q->getFullName();
@@ -26,10 +30,21 @@ if ($info['error']) {
     echo sprintf('<p id="msg_warning">%s</p>', $info['warn']);
 } elseif ($info['msg']) {
     echo sprintf('<p id="msg_notice">%s</p>', $info['msg']);
-} ?>
-<form action="#tickets/search" method="post" name="search" id="advsearch"
+}
+
+// Form action
+$action = '#tickets/search';
+if ($search->isSaved() && $search->getId())
+    $action .= sprintf('/%s/save', $search->getId());
+elseif (!$search instanceof AdhocSearch)
+    $action .= '/save';
+?>
+<form action="<?php echo $action; ?>" method="post" name="search" id="advsearch"
     class="<?php echo ($search->isSaved() || $parent) ? 'savedsearch' : 'adhocsearch'; ?>">
   <input type="hidden" name="id" value="<?php echo $search->getId(); ?>">
+<?php
+if ($editable) {
+    ?>
   <div class="flex row">
     <div class="span12">
       <select id="parent" name="parent_id" >
@@ -43,10 +58,16 @@ foreach ($queues as $id => $name) {
       </select>
     </div>
    </div>
+<?php
+} ?>
 <ul class="clean tabs">
     <li class="active"><a href="#criteria"><i class="icon-search"></i> <?php echo __('Criteria'); ?></a></li>
     <li><a href="#columns"><i class="icon-columns"></i> <?php echo __('Columns'); ?></a></li>
-    <li><a href="#fields"><i class="icon-download"></i> <?php echo __('Export'); ?></a></li>
+    <?php
+    if ($search->isSaved()) { ?>
+    <li><a href="#settings"><i class="icon-cog"></i> <?php echo __('Settings'); ?></a></li>
+    <?php
+    } ?>
 </ul>
 
 <div class="tab_content" id="criteria">
@@ -68,51 +89,80 @@ foreach ($queues as $id => $name) {
       </div>
       </div>
       <input type="hidden" name="a" value="search">
-      <?php include STAFFINC_DIR . 'templates/advanced-search-criteria.tmpl.php'; ?>
+     <?php
+        include STAFFINC_DIR . 'templates/advanced-search-criteria.tmpl.php';
+     ?>
     </div>
   </div>
 
 </div>
 
-<div class="tab_content hidden" id="columns" style="overflow-y: auto;
-height:auto;">
+<div class="tab_content hidden" id="columns">
     <?php
     include STAFFINC_DIR . "templates/queue-columns.tmpl.php";
     ?>
 </div>
-<div class="tab_content hidden" id="fields">
+<?php
+if ($search->isSaved()) { ?>
+<div class="tab_content hidden" id="settings">
     <?php
-    include STAFFINC_DIR . "templates/queue-fields.tmpl.php";  ?>
+    include STAFFINC_DIR . "templates/savedqueue-settings.tmpl.php";
+    ?>
 </div>
-   <?php
-   $save = (($parent && !$search->isSaved()) || $errors); ?>
+<?php
+} else { // Not saved.
+    $save = (($parent && !$search->isSaved()) || isset($_POST['queue-name']));
+?>
+<div>
   <div style="margin-top:10px;"><a href="#"
     id="save"><i class="icon-caret-<?php echo $save ? 'down' : 'right';
     ?>"></i>&nbsp;<span><?php echo __('Save Search'); ?></span></a></div>
   <div id="save-changes" class="<?php echo $save ? '' : 'hidden'; ?>" style="padding:5px; border-top: 1px dotted #777;">
-      <div><input name="name" type="text" size="40"
-        value="<?php echo $search->isSaved() ? Format::htmlchars($search->getName()) : ''; ?>"
+      <div><input name="queue-name" type="text" size="40"
+        value="<?php echo Format::htmlchars($search->isSaved() ? $search->getName() :
+        $_POST['queue-name']); ?>"
         placeholder="<?php echo __('Search Title'); ?>">
+        <?php
+        if ($search instanceof AdhocSearch && !$search->isSaved()) { ?>
         <span class="buttons">
-             <button class="button" type="button" name="save"
+             <button class="save button" type="button"  name="save-search"
              value="save"><i class="icon-save"></i>  <?php echo $search->id
              ? __('Save Changes') : __('Save'); ?></button>
         </span>
+        <?php
+        } ?>
         </div>
-      <div class="error" id="name-error"><?php echo Format::htmlchars($errors['name']); ?></div>
+      <div class="error" id="name-error"><?php echo
+      Format::htmlchars($errors['queue-name']); ?></div>
   </div>
+ </div>
+<?php
+} ?>
   <hr/>
  <div>
   <p class="full-width">
     <span class="buttons pull-left">
-        <input type="reset"  id="reset"  value="<?php echo __('Reset'); ?>">
-        <input type="button" name="cancel" class="close"
-        value="<?php echo __('Cancel'); ?>">
+        <input type="button"  name="cancel"  class="close" value="<?php echo __('Cancel'); ?>">
+        <?php
+        if ($search->isSaved()) { ?>
+        <input type="button" name="done" class="done" value="<?php echo
+            __('Done'); ?>" >
+        <?php
+        } ?>
     </span>
     <span class="buttons pull-right">
+      <?php
+      if (!$search instanceof AdhocSearch) { ?>
+      <button class="save button" type="submit" name="save" value="save"
+        id="do_save"><i class="icon-save"></i>
+        <?php echo __('Save'); ?></button>
+      <?php
+      } else { ?>
       <button class="button" type="submit" name="submit" value="search"
         id="do_search"><i class="icon-search"></i>
         <?php echo __('Search'); ?></button>
+      <?php
+      } ?>
     </span>
    </p>
  </div>
@@ -185,32 +235,45 @@ height:auto;">
         return false;
     });
 
-    $('form.savedsearch').on('keyup change paste', 'input, select, textarea', function() {
-       var form = $(this).closest('form');
-       $this = $('#save-changes', form);
-       if ($this.is(":hidden"))
-           $this.fadeIn();
-        $('a#save').find('i').removeClass('icon-caret-right').addClass('icon-caret-down');
-        $('button[name=save]', form).addClass('save pending');
-        $('div.error', form).html('');
+    $('form#advsearch').on('keyup change paste', 'input, select, textarea', function() {
+
+        var form = $(this).closest('form');
+        $this = $('#save-changes', form);
+        $('button.save', form).addClass('save pending');
+        $('div.error, div.error-banner', form).html('').hide();
      });
 
     $(document).on('click', 'form#advsearch input#reset', function(e) {
         var f = $(this).closest('form');
-        $('button[name=save]', f).removeClass('save pending');
+        $('button.save', f).removeClass('save pending');
         $('div#save-changes', f).hide();
     });
 
-    $('button[name=save]').click(function() {
+    $('button[name=save-search]').click(function() {
         var $form = $(this).closest('form');
         var id = parseInt($('input[name=id]', $form).val(), 10) || 0;
-        var action = '#tickets/search';
-        if (id > 0)
-            action = action + '/'+id;
+        var name = $('input[name=queue-name]', $form).val();
+        if (name.length) {
+            var action = '#tickets/search';
+            if (id > 0)
+                action = action + '/'+id;
+            $form.prop('action', action+'/save');
+            $form.submit();
+        } else {
+            $('div#name-error', $form).html('<?php echo __('Name required');
+                    ?>').show();
+        }
 
-        $form.prop('action', action+'/save');
-        $form.submit();
+        return false;
     });
 
+    $('input.done').click(function() {
+        var $form = $(this).closest('form');
+        var id = parseInt($('input[name=id]', $form).val(), 10) || 0;
+        if ($('button.save', $form).hasClass('pending'))
+            alert('Unsaved Changes - save or cancel to discard!');
+        else
+            window.location.href = 'tickets.php?queue='+id;
+    });
 }();
 </script>
diff --git a/include/staff/templates/collaborators.tmpl.php b/include/staff/templates/collaborators.tmpl.php
index 7f80a5804053f2c9b9ecf23d9880f4d5555f7b75..1a50164f1f78421050098dfef38f89de93d9cf0b 100644
--- a/include/staff/templates/collaborators.tmpl.php
+++ b/include/staff/templates/collaborators.tmpl.php
@@ -65,7 +65,7 @@ if(($users=$thread->getCollaborators())) {?>
     ?>
     <td>
       <div><a class="collaborator" id="addcollaborator"
-          href="#thread/<?php echo $thread->getId(); ?>/add-collaborator"
+          href="#thread/<?php echo $thread->getId(); ?>/add-collaborator/addcc"
           ><i class="icon-plus-sign"></i> <?php echo __('Add Collaborator'); ?></a></div>
     </td>
     </table>
diff --git a/include/staff/templates/note.tmpl.php b/include/staff/templates/note.tmpl.php
index f36f1d81a38fdb3ff33861d4fe22f54953bdf10a..4ceae8f1bd7d68655cdef2b451691fb45e60a701 100644
--- a/include/staff/templates/note.tmpl.php
+++ b/include/staff/templates/note.tmpl.php
@@ -7,7 +7,8 @@
         </div>
         <div class="header-right">
 <?php
-            echo $note->getStaff()->getName();
+$staff = $note->getStaff();
+echo $staff ? $staff->getName() : _('Staff');
 if (isset($show_options) && $show_options) { ?>
             <div class="options no-pjax">
                 <a href="#" class="action edit-note" title="edit"><i class="icon-pencil"></i></a>
diff --git a/include/staff/templates/queue-columns.tmpl.php b/include/staff/templates/queue-columns.tmpl.php
index 233a7c411defb2f853a22d3563502719efa4f262..feaf7cd4ef6163d89d99289bd23b294c01515540 100644
--- a/include/staff/templates/queue-columns.tmpl.php
+++ b/include/staff/templates/queue-columns.tmpl.php
@@ -1,3 +1,4 @@
+<div style="overflow-y: auto; height:auto; max-height: 350px;">
 <table class="table">
 <?php
 if ($queue->parent) { ?>
@@ -12,24 +13,22 @@ if ($queue->parent) { ?>
       </td>
     </tr>
   </tbody>
-<?php }
-      // Adhoc Advanced search does not have customizable columns, but saved
-      // ones do
-      elseif ($queue->__new__) { ?>
+<?php } elseif ($queue instanceof SavedQueue) { ?>
   <tbody>
     <tr>
       <td colspan="3">
         <input type="checkbox" name="inherit-columns" <?php
-          if (count($queue->columns) == 0) echo 'checked="checked"';
-          if ($queue instanceof SavedSearch) echo 'disabled="disabled"'; ?>
-          onchange="javascript:$(this).closest('table').find('.if-not-inherited').toggle(!$(this).prop('checked'));" />
+          if ($queue->useStandardColumns()) echo 'checked="checked"';
+          if ($queue instanceof SavedSearch && $queue->__new__) echo 'disabled="disabled"'; ?>
+          onchange="javascript:$(this).closest('table').find('.if-not-inherited').toggle(!$(this).prop('checked'));
+          $(this).closest('table').find('.standard-columns').toggle($(this).prop('checked'));" />
         <?php echo __('Use standard columns'); ?>
         <br /><br />
       </td>
     </tr>
   </tbody>
 <?php }
-$hidden_cols = $queue->inheritColumns() || count($queue->columns) === 0;
+$hidden_cols = $queue->inheritColumns() || $queue->useStandardColumns();
 ?>
   <tbody class="if-not-inherited <?php if ($hidden_cols) echo 'hidden'; ?>">
     <tr class="header">
@@ -92,17 +91,34 @@ $hidden_cols = $queue->inheritColumns() || count($queue->columns) === 0;
       </td>
     </tr>
   </tbody>
+  <tbody class="standard-columns <?php if (!$hidden_cols) echo 'hidden'; ?>">
+    <?php
+    foreach ($queue->getStandardColumns() as $c) { ?>
+    <tr>
+      <td nowrap><?php echo Format::htmlchars($c->heading); ?></td>
+      <td nowrap><?php echo Format::htmlchars($c->name); ?></td>
+      <td>&nbsp;</td>
+    </tr>
+    <?php
+    } ?>
+  </tbody>
 </table>
-
+</div>
 <script>
 +function() {
+$('[name=inherit-columns]').on('click', function() {
+    $('.standard-columns').toggle();
+});
 var Q = setInterval(function() {
   if ($('#append-column').length == 0)
     return;
   clearInterval(Q);
 
   var addColumn = function(colid, info) {
-    if (!colid) return;
+
+    if (!colid || $('tr#column-'+colid).length)
+        return;
+
     var copy = $('#column-template').clone(),
         name_prefix = 'columns[' + colid + ']';
     info['column_id'] = colid;
@@ -119,7 +135,7 @@ var Q = setInterval(function() {
       $this.attr('name', name_prefix + '[' + name + ']');
     });
     copy.find('span').text(info['name']);
-    copy.attr('id', '').show().insertBefore($('#column-template'));
+    copy.attr('id', 'column-'+colid).show().insertBefore($('#column-template'));
     copy.removeClass('hidden');
     if (info['trans'] !== undefined) {
       var input = copy.find('input[data-translate-tag]')
diff --git a/include/staff/templates/queue-savedsearches-nav.tmpl.php b/include/staff/templates/queue-savedsearches-nav.tmpl.php
index d6c57fdaa6d4569199f6dfeb2ebe41f809568e17..4899b794a2edce13e0cc6009f6f23beb883cb1e4 100644
--- a/include/staff/templates/queue-savedsearches-nav.tmpl.php
+++ b/include/staff/templates/queue-savedsearches-nav.tmpl.php
@@ -18,7 +18,6 @@
       <!-- 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
             ))
diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php
index c03cfacdb2de879337a7640926d3c5acd7f880fd..37d7cced74789b85fd6aa1f6cf6b0dc720eacc30 100644
--- a/include/staff/templates/queue-tickets.tmpl.php
+++ b/include/staff/templates/queue-tickets.tmpl.php
@@ -3,9 +3,10 @@
 // $tickets - <QuerySet> with all columns and annotations necessary to
 //      render the full page
 
+
 // Impose visibility constraints
 // ------------------------------------------------------------
-if (!($queue->ignoreVisibilityConstraints()))
+if (!$queue->ignoreVisibilityConstraints($thisstaff))
     $tickets->filter($thisstaff->getTicketsVisibility());
 
 // Make sure the cdata materialized view is available
@@ -121,62 +122,37 @@ return false;">
             <div class="pull-left flush-left">
                 <h2><a href="<?php echo $refresh_url; ?>"
                     title="<?php echo __('Refresh'); ?>"><i class="icon-refresh"></i> <?php echo
-                    $queue->getName(); ?></a></h2>
+                    $queue->getName(); ?></a>
+                    <?php
+                    if (($crit=$queue->getSupplementalCriteria()))
+                        echo sprintf('<i class="icon-filter"
+                                data-placement="bottom" data-toggle="tooltip"
+                                title="%s"></i>&nbsp;',
+                                Format::htmlchars($queue->describeCriteria($crit)));
+                    ?>
+                </h2>
             </div>
             <div class="configureQ">
                 <i class="icon-cog"></i>
                 <div class="noclick-dropdown anchor-left">
                     <ul>
-<?php
-if ($queue->isPrivate()) { ?>
                         <li>
                             <a class="no-pjax" href="#"
                               data-dialog="ajax.php/tickets/search/<?php echo
                               urlencode($queue->getId()); ?>"><i
-                            class="icon-fixed-width icon-save"></i>
-                            <?php echo __('Edit'); ?></a>
-                        </li>
-<?php }
-else {
-    if ($thisstaff->isAdmin()) { ?>
-                        <li>
-                            <a class="no-pjax"
-                            href="queues.php?id=<?php echo $queue->id; ?>"><i
                             class="icon-fixed-width icon-pencil"></i>
                             <?php echo __('Edit'); ?></a>
                         </li>
-<?php }
-# Anyone has permission to create personal sub-queues
-?>
                         <li>
                             <a class="no-pjax" href="#"
-                              data-dialog="ajax.php/tickets/search?parent_id=<?php
-                              echo $queue->id; ?>"><i
+                              data-dialog="ajax.php/tickets/search/create?pid=<?php
+                              echo $queue->getId(); ?>"><i
                             class="icon-fixed-width icon-plus-sign"></i>
-                            <?php echo __('Add Personal Queue'); ?></a>
-                        </li>
-<?php
-}
-if ($thisstaff->isAdmin()) { ?>
-                        <li>
-                            <a class="no-pjax"
-                            href="queues.php?a=sub&amp;id=<?php echo $queue->id; ?>"><i
-                            class="icon-fixed-width icon-level-down"></i>
                             <?php echo __('Add Sub Queue'); ?></a>
                         </li>
-                        <li>
-                            <a class="no-pjax"
-                            href="queues.php?a=clone&amp;id=<?php echo $queue->id; ?>"><i
-                            class="icon-fixed-width icon-copy"></i>
-                            <?php echo __('Clone'); ?></a>
-                        </li>
-<?php }
-if (
-    $queue->id > 0
-    && (
-        ($thisstaff->isAdmin() && $queue->parent_id)
-        || $queue->isPrivate()
-)) { ?>
+<?php
+
+if ($queue->id > 0 && $queue->isOwner($thisstaff)) { ?>
                         <li class="danger">
                             <a class="no-pjax confirm-action" href="#"
                                 data-dialog="ajax.php/queue/<?php
diff --git a/include/staff/templates/savedqueue-settings.tmpl.php b/include/staff/templates/savedqueue-settings.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..42d36d57bf1006021c67e219c57e357a83cf5568
--- /dev/null
+++ b/include/staff/templates/savedqueue-settings.tmpl.php
@@ -0,0 +1,63 @@
+<div style="overflow-y: auto; height:auto; max-height: 350px;">
+ <div>
+    <div class="faded"><strong><?php echo __('Name'); ?></strong></div>
+    <div>
+    <?php
+    if ($queue->checkOwnership($thisstaff)) { ?>
+    <input name="queue-name" type="text" size="40"
+        value="<?php echo Format::htmlchars($queue->getName()); ?>"
+        placeholder="<?php echo __('Search Title'); ?>">
+    <?php
+    } else {
+        echo Format::htmlchars($queue->getName());
+    } ?>
+    </div>
+    <div class="error" id="name-error"><?php echo
+    Format::htmlchars($errors['queue-name']); ?></div>
+ </div>
+ <div>
+    <div class="faded"><strong><?php echo __("Quick Filter"); ?></strong></div>
+    <div>
+        <select name="filter">
+          <option value="" <?php if ($queue->filter == "")
+              echo 'selected="selected"'; ?>>— <?php echo __('None'); ?> —</option>
+          <option value="::" <?php if ($queue->filter == "::")
+              echo 'selected="selected"'; ?>>— <?php echo __('Inherit from parent');
+            if ($queue->parent
+                && ($qf = $queue->parent->getQuickFilterField()))
+                echo sprintf(' (%s)', $qf->getLabel()); ?> —</option>
+<?php foreach ($queue->getSupportedFilters() as $path => $f) {
+        list($label, $field) = $f;
+?>
+          <option value="<?php echo $path; ?>"
+            <?php if ($path == $queue->filter) echo 'selected="selected"'; ?>
+            ><?php echo Format::htmlchars($label); ?></option>
+<?php } ?>
+         </select>
+      </div>
+        <div class="error"><?php
+            echo Format::htmlchars($errors['filter']); ?></div>
+ </div>
+ <div>
+    <div class="faded"><strong><?php echo __("Defaut Sorting"); ?></strong></div>
+    <div>
+        <select name="sort_id">
+         <option value="" <?php if ($queue->sort_id == 0)
+            echo 'selected="selected"'; ?>>— <?php echo __('None'); ?> —</option>
+          <option value="::" <?php if ($queue->isDefaultSortInherited() &&
+                  $queue->parent)
+              echo 'selected="selected"'; ?>>— <?php echo __('Inherit from parent');
+            if ($queue->parent
+                && ($sort = $queue->parent->getDefaultSort()))
+                echo sprintf(' (%s)', $sort->getName()); ?> —</option>
+<?php foreach ($queue->getSortOptions() as $sort) { ?>
+          <option value="<?php echo $sort->id; ?>"
+            <?php if ($sort->id == $queue->sort_id) echo 'selected="selected"'; ?>
+            ><?php echo Format::htmlchars($sort->getName()); ?></option>
+<?php } ?>
+        </select>
+      </div>
+        <div class="error"><?php
+            echo Format::htmlchars($errors['sort_id']); ?></div>
+  </div>
+</div>
diff --git a/include/staff/templates/status-options.tmpl.php b/include/staff/templates/status-options.tmpl.php
index c8cff18a94cc8b06535f0ecd017a2b87df9fd841..16d0f885bf3bb4813865d8ebbdc3171cf5be49b1 100644
--- a/include/staff/templates/status-options.tmpl.php
+++ b/include/staff/templates/status-options.tmpl.php
@@ -1,5 +1,10 @@
 <?php
 global $thisstaff, $ticket;
+
+$role = $ticket ? $ticket->getRole($thisstaff) : $thisstaff->getRole();
+if ($role && !$role->hasPerm(Ticket::PERM_CLOSE))
+    return;
+
 // Map states to actions
 $actions= array(
         'closed' => array(
@@ -14,9 +19,8 @@ $actions= array(
         );
 
 $states = array('open');
-if ($thisstaff->getRole($ticket ? $ticket->getDeptId() : null)->hasPerm(Ticket::PERM_CLOSE)
-        && (!$ticket || !Ticket::getMissingRequiredFields($ticket)))
-    $states = array_merge($states, array('closed'));
+if (!$ticket || $ticket->isCloseable())
+    $states[] = 'closed';
 
 $statusId = $ticket ? $ticket->getStatusId() : 0;
 $nextStatuses = array();
diff --git a/include/staff/templates/task-view.tmpl.php b/include/staff/templates/task-view.tmpl.php
index 0f6d44adf483efa54507354218d700bdb1c9b1eb..adb250728e1ae3bd19c355e78974120bc39237a5 100644
--- a/include/staff/templates/task-view.tmpl.php
+++ b/include/staff/templates/task-view.tmpl.php
@@ -1,7 +1,7 @@
 <?php
 if (!defined('OSTSCPINC')
     || !$thisstaff || !$task
-    || !($role = $thisstaff->getRole($task->getDeptId())))
+    || !($role = $thisstaff->getRole($task->getDept())))
     die('Invalid path');
 
 global $cfg;
diff --git a/include/staff/templates/thread-entries.tmpl.php b/include/staff/templates/thread-entries.tmpl.php
index 0d15aa0c9f15dfd410a9c60914a5c17ef67d3ab5..9b267bbae835c20484b484db1e963a1d4e5db2a0 100644
--- a/include/staff/templates/thread-entries.tmpl.php
+++ b/include/staff/templates/thread-entries.tmpl.php
@@ -80,7 +80,8 @@ foreach (Attachment::objects()->filter(array(
                 if (!$A->inline)
                     continue;
                 $urls[strtolower($A->file->getKey())] = array(
-                    'download_url' => $A->file->getDownloadUrl(),
+                    'download_url' => $A->file->getDownloadUrl(['id' =>
+                        $A->getId()]),
                     'filename' => $A->getFilename(),
                 );
             }
diff --git a/include/staff/templates/thread-entry.tmpl.php b/include/staff/templates/thread-entry.tmpl.php
index d36d2b7c9ab08e1e50e14829fa7726cbe5ee2050..5781db359fb9fe1383bd8f2235f536bb2f910774 100644
--- a/include/staff/templates/thread-entry.tmpl.php
+++ b/include/staff/templates/thread-entry.tmpl.php
@@ -101,7 +101,8 @@ if ($entry->flags & ThreadEntry::FLAG_COLLABORATOR && $entry->type == 'N') {
 ?>
         <span class="attachment-info">
         <i class="icon-paperclip icon-flip-horizontal"></i>
-        <a class="no-pjax truncate filename" href="<?php echo $A->file->getDownloadUrl();
+        <a class="no-pjax truncate filename" href="<?php echo
+        $A->file->getDownloadUrl(['id' => $A->getId()]);
             ?>" download="<?php echo Format::htmlchars($A->getFilename()); ?>"
             target="_blank"><?php echo Format::htmlchars($A->getFilename());
         ?></a><?php echo $size;?>
diff --git a/include/staff/templates/ticket-preview.tmpl.php b/include/staff/templates/ticket-preview.tmpl.php
index b615e37971f7fe9b7f4be23bc547192f752f2893..4c3bbc888249a0e9526f017d6028b7e7d9477a6d 100644
--- a/include/staff/templates/ticket-preview.tmpl.php
+++ b/include/staff/templates/ticket-preview.tmpl.php
@@ -6,7 +6,7 @@
 
 $staff=$ticket->getStaff();
 $lock=$ticket->getLock();
-$role=$thisstaff->getRole($ticket->getDeptId());
+$role=$ticket->getRole($thisstaff);
 $error=$msg=$warn=null;
 $thread = $ticket->getThread();
 
diff --git a/include/staff/templates/users.tmpl.php b/include/staff/templates/users.tmpl.php
index b34fa61111e05dac6726e6f379a1b67b6d88a83c..22eb67f25746a58937dd64f2d690f5666e1e6cda 100644
--- a/include/staff/templates/users.tmpl.php
+++ b/include/staff/templates/users.tmpl.php
@@ -1,16 +1,18 @@
 <?php
 $qs = array();
-$select = 'SELECT user.*, email.address as email ';
+$select = 'SELECT user.*, email.address as email, account.status as status, account.id as account_id ';
 
 $from = 'FROM '.USER_TABLE.' user '
-      . 'LEFT JOIN '.USER_EMAIL_TABLE.' email ON (user.id = email.user_id) ';
+      . 'LEFT JOIN '.USER_EMAIL_TABLE.' email ON (user.id = email.user_id) '
+      . 'LEFT JOIN '.USER_ACCOUNT_TABLE.' account ON (user.id = account.user_id) ';
 
 $where = ' WHERE user.org_id='.db_input($org->getId());
 
 $sortOptions = array('name' => 'user.name',
                      'email' => 'email.address',
                      'create' => 'user.created',
-                     'update' => 'user.updated');
+                     'update' => 'user.updated',
+                     'status' => 'account.status');
 $orderWays = array('DESC'=>'DESC','ASC'=>'ASC');
 $sort= ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name';
 //Sorting options...
@@ -83,9 +85,9 @@ if ($num) { ?>
     <thead>
         <tr>
             <th width="4%">&nbsp;</th>
-            <th width="38%"><?php echo __('Name'); ?></th>
-            <th width="35%"><?php echo __('Email'); ?></th>
-            <th width="8%"><?php echo __('Status'); ?></th>
+            <th width="30%"><?php echo __('Name'); ?></th>
+            <th width="33%"><?php echo __('Email'); ?></th>
+            <th width="18%"><?php echo __('Status'); ?></th>
             <th width="15%"><?php echo __('Created'); ?></th>
         </tr>
     </thead>
@@ -96,7 +98,10 @@ if ($num) { ?>
             while ($row = db_fetch_array($res)) {
 
                 $name = new UsersName($row['name']);
-                $status = 'Active';
+                if (!$row['account_id'])
+                    $status = __('Guest');
+                else
+                    $status = new UserAccountStatus($row['status']);
                 $sel=false;
                 if($ids && in_array($row['id'], $ids))
                     $sel=true;
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index d19138676399500753db5d7ff641711999031295..591442462ffc72772c684e119a35295506a93581 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -152,18 +152,8 @@ if ($_POST)
                 <tr class="no_border" id="ccRow">
                   <td width="160"><?php echo __('Cc'); ?>:</td>
                   <td>
-                      <select name="ccs[]" id="cc_users_open" multiple="multiple"
+                      <select class="collabSelections" name="ccs[]" id="cc_users_open" multiple="multiple"
                           data-placeholder="<?php echo __('Select Contacts'); ?>">
-                          <option value=""></option>
-                          <?php
-                          $users = User::objects();
-                          foreach ($users as $u) {
-                            if($user && $u->id != $user->getId()) {
-                          ?>
-                          <option value="<?php echo $u->id; ?>"
-                              ><?php echo $u->getName(); ?>
-                          </option>
-                              <?php } } ?>
                       </select>
                       <br/><span class="error"><?php echo $errors['ccs']; ?></span>
                   </td>
@@ -171,18 +161,8 @@ if ($_POST)
                 <tr class="no_border" id="bccRow">
                   <td width="160"><?php echo __('Bcc'); ?>:</td>
                   <td>
-                      <select name="bccs[]" id="bcc_users_open" multiple="multiple"
+                      <select class="collabSelections" name="bccs[]" id="bcc_users_open" multiple="multiple"
                           data-placeholder="<?php echo __('Select Contacts'); ?>">
-                          <option value=""></option>
-                          <?php
-                          $users = User::objects();
-                          foreach ($users as $u) {
-                            if($user && $u->id != $user->getId()) {
-                          ?>
-                          <option value="<?php echo $u->id; ?>"
-                              ><?php echo $u->getName(); ?>
-                          </option>
-                              <?php } } ?>
                       </select>
                       <br/><span class="error"><?php echo $errors['ccs']; ?></span>
                   </td>
@@ -534,9 +514,33 @@ $(function() {
         $('div#org-profile').fadeIn();
         return false;
     });
-    $("#cc_users_open").select2({width: '300px'});
-    $("#bcc_users_open").select2({width: '300px'});
-});
+
+    $('.collabSelections').select2({
+      width: '350px',
+      minimumInputLength: 3,
+      ajax: {
+        url: "ajax.php/users/local",
+        dataType: 'json',
+        data: function (params) {
+          return {
+            q: params.term,
+          };
+        },
+        processResults: function (data) {
+          return {
+            results: $.map(data, function (item) {
+              return {
+                text: item.name,
+                slug: item.slug,
+                id: item.id
+              }
+            })
+          };
+        }
+      }
+    });
+
+  });
 
 $(document).ready(function () {
     $('#emailcollab').on('change', function(){
diff --git a/include/staff/ticket-tasks.inc.php b/include/staff/ticket-tasks.inc.php
index a260f0f05b07e2427b973ccfd68f44ea9ce0e30c..aa765aef5cd39eebf3f68f53f4d02227f5ca4641 100644
--- a/include/staff/ticket-tasks.inc.php
+++ b/include/staff/ticket-tasks.inc.php
@@ -1,7 +1,7 @@
 <?php
 global $thisstaff;
 
-$role = $thisstaff->getRole($ticket->getDeptId());
+$role = $ticket->getRole($thisstaff);
 
 $tasks = Task::objects()
     ->select_related('dept', 'staff', 'team')
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index f5d7ae5f7ba3bf740000ed119c2e1429b2e3324e..b5ededef80a34f4bd784568e908c2edf1b8ef8fe 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -10,7 +10,7 @@ $info=($_POST && $errors)?Format::input($_POST):array();
 
 //Get the goodies.
 $dept  = $ticket->getDept();  //Dept
-$role  = $thisstaff->getRole($dept, $ticket->isAssigned($thisstaff));
+$role  = $ticket->getRole($thisstaff);
 $staff = $ticket->getStaff(); //Assigned or closed by..
 $user  = $ticket->getOwner(); //Ticket User (EndUser)
 $team  = $ticket->getTeam();  //Assigned team.
@@ -186,8 +186,10 @@ if($ticket->isOverdue())
                     return false"
                     ><i class="icon-paste"></i> <?php echo __('Manage Forms'); ?></a></li>
                 <?php
-                } ?>
+                }
 
+                if ($role->hasPerm(Ticket::PERM_REPLY)) {
+                    ?>
                 <li>
 
                     <?php
@@ -199,9 +201,12 @@ if($ticket->isOverdue())
                             $recipients);
                    ?>
                 </li>
+                <?php
+                } ?>
 
 
-<?php           if ($thisstaff->hasPerm(Email::PERM_BANLIST)) {
+<?php           if ($thisstaff->hasPerm(Email::PERM_BANLIST)
+                    && $role->hasPerm(Ticket::PERM_REPLY)) {
                      if(!$emailBanned) {?>
                         <li><a class="confirm-action" id="ticket-banemail"
                             href="#banemail"><i class="icon-ban-circle"></i> <?php echo sprintf(
@@ -576,13 +581,17 @@ if($ticket->isOverdue())
 <br>
 <?php
 foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) {
+    //Find fields to exclude if disabled by help topic
+    $disabled = Ticket::getMissingRequiredFields($ticket, true);
+
     // Skip core fields shown earlier in the ticket view
     // TODO: Rewrite getAnswers() so that one could write
     //       ->getAnswers()->filter(not(array('field__name__in'=>
     //           array('email', ...))));
     $answers = $form->getAnswers()->exclude(Q::any(array(
         'field__flags__hasbit' => DynamicFormField::FLAG_EXT_STORED,
-        'field__name__in' => array('subject', 'priority')
+        'field__name__in' => array('subject', 'priority'),
+        'field__id__in' => $disabled,
     )));
     $displayed = array();
     foreach($answers as $a) {
@@ -607,7 +616,14 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) {
               <a class="ticket-action" id="inline-update" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Update'); ?>"
                   data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>"
                   href="#tickets/<?php echo $ticket->getId(); ?>/field/<?php echo $id; ?>/edit">
-                  <?php echo $v; ?>
+                  <?php
+                    if (strlen($v) > 200) {
+                      echo Format::truncate($v, 200);
+                      echo "<br><i class=\"icon-edit\"></i>";
+                    }
+                    else
+                      echo $v;
+                  ?>
               </a>
             <?php } else {
                 echo $v;
@@ -695,15 +711,15 @@ if ($errors['err'] && isset($_POST['a'])) {
             <?php
             }?>
            <tbody id="to_sec">
+           <?php
+           # XXX: Add user-to-name and user-to-email HTML ID#s
+           if ($addresses = Email::getAddresses(array('smtp' => true))){
+           ?>
            <tr>
                <td width="120">
                    <label><strong><?php echo __('From'); ?>:</strong></label>
                </td>
                <td>
-                   <?php
-                   # XXX: Add user-to-name and user-to-email HTML ID#s
-                   $addresses = Email::getAddresses();
-                   ?>
                    <select id="from_name" name="from_name">
                      <?php
                      $sql=' SELECT email_id, email, name, smtp_host '
@@ -719,6 +735,7 @@ if ($errors['err'] && isset($_POST['a'])) {
                    </select>
                </td>
            </tr>
+           <?php } ?>
             <tr>
                 <td width="120">
                     <label><strong><?php echo __('To'); ?>:</strong></label>
@@ -772,20 +789,18 @@ if ($errors['err'] && isset($_POST['a'])) {
              <tr>
                  <td width="160"><b><?php echo __('Cc'); ?>:</b></td>
                  <td>
-                     <select name="ccs[]" id="cc_users" multiple="multiple"
+                     <select class="collabSelections" name="ccs[]" id="cc_users" multiple="multiple"
                          data-placeholder="<?php echo __('Select Contacts'); ?>">
-                         <option value=""></option>
-                         <option value="NEW">&mdash; <?php echo __('Add New');?> &mdash;</option>
                          <?php
-                         $users = User::objects();
-                         foreach ($users as $u) {
-                           if($u->id != $ticket->user_id && !in_array($u->getId(), $bcc_cids)) {
+                         foreach ($cc_cids as $u) {
+                           if($u != $ticket->user_id && !in_array($u, $bcc_cids)) {
+                             ?>
+                             <option value="<?php echo $u; ?>" <?php
+                             if (in_array($u, $cc_cids))
+                             echo 'selected="selected"'; ?>><?php echo User::lookup($u); ?>
+                           </option>
+                         <?php } } ?>
                          ?>
-                         <option value="<?php echo $u->id; ?>" <?php
-                            if (in_array($u->getId(), $cc_cids))
-                              echo 'selected="selected"'; ?>><?php echo $u->getName(); ?>
-                         </option>
-                             <?php } } ?>
                      </select>
                      <br/><span class="error"><?php echo $errors['ccs']; ?></span>
                  </td>
@@ -793,20 +808,18 @@ if ($errors['err'] && isset($_POST['a'])) {
              <tr>
                <td width="160"><b><?php echo __('Bcc'); ?>:</b></td>
                <td>
-                   <select name="bccs[]" id="bcc_users" multiple="multiple"
+                   <select class="collabSelections" name="bccs[]" id="bcc_users" multiple="multiple"
                        data-placeholder="<?php echo __('Select Contacts'); ?>">
-                       <option value=""></option>
-                       <option value="NEW">&mdash; <?php echo __('Add New');?> &mdash;</option>
                        <?php
-                       $users = User::objects();
-                       foreach ($users as $u) {
-                         if($u->id != $ticket->user_id && !in_array($u->getId(), $cc_cids)) {
+                       foreach ($bcc_cids as $u) {
+                         if($u != $ticket->user_id && !in_array($u, $cc_cids)) {
+                           ?>
+                           <option value="<?php echo $u; ?>" <?php
+                           if (in_array($u, $bcc_cids))
+                           echo 'selected="selected"'; ?>><?php echo User::lookup($u); ?>
+                         </option>
+                       <?php } } ?>
                        ?>
-                       <option value="<?php echo $u->id; ?>" <?php
-                           if (in_array($u->getId(), $bcc_cids))
-                           echo 'selected="selected"'; ?>><?php echo $u->getName(); ?>
-                       </option>
-                           <?php } } ?>
                    </select>
                    <br/><span class="error"><?php echo $errors['bccs']; ?></span>
                </td>
@@ -1176,76 +1189,72 @@ $(function() {
 });
 
 $(function() {
-    $("#cc_users").select2({width: '350px'});
-    $("#bcc_users").select2({width: '350px'});
-});
-
-$(function() {
-   $('#cc_users').on("select2:select", function(e) {
-     var el = $(this);
-     var tid = <?php echo $ticket->getThreadId(); ?>;
-
-    if(el.val().includes("NEW")) {
-      $("li[title='— Add New —']").remove();
-      var url = 'ajax.php/thread/' + tid + '/add-collaborator' ;
-       $.userLookup(url, function(user) {
-         e.preventDefault();
-          if($('.dialog#confirm-action').length) {
-              $('.dialog#confirm-action #action').val('addcc');
-              $('#confirm-form').append('<input type=hidden name=user_id value='+user.id+' />');
-              $('#overlay').show();
-          }
-       });
-          var arr = el.val();
-          var removeStr = "NEW";
-
-          arr.splice($.inArray(removeStr, arr),1);
-          $(this).val(arr);
-     }
-  });
-
-  $('#bcc_users').on("select2:select", function(e) {
-      var el = $(this);
-      var tid = <?php echo $ticket->getThreadId(); ?>;
+  $('.collabSelections').on("select2:select", function(e) {
+    var el = $(this);
+    var tid = <?php echo $ticket->getThreadId(); ?>;
+    var target = e.currentTarget.id;
+    var addTo = (target == 'cc_users') ? 'addcc' : 'addbcc';
 
-      if(el.val().includes("NEW")) {
-        $("li[title='— Add New —']").remove();
-        var url = 'ajax.php/thread/' + tid + '/add-collaborator' ;
-         $.userLookup(url, function(user) {
-            e.preventDefault();
-            if($('.dialog#confirm-action').length) {
-                $('.dialog#confirm-action #action').val('addbcc');
-                $('#confirm-form').append('<input type=hidden name=user_id value='+user.id+' />');
-                $('#overlay').show();
-            }
-         });
+   if(el.val().includes("NEW")) {
+     $("li[title='— Add New —']").remove();
+     var url = 'ajax.php/thread/' + tid + '/add-collaborator/' + addTo ;
+      $.userLookup(url, function(user) {
+        e.preventDefault();
+         if($('.dialog#confirm-action').length) {
+             $('.dialog#confirm-action #action').val(addTo);
+             $('#confirm-form').append('<input type=hidden name=user_id value='+user.id+' />');
+             $('#overlay').show();
+         }
+      });
          var arr = el.val();
          var removeStr = "NEW";
 
          arr.splice($.inArray(removeStr, arr),1);
          $(this).val(arr);
-      }
-  });
-
-  $('#cc_users').on("select2:unselecting", function(e) {
-      var confirmation = confirm(__("Are you sure you want to remove the collaborator from receiving this reply?"));
-      if (confirmation == false) {
-        $('#cc_users').on("select2:opening", function(e) {
-          return false;
-        });
-        return false;
-      }
-
+    }
  });
 
- $('#bcc_users').on("select2:unselecting", function(e) {
+ $('.collabSelections').on("select2:unselecting", function(e) {
+   var el = $(this);
+   var target = '#' + e.currentTarget.id;
      var confirmation = confirm(__("Are you sure you want to remove the collaborator from receiving this reply?"));
      if (confirmation == false) {
-         $('#bcc_users').on("select2:opening", function(e) {
-           return false;
-         });
+       $(target).on("select2:opening", function(e) {
          return false;
+       });
+       return false;
+     }
+
+});
+
+ $('.collabSelections').select2({
+   width: '350px',
+   minimumInputLength: 3,
+   ajax: {
+     url: "ajax.php/users/local",
+     dataType: 'json',
+     data: function (params) {
+       if (!params) {
+         params.term = 'test';
        }
+       return {
+         q: params.term,
+       };
+     },
+     processResults: function (data) {
+       data[0] = {name: "\u2014 Add New \u2014", id: "NEW"};
+       return {
+         results: $.map(data, function (item) {
+           return {
+             text: item.name,
+             slug: item.slug,
+             id: item.id
+           }
+         })
+       };
+     }
+   }
  });
+
 });
 </script>
diff --git a/include/staff/users.inc.php b/include/staff/users.inc.php
index 5d35e3f277a50c1d6dfabaabe2005cbeb1631cb6..04c292a0bbf5fbc66b47f99564eb94f1fae8abdc 100644
--- a/include/staff/users.inc.php
+++ b/include/staff/users.inc.php
@@ -312,6 +312,11 @@ $(function() {
         goBaby($(this).attr('href').substr(1));
         return false;
     });
+
+    // Remove CSRF Token From GET Request
+    document.querySelector("form[action='users.php']").onsubmit = function() {
+        document.getElementsByName("__CSRFToken__")[0].remove();
+    };
 });
 </script>
 
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 4ad11e1f415bd953c9e8a80441aa12cdb7bbf5dc..5c4c675cad5ffbfde918a49029645fc955d772dd 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-e7dfe82131b906a14f6a13163943855f
+70921d5c3920ab240b08bdd55bc894c8
diff --git a/include/upgrader/streams/core/934b8db8-ad9d0a5f.task.php b/include/upgrader/streams/core/934b8db8-ad9d0a5f.task.php
index f83f7c8b352f04e987b964dfdd57003742a32d28..886bcf6ebb0a7f53102efc66672e18073a032e29 100644
--- a/include/upgrader/streams/core/934b8db8-ad9d0a5f.task.php
+++ b/include/upgrader/streams/core/934b8db8-ad9d0a5f.task.php
@@ -21,10 +21,18 @@ class QueueSortCreator extends MigrationTask {
 
         // Re-insert old saved searches
         foreach ($old ?: array() as $row) {
+            // Only save entries with "valid" criteria
+            if (!$row['title']
+                    || !($config = JsonDataParser::parse($row['config'], true))
+                    || !($criteria = CustomQueue::isolateCriteria($criteria)))
+                continue;
+
+            $row['config'] = JsonDataEncoder::encode(array(
+                        'criteria' => $criteria, 'conditions' => array()));
             $row['root'] = 'T';
             CustomQueue::__create(array_intersect_key($row, array_flip(
                             array('staff_id', 'title', 'config', 'flags',
-                                'created', 'updated'))));
+                                'root', 'created', 'updated'))));
         }
 
         $columns = $i18n->getTemplate('queue_sort.yaml')->getData();
diff --git a/include/upgrader/streams/core/cce1ba43-e7dfe821.patch.sql b/include/upgrader/streams/core/cce1ba43-e7dfe821.patch.sql
index 660400dde7260d323533cfb805b7b8a94b8ec68c..233b1e1749703bae43e4d8b89664e97ee03d6a8f 100644
--- a/include/upgrader/streams/core/cce1ba43-e7dfe821.patch.sql
+++ b/include/upgrader/streams/core/cce1ba43-e7dfe821.patch.sql
@@ -9,6 +9,11 @@
 ALTER TABLE `%TABLE_PREFIX%faq_category`
     ADD `category_pid` int(10) unsigned DEFAULT NULL AFTER  `category_id`;
 
+-- Phone Field `name` and `flags`
+UPDATE `%TABLE_PREFIX%form_field`
+    SET `flags` = `flags` + 262144
+    WHERE `type` = 'phone' AND `name` = 'phone' AND `form_id` = 1 AND `id` < 10;
+
  -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
     SET `value` = 'e7dfe82131b906a14f6a13163943855f'
diff --git a/include/upgrader/streams/core/e7dfe821-70921d5c.patch.sql b/include/upgrader/streams/core/e7dfe821-70921d5c.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3547e6698f15cb0adcff927510968cb168bb1f08
--- /dev/null
+++ b/include/upgrader/streams/core/e7dfe821-70921d5c.patch.sql
@@ -0,0 +1,43 @@
+/**
+* @signature 70921d5c3920ab240b08bdd55bc894c8
+* @version v1.11.0
+* @title Make Public CustomQueues Configurable
+*
+* This patch adds staff_id to queue_columns table and queue_config table to
+* allow for ability to customize public queue columns as well as additional
+* settings
+*
+*/
+
+-- Add staff_id to queue_columns table
+ALTER TABLE `%TABLE_PREFIX%queue_columns`
+    ADD `staff_id` int(11) unsigned NOT NULL AFTER  `column_id`;
+
+-- Set staff_id to 0 for default columns
+UPDATE `%TABLE_PREFIX%queue_columns`
+    SET `staff_id` = 0;
+
+-- Add staff_id to PRIMARY KEY
+ALTER TABLE `%TABLE_PREFIX%queue_columns`
+    DROP PRIMARY KEY,
+    ADD PRIMARY KEY (`queue_id`, `column_id`, `staff_id`);
+
+-- Set staff_id to 0 for public queues
+UPDATE `%TABLE_PREFIX%queue`
+    SET `staff_id` = 0
+    WHERE (`flags` & 1) >0;
+
+-- Add bridge table for public Queues staff configuration & settings
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_config`;
+CREATE TABLE `%TABLE_PREFIX%queue_config` (
+  `queue_id` int(11) unsigned NOT NULL,
+  `staff_id` int(11) unsigned NOT NULL,
+  `setting` text,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`queue_id`,`staff_id`)
+) DEFAULT CHARSET=utf8;
+
+ -- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = '70921d5c3920ab240b08bdd55bc894c8'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/open.php b/open.php
index 450b9d1f60609daaa31e77673e4898d672230403..a507bc979515181f5d46eb035a8ccaf9ade92e59 100644
--- a/open.php
+++ b/open.php
@@ -82,7 +82,8 @@ if ($ticket
     echo Format::viewableImages(
         $ticket->replaceVars(
             $page->getLocalBody()
-        )
+        ),
+        ['type' => 'P']
     );
 }
 else {
diff --git a/scp/ajax.php b/scp/ajax.php
index 039af6b3e48d838eeabd2506dffbfc67c3055ed7..85d010e92b48b47d39643715bd8d9884c4c040c8 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -177,6 +177,7 @@ $dispatcher = patterns('',
             url_post('^$', 'doSearch'),
             url_get('^/(?P<id>\d+)$', 'editSearch'),
             url_get('^/adhoc,(?P<key>[\w=/+]+)$', 'getAdvancedSearchDialog'),
+            url_get('^/create$', 'createSearch'),
             url_post('^/(?P<id>\d+)/save$', 'saveSearch'),
             url_post('^/save$', 'saveSearch'),
             url_delete('^/(?P<id>\d+)$', 'deleteSearch'),
@@ -210,9 +211,9 @@ $dispatcher = patterns('',
         url_get('^(?P<tid>\d+)/collaborators/preview$', 'previewCollaborators'),
         url_get('^(?P<tid>\d+)/collaborators$', 'showCollaborators'),
         url_post('^(?P<tid>\d+)/collaborators$', 'updateCollaborators'),
-        url_get('^(?P<tid>\d+)/add-collaborator/(?P<uid>\d+)$', 'addCollaborator'),
+        url_get('^(?P<tid>\d+)/add-collaborator/(?P<type>\w+)/(?P<uid>\d+)$', 'addCollaborator'),
         url_get('^(?P<tid>\d+)/add-collaborator/auth:(?P<bk>\w+):(?P<id>.+)$', 'addRemoteCollaborator'),
-        url('^(?P<tid>\d+)/add-collaborator$', 'addCollaborator'),
+        url('^(?P<tid>\d+)/add-collaborator/(?P<type>\w+)$', 'addCollaborator'),
         url_get('^(?P<tid>\d+)/collaborators/(?P<cid>\d+)/view$', 'viewCollaborator'),
         url_post('^(?P<tid>\d+)/collaborators/(?P<cid>\d+)$', 'updateCollaborator')
     )),
diff --git a/scp/emailtest.php b/scp/emailtest.php
index ae8d68579d88768ee3034c856d89ace84c5b6283..8228de5c3507906057c6d7a5f1a7ac70b067edd2 100644
--- a/scp/emailtest.php
+++ b/scp/emailtest.php
@@ -16,8 +16,6 @@
 require('admin.inc.php');
 include_once(INCLUDE_DIR.'class.email.php');
 include_once(INCLUDE_DIR.'class.csrf.php');
-$info=array();
-$info['subj']='osTicket test email';
 
 if($_POST){
     $errors=array();
@@ -53,6 +51,8 @@ $ost->addExtraHeader('<meta name="tip-namespace" content="emails.diagnostic" />'
     "$('#content').data('tipNamespace', '".$tip_namespace."');");
 require(STAFFINC_DIR.'header.inc.php');
 
+$info=array();
+$info['subj']='osTicket test email';
 $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 ?>
 <form action="emailtest.php" method="post" class="save">
diff --git a/scp/filters.php b/scp/filters.php
index 23bbe23e37ae0721eccbf43288dbfc6a77ab0e75..c83f9eac074175b908b3cea6cf3eaaa75778f64a 100644
--- a/scp/filters.php
+++ b/scp/filters.php
@@ -120,11 +120,12 @@ $tip_namespace = 'manage.filter';
 if($filter || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) {
   if($filter) {
     foreach ($filter->getActions() as $A) {
+      $config = JsonDataParser::parse($A->configuration);
       if($A->type == 'dept')
-        $dept = Dept::lookup($A->parseConfiguration($_POST)['dept_id']);
+        $dept = Dept::lookup($config['dept_id']);
 
       if($A->type == 'topic')
-        $topic = Topic::lookup($A->parseConfiguration($_POST)['topic_id']);
+        $topic = Topic::lookup($config['topic_id']);
     }
   }
 
diff --git a/scp/js/scp.js b/scp/js/scp.js
index fb2a6428a9c8e1884186968236b252f9a197eda7..508123366f03b860cf906dd727bb26b9ff95fc4d 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -506,7 +506,7 @@ var scp_prep = function() {
   // Auto fetch queue counts
   $(function() {
     var fired = false;
-    $('li.top-queue.item').hover(function() {
+    $('#customQ_nav li.item').hover(function() {
       if (fired) return;
       fired = true;
       $.ajax({
@@ -711,7 +711,9 @@ $.dialog = function (url, codes, cb, options) {
                         }
                         catch (e) { }
                         $('div.body', $popup).html(resp);
-                        $popup.effect('shake');
+                        if ($('#msg_error, .error-banner', $popup).length) {
+                            $popup.effect('shake');
+                        }
                         $('#msg_notice, #msg_error', $popup).delay(5000).slideUp();
                         $('div.tab_content[id] div.error:not(:empty)', $popup).each(function() {
                           var div = $(this).closest('.tab_content');
diff --git a/scp/queues.php b/scp/queues.php
index c2a641614e47116bcd2cd9efb0ebc38b331d20eb..ba5f33b78baf9bdaec51b1da074993030703030c 100644
--- a/scp/queues.php
+++ b/scp/queues.php
@@ -20,7 +20,7 @@ require('admin.inc.php');
 $nav->setTabActive('settings', 'settings.php?t='.urlencode($_GET['t']));
 $errors = array();
 
-if ($_REQUEST['id']) {
+if ($_REQUEST['id'] && is_numeric($_REQUEST['id'])) {
     $queue = CustomQueue::lookup($_REQUEST['id']);
 }
 
@@ -43,11 +43,14 @@ if ($_POST) {
     case 'create':
         $queue = CustomQueue::create(array(
             'flags' => CustomQueue::FLAG_PUBLIC,
-            'root' => $_POST['root'] ?: 'Ticket'
+            'staff_id' => 0,
+            'title' => $_POST['queue-name'],
+            'root' => $_POST['root'] ?: 'T'
         ));
 
         if ($queue->update($_POST, $errors) && $queue->save(true)) {
-            $msg = sprintf(__('Successfully added %s'), Format::htmlchars($_POST['name']));
+            $msg = sprintf(__('Successfully added %s'),
+                    Format::htmlchars($queue->getName()));
         }
         elseif (!$errors['err']) {
             $errors['err']=sprintf(__('Unable to add %s. Correct error(s) below and try again.'),
diff --git a/scp/tasks.php b/scp/tasks.php
index 5cd77777f3714b6ff592878ab25ca6ee35a212a1..63e4b87ca1b35831e54f6bfcde6ce0efe07620db 100644
--- a/scp/tasks.php
+++ b/scp/tasks.php
@@ -44,7 +44,7 @@ if($_POST && !$errors):
     if ($task) {
         //More coffee please.
         $errors=array();
-        $role = $thisstaff->getRole($task->getDeptId());
+        $role = $thisstaff->getRole($task->getDept());
         switch(strtolower($_POST['a'])):
         case 'postnote': /* Post Internal Note */
             $vars = $_POST;
diff --git a/scp/tickets.php b/scp/tickets.php
index 112a7f7d581212a1c16ccaab830468af560e6164..2d04b89efc48ccedab6099f58bee837d72ac2b9d 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -110,10 +110,10 @@ if (!$ticket) {
     $_SESSION[$queue_key] = $queue_id;
 
     if ((int) $queue_id && !$queue) {
-        $queue = CustomQueue::lookup($queue_id);
+        $queue = SavedQueue::lookup($queue_id);
     }
     if (!$queue) {
-        $queue = CustomQueue::lookup($cfg->getDefaultTicketQueueId());
+        $queue = SavedQueue::lookup($cfg->getDefaultTicketQueueId());
     }
 
     // Set the queue_id for navigation to turn a top-level item bold
@@ -139,7 +139,7 @@ if($_POST && !$errors):
         //More coffee please.
         $errors=array();
         $lock = $ticket->getLock(); //Ticket lock if any
-        $role = $thisstaff->getRole($ticket->getDeptId());
+        $role = $ticket->getRole($thisstaff);
         switch(strtolower($_POST['a'])):
         case 'reply':
             if (!$role || !$role->hasPerm(Ticket::PERM_REPLY)) {
@@ -456,7 +456,7 @@ $nav->setTabActive('tickets');
 $nav->addSubNavInfo('jb-overflowmenu', 'customQ_nav');
 
 // Fetch ticket queues organized by root and sub-queues
-$queues = CustomQueue::queues()
+$queues = SavedQueue::queues()
     ->filter(Q::any(array(
         'flags__hasbit' => CustomQueue::FLAG_PUBLIC,
         'staff_id' => $thisstaff->getId(),
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index 1a198325548faafda964b654f26423e7ad828844..d5aff5044cbf99b2a8b6460b6251b215c4bc2911 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -210,6 +210,18 @@ class Installer extends SetupWizard {
             return false;
         }
 
+        // Extended Access
+        foreach (Dept::objects()
+                ->filter(Q::not(array('id' => $dept_id)))
+                ->values_flat('id') as $row) {
+            $da = new StaffDeptAccess(array(
+                        'dept_id' => $row[0],
+                        'role_id' => $role_id
+                        ));
+            $staff->dept_access->add($da);
+        }
+        $staff->dept_access->saveAll();
+
         // Create default emails!
         $email = $vars['email'];
         list(,$domain) = explode('@', $vars['email']);
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 45482f83f8a448904a1e054406cd9a25c83c9c61..5c1ddc7ea1a46c736dd7d05f2037925a1f75131f 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -876,11 +876,12 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_columns`;
 CREATE TABLE `%TABLE_PREFIX%queue_columns` (
   `queue_id` int(11) unsigned NOT NULL,
   `column_id` int(11) unsigned NOT NULL,
+  `staff_id` int(11) unsigned NOT NULL,
   `bits` int(10) unsigned NOT NULL DEFAULT '0',
   `sort` int(10) unsigned NOT NULL DEFAULT '1',
   `heading` varchar(64) DEFAULT NULL,
   `width` int(10) unsigned NOT NULL DEFAULT '100',
-  PRIMARY KEY (`queue_id`, `column_id`)
+  PRIMARY KEY (`queue_id`, `column_id`, `staff_id`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_sort`;
@@ -913,6 +914,15 @@ CREATE TABLE `%TABLE_PREFIX%queue_export` (
   KEY `queue_id` (`queue_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_config`;
+CREATE TABLE `%TABLE_PREFIX%queue_config` (
+  `queue_id` int(11) unsigned NOT NULL,
+  `staff_id` int(11) unsigned NOT NULL,
+  `setting` text,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`queue_id`,`staff_id`)
+) DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%translation`;
 CREATE TABLE `%TABLE_PREFIX%translation` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
diff --git a/setup/test/run-tests.php b/setup/test/run-tests.php
index fe55e681e06a947511848e8d3a9a669e339485bb..9834aa90a684456a246cc7dc9fa3d07e892cbc2c 100644
--- a/setup/test/run-tests.php
+++ b/setup/test/run-tests.php
@@ -43,7 +43,7 @@ if (function_exists('pcntl_signal')) {
 foreach (glob_recursive(dirname(__file__)."/tests/test.*.php") as $t) {
     if (strpos($t,"class.") !== false)
         continue;
-    $class = (include $t);
+    $class = @(include $t);
     if (!is_string($class))
         continue;
     if($selected_test && ($class != $selected_test))
diff --git a/setup/test/tests/class.test.php b/setup/test/tests/class.test.php
index 5dd6097dcb127a1a3391d36361872ed0e6b5dec2..db05c2c16a88c069b55ddace79e641430d5398cd 100644
--- a/setup/test/tests/class.test.php
+++ b/setup/test/tests/class.test.php
@@ -31,10 +31,15 @@ class Test {
     function teardown() {
     }
 
-    static function getAllScripts($excludes=true, $root=false) {
+    function ignore3rdparty() {
+        return true;
+    }
+
+    function getAllScripts($pattern='*.php', $root=false, $excludes=true) {
         $root = $root ?: get_osticket_root_path();
+        $excludes = $excludes ?: $this->ignore3rdparty();
         $scripts = array();
-        foreach (glob_recursive("$root/*.php") as $s) {
+        foreach (glob_recursive("$root/$pattern") as $s) {
             $found = false;
             if ($excludes) {
                 foreach (self::$third_party_paths as $p) {
@@ -88,20 +93,20 @@ class Test {
         foreach ($rc->getMethods() as $m) {
             if (stripos($m->name, 'test') === 0) {
                 $this->setup();
-                call_user_func(array($this, $m->name));
+                @call_user_func(array($this, $m->name));
                 $this->teardown();
             }
         }
     }
 
-    function line_number_for_offset($filename, $offset) {
-        $lines = file($filename);
-        $bytes = $line = 0;
-        while ($bytes < $offset) {
-            $bytes += strlen(array_shift($lines));
-            $line += 1;
-        }
-        return $line;
+    function line_number_for_offset($file, $offset) {
+
+        if (is_file($file))
+            $content = file_get_contents($file, false, null, 0, $offset);
+        else
+            $content = @substr($file, 0, $offset);
+
+        return count(explode("\n", $content));
     }
 }
 
diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php
index 0ad23392642a6e1d4cedd18e7876e44fd1972d35..6a7b0d285ea0e9898f600365ca692f29c768f1c0 100644
--- a/setup/test/tests/stubs.php
+++ b/setup/test/tests/stubs.php
@@ -104,6 +104,7 @@ class Phar {
     function startBuffering() {}
     function stopBuffering() {}
     function setSignatureAlgorithm() {}
+    function compress() {}
 }
 
 class ZipArchive {
@@ -114,6 +115,10 @@ class ZipArchive {
     function setExternalAttributesName() {}
 }
 
+class Spyc {
+    function YAMLLoad() {}
+}
+
 class finfo {
     function file() {}
     function buffer() {}
@@ -176,6 +181,7 @@ class NumberFormatter {
 
 class Collator {
     function setStrength() {}
+    function compare() {}
 }
 
 class Aws_Route53_Client {
@@ -189,4 +195,43 @@ class Memcache {
     function set() {}
     function get() {}
 }
+
+class Crypt_Hash {
+    function setKey() {}
+    function setIV() {}
+}
+
+class Crypt_AES {
+    function setKey() {}
+    function setIV() {}
+    function enableContinuousBuffer() {}
+}
+
+class PEAR {
+    function isError() {}
+    function mail() {}
+}
+
+class mail {
+    function factory() {}
+    function connect() {}
+    function disconnect() {}
+}
+
+class Mail_mime {
+    function headers() {}
+    function setTXTBody() {}
+    function setHTMLBody() {}
+    function addCc() {}
+}
+
+class mPDF {
+    function Output() {}
+}
+
+class HashPassword {
+    function CheckPassword() {}
+    function HashPassword() {}
+}
+
 ?>
diff --git a/setup/test/tests/test.extra-whitespace.php b/setup/test/tests/test.extra-whitespace.php
index 96d31ac8425e40a38e66ff90f7a59e9717697bb9..ff3fc7be0721cd981de6550ba051a540565c6b28 100644
--- a/setup/test/tests/test.extra-whitespace.php
+++ b/setup/test/tests/test.extra-whitespace.php
@@ -3,16 +3,17 @@ require_once "class.test.php";
 
 class ExtraWhitespace extends Test {
     var $name = "PHP Leading and Trailing Whitespace";
-    
+
     function testFindWhitespace() {
         foreach ($this->getAllScripts() as $s) {
             $matches = array();
+            $content = file_get_contents($s);
             if (preg_match_all('/^\s+<\?php|\?>\n\s+$/s',
-                    file_get_contents($s), $matches,
+                    $content, $matches,
                     PREG_OFFSET_CAPTURE) > 0) {
                 foreach ($matches[0] as $match)
                     $this->fail(
-                        $s, $this->line_number_for_offset($s, $match[1]),
+                        $s, $this->line_number_for_offset($content, $match[1]),
                         (strpos('?>', $match[0]) !== false)
                             ? 'Leading whitespace'
                             : 'Trailing whitespace');
diff --git a/setup/test/tests/test.jslint.php b/setup/test/tests/test.jslint.php
index 5f8fcfca9378b8eef6441e0f71e5d7853dcb28dc..e916715c663587d14f20be9dc213011c00055d1a 100644
--- a/setup/test/tests/test.jslint.php
+++ b/setup/test/tests/test.jslint.php
@@ -6,8 +6,7 @@ class JsSyntaxTest extends Test {
 
     function testLintErrors() {
         $exit = 0;
-        $root = get_osticket_root_path();
-        foreach (glob_recursive("$root/*.js") as $s) {
+        foreach ($this->getAllScripts('*.js') as $s) {
             ob_start();
             system("jsl -process $s", $exit);
             $line = ob_get_contents();
diff --git a/setup/test/tests/test.shortopentags.php b/setup/test/tests/test.shortopentags.php
index 571fc08e15e1c3af5c0c60216eeda7d494a6cfa6..ec5b8669e454c6ae735aeb81fbfaba94f0f4e016 100644
--- a/setup/test/tests/test.shortopentags.php
+++ b/setup/test/tests/test.shortopentags.php
@@ -7,13 +7,14 @@ class ShortOpenTag extends Test {
     function testFindShortOpens() {
         foreach ($this->getAllScripts() as $s) {
             $matches = array();
+            $content = file_get_contents($s);
             if (preg_match_all('/<\?\s*(?!php|xml).*$/m',
-                    file_get_contents($s), $matches,
+                    $content, $matches,
                     PREG_OFFSET_CAPTURE) > 0) {
                 foreach ($matches[0] as $match)
                     $this->fail(
                         $s,
-                        $this->line_number_for_offset($s, $match[1]),
+                        $this->line_number_for_offset($content, $match[1]),
                         $match[0]);
             }
             else $this->pass();
diff --git a/setup/test/tests/test.signals.php b/setup/test/tests/test.signals.php
index 7ce888383ab0aee781450f3f908d8c4a36d96ae1..0f6287a0a9e0a8414d39cecacdf2a2ed99704964 100644
--- a/setup/test/tests/test.signals.php
+++ b/setup/test/tests/test.signals.php
@@ -16,14 +16,15 @@ class SignalsTest extends Test {
                 foreach ($matches as $match)
                     $published_signals[] = $match[1];
         foreach ($scripts as $s) {
+            $content = file_get_contents($s);
             if (preg_match_all("/^ *Signal::connect\('([^']+)'/m",
-                    file_get_contents($s), $matches,
+                    $content, $matches,
                     PREG_OFFSET_CAPTURE|PREG_SET_ORDER) > 0) {
                 foreach ($matches as $match) {
                     $match = $match[1];
                     if (!in_array($match[0], $published_signals))
                         $this->fail(
-                            $s, self::line_number_for_offset($s, $match[1]),
+                            $s, $this->line_number_for_offset($content, $match[1]),
                             "Signal '{$match[0]}' is never sent");
                     else
                         $this->pass();
@@ -31,16 +32,6 @@ class SignalsTest extends Test {
             }
         }
     }
-
-    function line_number_for_offset($filename, $offset) {
-        $lines = file($filename);
-        $bytes = $line = 0;
-        while ($bytes < $offset) {
-            $bytes += strlen(array_shift($lines));
-            $line += 1;
-        }
-        return $line;
-    }
 }
 
 return 'SignalsTest';
diff --git a/setup/test/tests/test.syntax.php b/setup/test/tests/test.syntax.php
index 0adf1465a3fcca066262efe308222e8da1e48720..cc528f52fb53b8174caf96a61652c11f29bde818 100644
--- a/setup/test/tests/test.syntax.php
+++ b/setup/test/tests/test.syntax.php
@@ -4,9 +4,13 @@ require_once "class.test.php";
 class SyntaxTest extends Test {
     var $name = "PHP Syntax Checks";
 
+    function ignore3rdparty() {
+        return false;
+    }
+
     function testCompileErrors() {
         $exit = 0;
-        foreach ($this->getAllScripts(false) as $s) {
+        foreach ($this->getAllScripts() as $s) {
             ob_start();
             system("php -l $s", $exit);
             $line = ob_get_contents();
diff --git a/setup/test/tests/test.undefinedmethods.php b/setup/test/tests/test.undefinedmethods.php
index 83a227eeeae0da98b146908f50055c2b2387ff97..16f4e76e1678ac5e755e4c23235a5279e348cb03 100644
--- a/setup/test/tests/test.undefinedmethods.php
+++ b/setup/test/tests/test.undefinedmethods.php
@@ -4,8 +4,12 @@ require_once "class.test.php";
 class UndefinedMethods extends Test {
     var $name = "Access to undefined object methods";
 
-    function testFindShortOpen() {
-        $scripts = $this->getAllScripts(false);
+    function ignore3rdparty() {
+        return false;
+    }
+
+    function testUndefinedMethods() {
+        $scripts = $this->getAllScripts();
         $function_defs = array();
         foreach ($scripts as $s) {
             $matches = array();
diff --git a/setup/test/tests/test.var-dump.php b/setup/test/tests/test.var-dump.php
new file mode 100644
index 0000000000000000000000000000000000000000..1244ec7b5e1c1e95e037946d105ae70cf8a92fdf
--- /dev/null
+++ b/setup/test/tests/test.var-dump.php
@@ -0,0 +1,27 @@
+<?php
+require_once "class.test.php";
+
+class VarDump extends Test {
+    var $name = "var_dump Checks";
+
+    function testFindShortOpens() {
+        $re = '/^(([\t ]*?)var_dump\(.*[\)|,|;])((?!nolint).)*$/m';
+        foreach ($this->getAllScripts() as $s) {
+            $matches = array();
+            $content = file_get_contents($s);
+            if (preg_match_all($re,
+                    $content, $matches,
+                    PREG_OFFSET_CAPTURE) > 0) {
+                foreach ($matches[0] as $match) {
+                    $this->fail(
+                        $s,
+                        $this->line_number_for_offset($content, $match[1]),
+                        trim($match[0]));
+                }
+            }
+            else $this->pass();
+        }
+    }
+}
+return 'VarDump';
+?>