diff --git a/include/ajax.search.php b/include/ajax.search.php
new file mode 100644
index 0000000000000000000000000000000000000000..b0d99f18fac9f8b323908e62c26d9a9bb6774b3c
--- /dev/null
+++ b/include/ajax.search.php
@@ -0,0 +1,153 @@
+<?php
+/*********************************************************************
+    ajax.search.php
+
+    AJAX interface for searches, queue management, etc.
+
+    Jared Hancock <jared@osticket.com>
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2006-2014 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+if(!defined('INCLUDE_DIR')) die('403');
+
+include_once(INCLUDE_DIR.'class.ticket.php');
+require_once(INCLUDE_DIR.'class.ajax.php');
+
+class SearchAjaxAPI extends AjaxController {
+
+    function getAdvancedSearchDialog() {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login required');
+
+        $search = SavedSearch::create();
+        $form = $search->getForm();
+        if (isset($_SESSION['advsearch']))
+            $form->loadState($_SESSION['advsearch']);
+        $matches = Filter::getSupportedMatches();
+
+        include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
+    }
+
+    function addField($name) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login required');
+
+    }
+
+    function doSearch() {
+        global $thisstaff;
+
+        $search = SavedSearch::create();
+
+        // Add "other" fields (via $_POST['other'][])
+
+        $form = $search->getForm($_POST);
+        if (!$form->isValid()) {
+            $matches = Filter::getSupportedMatches();
+            include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
+            return;
+        }
+        $_SESSION['advsearch'] = $form->getState();
+
+        Http::response(200, $this->encode(array(
+            'redirect' => 'tickets.php?advanced',
+        )));
+    }
+
+    function saveSearch($id) {
+        global $thisstaff;
+
+        $search = SavedSearch::lookup($id);
+        if (!$search || !$search->checkAccess($thisstaff))
+            Http::response(404, 'No such saved search');
+        elseif (!$thisstaff)
+            Http::response(403, 'Agent login is required');
+
+        return self::_saveSearch($search);
+    }
+
+    function _saveSearch($search) {
+        $data = array();
+        foreach ($_POST['form'] as $id=>$info) {
+            $name = $info['name'];
+            if (substr($name, -2) == '[]')
+                $data[substr($name, 0, -2)][] = $info['value'];
+            else
+                $data[$name] = $info['value'];
+        }
+        $form = $search->getForm($data);
+        if (!$data || !$form->isValid()) {
+            Http::response(422, 'Validation errors exist on form');
+        }
+
+        $search->config = JsonDataEncoder::encode($form->getState());
+        if (isset($_POST['name']))
+            $search->title = $_POST['name'];
+        if (!$search->save()) {
+            Http::response(500, 'Internal error. Unable to update search');
+        }
+        Http::response(201, $this->encode(array(
+            'id' => $search->id,
+            'title' => $search->title,
+        )));
+    }
+
+    function createSearch() {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login is required');
+
+        $search = SavedSearch::create();
+        $search->staff_id = $thisstaff->getId();
+        return self::_saveSearch($search);
+    }
+
+    function loadSearch($id) {
+        global $thisstaff;
+
+        if (!$thisstaff) {
+            Http::response(403, 'Agent login is required');
+        }
+        elseif (!($search = SavedSearch::lookup($id))) {
+            Http::response(404, 'No such saved search');
+        }
+
+        $form = $search->getForm();
+        if ($state = JsonDataParser::parse($search->config))
+            $form->loadState($state);
+
+        $matches = Filter::getSupportedMatches();
+        include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
+    }
+
+    function deleteSearch($id) {
+        global $thisstaff;
+
+        if (!$thisstaff) {
+            Http::response(403, 'Agent login is required');
+        }
+        elseif (!($search = SavedSearch::lookup($id))) {
+            Http::response(404, 'No such saved search');
+        }
+        elseif (!$search->delete()) {
+            Http::response(500, 'Unable to delete search');
+        }
+
+        Http::response(200, $this->encode(array(
+            'id' => $search->id,
+            'success' => true,
+        )));
+    }
+}
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 2e90137a521d19134498e51e795bd2ce8eeb20e0..708bb6dbdc88b3f6adadba7205cc60efb5da8b4c 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -275,20 +275,6 @@ class TicketsAjaxAPI extends AjaxController {
         return $this->json_encode($result);
     }
 
-    function getAdvancedSearchDialog() {
-        global $cfg, $thisstaff;
-
-        if (!$thisstaff)
-            Http::response(403, 'Agent login required');
-
-        include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
-    }
-
-    function doSearch() {
-        $tickets = self::_search($_POST);
-        $result = array();
-    }
-
     function acquireLock($tid) {
         global $cfg,$thisstaff;
 
diff --git a/include/class.forms.php b/include/class.forms.php
index fb0dfff84710b77122f884001cf3b1b6a8011194..aa514acd777d35846b53ea21d5c9e17659cb5a5b 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -2006,10 +2006,10 @@ class InlineFormField extends FormField {
         return ob_get_clean();
     }
 
-    function getInlineForm() {
+    function getInlineForm($data=false) {
         $form = $this->get('form');
         if (is_array($form)) {
-            $form = new Form($form);
+            $form = new Form($form, $data ?: $this->value ?: $this->getSource());
         }
         return $form;
     }
diff --git a/include/class.search.php b/include/class.search.php
index 108a77d9365264b6b9fbddee488c824e6da34807..e7647cc3711330adfd9fa5566b895c686b231aa3 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -630,6 +630,187 @@ Signal::connect('system.install',
 
 MysqlSearchBackend::register();
 
+// Saved search system
+
+/**
+ *
+ * Fields:
+ * id - (int:unsigned:auto:pk) unique identifier
+ * flags - (int:unsigned) flags for this queue
+ * staff_id - (int:unsigned) Agent to whom this queue belongs (can be null
+ *      for public saved searches)
+ * title - (text:60) name of the queue
+ * config - (text) JSON encoded search configuration for the queue
+ * created - (date) date initially created
+ * updated - (date:auto_update) time of last update
+ */
+class SavedSearch extends VerySimpleModel {
+
+    static $meta = array(
+        'table' => 'ost_queue', # QUEUE_TABLE
+        'pk' => array('id'),
+        'ordering' => array('sort'),
+    );
+
+    const FLAG_PUBLIC =     0x0001;
+    const FLAG_QUEUE =      0x0002;
+
+    static function forStaff(Staff $agent) {
+        return static::objects()->filter(Q::any(array(
+            'staff_id' => $agent->getId(),
+            'flags__hasbit' => self::FLAG_PUBLIC,
+        )));
+    }
+
+    function getForm($source=false) {
+        // XXX: Ensure that the UIDs generated for these fields are
+        //      consistent between requests
+        FormField::$uid = 1000;
+
+        $searchable = $this->getCurrentSearchFields($source);
+        $fields = array(
+            'keywords' => new TextboxField(array(
+                'configuration' => array(
+                    'size' => 40,
+                    'length' => 400,
+                    'classes' => 'full-width headline',
+                    'placeholder' => __('Keywords — Optional'),
+                ),
+            )),
+        );
+        foreach ($searchable as $name=>$field) {
+            $fields = array_merge($fields, $this->getSearchField($field, $name));
+        }
+        $form = new Form($fields, $source);
+        $form->addValidator(function($form) {
+            $selected = 0;
+            foreach ($form->getFields() as $F) {
+                if (substr($F->get('name'), -7) == '+search' && $F->getClean())
+                    $selected += 1;
+            }
+            if (!$selected)
+                $form->addError('No fields selected for searching');
+        });
+        return $form;
+    }
+
+    function getCurrentSearchFields($source=false) {
+        $core = array(
+            'state' =>      new TicketStateChoiceField(array(
+                'label' => __('State'),
+            )),
+            'status_id' =>  new TicketStatusChoiceField(array(
+                'label' => __('Status'),
+            )),
+            'flags' =>      new TicketFlagChoiceField(array(
+                'label' => __('Flags'),
+            )),
+            'dept_id'   =>  new DepartmentChoiceField(array(
+                'label' => __('Department'),
+            )),
+            'assignee'  =>  new AssigneeChoiceField(array(
+                'label' => __('Assignee'),
+            )),
+            'topic_id'  =>  new HelpTopicChoiceField(array(
+                'label' => __('Help Topic'),
+            )),
+            'created'   =>  new DateTimeField(array(
+                'label' => __('Created'),
+            )),
+            'duedate'   =>  new DateTimeField(array(
+                'label' => __('Due Date'),
+            )),
+        );
+
+        // TODO: Add "other" fields to the basic set
+
+        return $core;
+    }
+
+    function getSearchField($field, $name) {
+        $pieces = array();
+        $pieces["{$name}+search"] = new BooleanField(array(
+            'configuration' => array('desc' => $field->get('label'))
+        ));
+        $methods = $field->getSearchMethods();
+        $pieces["{$name}+method"] = new ChoiceField(array(
+            'choices' => $methods,
+            'default' => key($methods),
+            'visibility' => new VisibilityConstraint(new Q(array(
+                "{$name}+search__eq" => true,
+            )), VisibilityConstraint::HIDDEN),
+        ));
+        foreach ($field->getSearchMethodWidgets() as $m=>$w) {
+            if (!$w)
+                continue;
+            list($class, $args) = $w;
+            $args['required'] = true;
+            $args['visibility'] = new VisibilityConstraint(new Q(array(
+                    "{$name}+method__eq" => $m,
+                )), VisibilityConstraint::HIDDEN);
+            $pieces["{$name}+{$m}"] = new $class($args);
+        }
+        return $pieces;
+    }
+
+    function mangleQuerySet(QuerySet $qs, $form=false) {
+        $form = $form ?: $this->getForm();
+        $searchable = $this->getCurrentSearchFields($form->getSource());
+
+        // Figure out fields to search on
+        foreach ($form->getFields() as $f) {
+            if (substr($f->get('name'), -7) == '+search' && $f->getClean()) {
+                $name = substr($f->get('name'), 0, -7);
+                // Determine the search method and fetch the original field
+                if (($M = $form->getField("{$name}+method"))
+                    && ($method = $M->getClean())
+                    && ($field = $searchable[$name])
+                ) {
+                    // Request the field to generate a search Q for the
+                    // search method and given value
+                    $value = null;
+                    if ($value = $form->getField("{$name}+{$method}"))
+                        $value = $value->getClean();
+
+                    // Add the criteria to the QuerySet
+                    if ($Q = $field->getSearchQ($method, $value, $name))
+                        $qs = $qs->filter($Q);
+                }
+            }
+        }
+        return $qs;
+    }
+
+    function checkAccess(Staff $agent) {
+        return $agent->getId() == $this->staff_id
+            || $this->hasFlag(self::FLAG_PUBLIC);
+    }
+
+    protected function hasFlag($flag) {
+        return $this->get('flag') & $flag !== 0;
+    }
+
+    protected function clearFlag($flag) {
+        return $this->set('flag', $this->get('flag') & ~$flag);
+    }
+
+    protected function setFlag($flag) {
+        return $this->set('flag', $this->get('flag') | $flag);
+    }
+
+    static function create($vars=array()) {
+        $inst = parent::create($vars);
+        $inst->created = SqlFunction::NOW();
+        return $inst;
+    }
+
+    function save($refetch=false) {
+        if ($this->dirty)
+            $this->updated = SqlFunction::NOW();
+        return parent::save($refetch || $this->dirty);
+    }
+}
+
 // Advanced search special fields
 
 class HelpTopicChoiceField extends ChoiceField {
@@ -650,21 +831,8 @@ class DepartmentChoiceField extends ChoiceField {
 
     function getSearchMethods() {
         return array(
-            'is' =>   __('is'),
-            'isnot' =>   __('is not'),
-        );
-    }
-
-    function getSearchMethodWidgets() {
-        return array(
-            'is' => array('ChoiceField', array(
-                'choices' => $this->getChoices(),
-                'configuration' => array('multiselect' => true),
-            )),
-            'isnot' => array('ChoiceField', array(
-                'choices' => $this->getChoices(),
-                'configuration' => array('multiselect' => true),
-            )),
+            'includes' =>   __('is'),
+            '!includes' =>  __('is not'),
         );
     }
 }
@@ -672,8 +840,8 @@ class DepartmentChoiceField extends ChoiceField {
 class AssigneeChoiceField extends ChoiceField {
     function getChoices() {
         $items = array(
-            'me' => __('Me'),
-            'myteam' => __('One of my teams'),
+            'M' => __('Me'),
+            'T' => __('One of my teams'),
         );
         foreach (Staff::getStaffMembers() as $id=>$name) {
             $items['s' . $id] = $name;
@@ -686,27 +854,118 @@ class AssigneeChoiceField extends ChoiceField {
 
     function getSearchMethods() {
         return array(
-            'assigned' => __('assigned'),
-            'assigned.not' => __('unassigned'),
-            'is' =>     __('is'),
-            'isnot' => __('is not'),
+            'assigned' =>   __('assigned'),
+            '!assigned' =>  __('unassigned'),
+            'includes' =>   __('includes'),
+            '!includes' =>  __('does not include'),
         );
     }
 
     function getSearchMethodWidgets() {
         return array(
             'assigned' => null,
-            'assigned.not' => null,
-            'is' => array('ChoiceField', array(
+            '!assigned' => null,
+            'includes' => array('ChoiceField', array(
                 'choices' => $this->getChoices(),
                 'configuration' => array('multiselect' => true),
             )),
-            'isnot' => array('ChoiceField', array(
+            '!includes' => array('ChoiceField', array(
                 'choices' => $this->getChoices(),
                 'configuration' => array('multiselect' => true),
             )),
         );
     }
+
+    function getSearchQ($method, $value, $name=false) {
+        global $thisstaff;
+
+        $Q = new Q();
+        switch ($method) {
+        case '!assigned':
+            $Q->negate();
+        case 'assigned':
+            $Q->add(array('team_id__gt' => 0,
+                'staff_id__gt' => 0));
+            break;
+        case '!includes':
+            $Q->negate();
+        case 'includes':
+            $teams = $agents = array();
+            foreach ($value as $id => $ST) {
+                switch ($id[0]) {
+                case 'M':
+                    $agents[] = $thisstaff->getId();
+                    break;
+                case 's':
+                    $agents[] = (int) substr($id, 1);
+                    break;
+                case 'T':
+                    $teams = array_merge($thisstaff->getTeams());
+                    break;
+                case 't':
+                    $teams[] = (int) substr($id, 1);
+                    break;
+                }
+            }
+            $constraints = array();
+            if ($teams)
+                $constraints['team_id__in'] = $teams;
+            if ($agents)
+                $constraints['staff_id__in'] = $agents;
+            $Q->add(Q::any($constraints));
+        }
+        return $Q;
+    }
+}
+
+class TicketStateChoiceField extends ChoiceField {
+    function getChoices() {
+        return array(
+            'open' => __('Open'),
+            'closed' => __('Closed'),
+            'archived' => __('Archived'),
+            'deleted' => __('Deleted'),
+        );
+    }
+
+    function getSearchMethods() {
+        return array(
+            'includes' =>   __('is'),
+            '!includes' =>  __('is not'),
+        );
+    }
+
+    function getSearchQ($method, $value, $name=false) {
+        return parent::getSearchQ($method, $value, 'status__state');
+    }
+}
+
+class TicketFlagChoiceField extends ChoiceField {
+    function getChoices() {
+        return array(
+            'isanswered' =>   __('Answered'),
+            'isoverdue' =>    __('Overdue'),
+        );
+    }
+
+    function getSearchMethods() {
+        return array(
+            'includes' =>   __('is'),
+            '!includes' =>  __('is not'),
+        );
+    }
+
+    function getSearchQ($method, $value, $name=false) {
+        $Q = new Q();
+        if (isset($value['isanswered']))
+            $Q->add(array('isanswered' => 1));
+        if (isset($value['isoverdue']))
+            $Q->add(array('isoverdue' => 1));
+        if ($method == '!includes')
+            $Q->negate();
+        if ($Q->constraints)
+            return $Q;
+    }
 }
 
 class TicketStatusChoiceField extends SelectionField {
@@ -721,21 +980,8 @@ class TicketStatusChoiceField extends SelectionField {
 
     function getSearchMethods() {
         return array(
-            'is' =>     __('is'),
-            'isnot' => __('is not'),
-        );
-    }
-
-    function getSearchMethodWidgets() {
-        return array(
-            'is' => array('ChoiceField', array(
-                'choices' => $this->getChoices(),
-                'configuration' => array('multiselect' => true),
-            )),
-            'isnot' => array('ChoiceField', array(
-                'choices' => $this->getChoices(),
-                'configuration' => array('multiselect' => true),
-            )),
+            'includes' =>   __('is'),
+            '!includes' =>  __('is not'),
         );
     }
 }
diff --git a/include/staff/templates/advanced-search.tmpl.php b/include/staff/templates/advanced-search.tmpl.php
index de13b7a088dcc4eb26ef123e9008b17998a98296..d8d0b391e519624234ae15e99a722016b129b153 100644
--- a/include/staff/templates/advanced-search.tmpl.php
+++ b/include/staff/templates/advanced-search.tmpl.php
@@ -1,86 +1,164 @@
 <?php
-$matches=Filter::getSupportedMatches();
-$basicForm = array(
-    'status' => new TicketStateField(array(
-        'label'=>__('Status'),
-    )),
-    'dept' => new ChoiceField(array(
-        'label'=>__('Department'),
-        'choices' => Dept::getDepartments(),
-    )),
-    'assignee' => new ChoiceField(array(
-        'label' => __('Assigned To'),
-        'choices' => Staff::getStaffMembers(),
-    )),
-    'closed' => new ChoiceField(array(
-        'label' => __('Closed By'),
-        'choices' => Staff::getStaffMembers(),
-    )),
-);
-
-$basic = array(
-    'status'    =>  new TicketStatusChoiceField(array(
-        'label' => __('Ticket Status'),
-    )),
-    'dept'      =>  new DepartmentChoiceField(array(
-        'label' => __('Department'),
-    )),
-    'assignee'  =>  new AssigneeChoiceField(array(
-        'label' => __('Assignee'),
-    )),
-    'topic'     =>  new HelpTopicChoiceField(array(
-        'label' => __('Help Topic'),
-    )),
-    'created'   =>  new DateTimeField(array(
-        'label' => __('Created'),
-    )),
-);
 ?>
 <div id="advanced-search">
 <h3><?php echo __('Advanced Ticket Search');?></h3>
 <a class="close" href=""><i class="icon-remove-circle"></i></a>
 <hr/>
-<form action="ajax.php/tickets/advanced-search" method="post" name="search">
+<form action="#tickets/search" method="post" name="search">
+<div class="row">
+<div class="span6">
     <input type="hidden" name="a" value="search">
-    <fieldset class="query">
-        <input type="input" id="query" name="query" size="20" placeholder="<?php echo __('Keywords') . ' &mdash; ' . __('Optional'); ?>">
-    </fieldset>
+<?php
+foreach ($form->errors(true) ?: array() as $message) {
+    ?><div class="error-banner"><?php echo $message;?></div><?php
+}
 
+foreach ($form->getFields() as $name=>$field) { ?>
+    <fieldset id="field<?php echo $field->getWidget()->id;
+        ?>" <?php if (!$field->isVisible()) echo 'style="display:none;"'; ?>>
+        <?php echo $field->render(); ?>
+        <?php foreach ($field->errors() as $E) {
+            ?><div class="error"><?php echo $E; ?></div><?php
+        } ?>
+    </fieldset>
+<?php }
+?>
+<hr/>
+<select name="new-field" style="max-width: 100%;">
+    <option value="">— <?php echo __('Add Other Field'); ?> —</option>
 <?php
-foreach ($basic as $name=>$field) { ?>
-    <fieldset>
-        <input type="checkbox" name="fields[]" value="<?php echo $name; ?>"
-            onchange="javascript:
-                $('#search-<?php echo $name; ?>').slideToggle($(this).is(':checked'));
-                $('#method-<?php echo $name; ?>-' + $('#search-<?php echo $name; ?>').find(':selected').val()).slideDown('fast');">
-        <?php echo $field->getLabel(); ?>
-        <div class="search-dropdown" style="display:none" id="search-<?php echo $name; ?>">
-            <select style="min-width:150px" name="method-<?php echo $name; ?>" onchange="javascript:
-                $(this).parent('div').find('.search-value').slideUp('fast');
-                $('#method-<?php echo $name; ?>-' + $(this).find(':selected').val()).slideDown('fast');
-">
-<?php foreach ($field->getSearchMethods() as $method=>$label) { ?>
-                <option value="<?php echo $method; ?>"><?php echo $label; ?></option>
-<?php } ?>
-            </select>
-<?php foreach ($field->getSearchMethods() as $method=>$label) { ?>
-            <span class="search-value" style="display:none;" id="method-<?php echo $name . '-' . $method; ?>">
+foreach ($matches as $name => $fields) { ?>
+    <optgroup label="<?php echo $name; ?>">
 <?php
-        if ($f = $field->getSearchWidget($method))
-            print $f->render();
-?>
-            </span>
+    foreach ($fields as $id => $desc) { ?>
+        <option value="<?php echo $id; ?>"><?php echo $desc; ?></option>
+<?php } ?>
+    </optgroup>
 <?php } ?>
-        </div>
+</select>
+
+</div>
+<div class="span6" style="border-left: 1px solid #888;">
+<div style="margin-bottom: 0.5em;"><b style="font-size: 110%;"><?php echo __('Saved Searches'); ?></b></div>
+<div id="saved-searches" class="accordian">
+<?php foreach (SavedSearch::forStaff($thisstaff) as $S) { ?>
+    <dt class="saved-search">
+        <a href="#" class="load-search"><?php echo $S->title; ?>
+        <i class="icon-chevron-down pull-right"></i>
+        </a>
+    </dt>
+    <dd>
+        <span>
+            <button onclick="javascript:$(this).closest('form').attr({
+'method': 'get', 'action': '#tickets/search/<?php echo $S->id; ?>'});"><i class="icon-chevron-left"></i> Load</button>
+            <?php if ($thisstaff->isAdmin()) { ?>
+                <button><i class="icon-bullhorn"></i> <?php echo __('Publish'); ?></button>
+            <?php } ?>
+            <button onclick="javascript:
+$.ajax({
+    url: 'ajax.php/tickets/search/<?php echo $S->id; ?>',
+    type: 'POST',
+    data: {'form': $(this).closest('.dialog').find('form[name=search]').serializeArray()},
+    dataType: 'json',
+    success: function(json) {
+      if (!json.id)
+        return;
+      $('<dt>').effect('highlight');
+    }
+});
+return false;
+"><i class="icon-save"></i> <?php echo __('Update'); ?></button>
+        </span>
+        <span class="pull-right">
+            <button title="<?php echo __('Delete'); ?>" onclick="javascript:
+    if (!confirm(__('You sure?'))) return false;
+    var that = this;
+    $.ajax({
+        'url': 'ajax.php/tickets/search/<?php echo $S->id; ?>',
+        'type': 'delete',
+        'dataType': 'json',
+        'success': function(json) {
+            if (json.success) {
+                $(that).closest('dd').prev('dt').slideUp().next('dd').slideUp();
+            }
+        }
+    });
+    return false;
+"><i class="icon-trash"></i></button>
+        </span>
+    </dd>
+<?php } ?>
+</div>
+<div>
+    <form method="post">
+    <fieldset>
+    <input name="title" type="text" size="30" placeholder="Enter a title for the search"/>
+    <span class="action-buttons">
+        <span class="action-button">
+            <a href="#tickets/search/create" onclick="javascript:
+$.ajax({
+    url: 'ajax.php/' + $(this).attr('href').substr(1),
+    type: 'POST',
+    data: {'name': $(this).closest('form').find('[name=title]').val(),
+           'form': $(this).closest('.dialog').find('form[name=search]').serializeArray()},
+    dataType: 'json',
+    success: function(json) {
+      if (!json.id)
+        return;
+      $('<dt>')
+        .append($('<a>').text(' ' + json.title)
+          .prepend($('<i>').addClass('icon-chevron-left'))
+        ).appendTo($('#saved-searches'));
+    }
+});
+return false;
+"><i class="icon-save"></i> <?php echo __('Save'); ?></a>
+        </span>
+        <span class="action-button pull-right" data-dropdown="#save-dropdown-more">
+            <i class="icon-caret-down pull-right"></i>
+        </span>
+    </span>
     </fieldset>
-<?php
-} ?>
-</form>
+    <div id="save-dropdown-more" class="action-dropdown anchor-right">
+      <ul>
+        <li><a href="#queue/create">
+            <i class="icon-list"></i> <?php echo __('Create Queue'); ?></a>
+        </li>
+      </ul>
+    </div>
+</div>
+</div>
+</div>
 
 <hr/>
 <div>
-    <div class="pull-right">
-        <button><i class="icon-save"></i> Save Search</button>
+    <div id="search-hint" class="pull-left">
+    </div>
+    <div class="buttons pull-right">
+        <button class="button" id="do_search"><i class="icon-search"></i> <?php echo __('Search'); ?></button>
     </div>
 </div>
+
+</form>
+
 <link rel="stylesheet" type="text/css" href="<?php echo ROOT_PATH; ?>css/jquery.multiselect.css"/>
+
+<script type="text/javascript">
+$(function() {
+  $('#advanced-search [data-dropdown]').dropdown();
+
+  var I = setInterval(function() {
+    var A = $('#saved-searches.accordian');
+    if (!A.length) return;
+    clearInterval(I);
+
+    var allPanels = $('dd', A).hide();
+    $('dt > a', A).click(function() {
+      $('dt', A).removeClass('active');
+      allPanels.slideUp();
+      $(this).parent().addClass('active').next().slideDown();
+      return false;
+    });
+  }, 200);
+});
+</script>
diff --git a/scp/ajax.php b/scp/ajax.php
index ac53db3f710154af18ae70a6751529dbcbeecf92..d040343e28995e0601cd50836e7577939b428b92 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -137,8 +137,6 @@ $dispatcher = patterns('',
         url_get('^(?P<tid>\d+)/add-collaborator/(?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_get('^advanced-search', 'getAdvancedSearchDialog'),
-        url_post('^advanced-search', 'doSearch'),
         url_get('^(?P<tid>\d+)/forms/manage$', 'manageForms'),
         url_post('^(?P<tid>\d+)/forms/manage$', 'updateForms'),
         url_get('^(?P<tid>\d+)/canned-resp/(?P<cid>\w+).(?P<format>json|txt)', 'cannedResponse'),
@@ -147,7 +145,16 @@ $dispatcher = patterns('',
         url_get('^status/(?P<status>\w+)(?:/(?P<sid>\d+))?$', 'changeSelectedTicketsStatus'),
         url_post('^status/(?P<state>\w+)$', 'setSelectedTicketsStatus'),
         url_get('^lookup', 'lookup'),
-        url_get('^search', 'search')
+        url('^search', patterns('ajax.search.php:SearchAjaxAPI',
+            url_get('^$', 'getAdvancedSearchDialog'),
+            url_post('^$', 'doSearch'),
+            url_get('^quick$', 'doQuickSearch'),
+            url_get('^/(?P<id>\d+)$', 'loadSearch'),
+            url_post('^/(?P<id>\d+)$', 'saveSearch'),
+            url_delete('^/(?P<id>\d+)$', 'deleteSearch'),
+            url_post('^/create$', 'createSearch'),
+            url_get('^/field/(?P<id>\d+)$', 'getField')
+        ))
     )),
     url('^/collaborators/', patterns('ajax.tickets.php:TicketsAjaxAPI',
         url_get('^(?P<cid>\d+)/view$', 'viewCollaborator'),
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 38f3484ec7a97eeb396196469ec6ee2a7d586e09..686651801f540107b402a9ac46a1cfb4ea3b8d4d 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -39,6 +39,9 @@ div#header a {
 .full-width {
     width: 100%;
 }
+.headline {
+    margin-bottom: 15px;
+}
 
 .search-input {
     height: 20px;
@@ -553,7 +556,7 @@ a.print {
     color:#000;
 }
 
-#actions button, .button { padding:2px 5px 3px; margin-right:10px;  color:#777;}
+#actions button, .button { padding:2px 5px 3px; margin-right:10px;  color:#333;}
 
 .btn_sm {
     padding:2px 5px;
@@ -1504,6 +1507,7 @@ time {
 .dialog fieldset input {
     border:1px solid #ccc;
     background:#fff;
+    padding: 3px;
 }
 
 .dialog fieldset span.between {
@@ -1524,18 +1528,15 @@ time {
     cursor: move;
 }
 
-#advanced-search fieldset.span6 {
-    display: inline-block;
-    width: 49%;
-    margin-bottom: 5px;
+.row {
+    display: table-row;
 }
-#advanced-search fieldset label {
-    display: block;
-}
-#advanced-search fieldset.span6 select,
-#advanced-search fieldset.span6 input {
-    max-width: 100%;
-    min-width: 75%;
+
+.row .span6 {
+    display: table-cell;
+    width: 48%;
+    padding: 5px 10px;
+    vertical-align: top;
 }
 
 #advanced-search .query input {
@@ -1570,9 +1571,14 @@ time {
     text-align:center;
 }
 
+.search-dropdown {
+    padding-left: 19px;
+}
+
 .dialog input[type="submit"],
 .dialog input[type="reset"],
-.dialog input[type="button"] {
+.dialog input[type="button"],
+.dialog button.button {
     display:inline-block;
     margin:0;
     height:24px;
@@ -2072,3 +2078,57 @@ button a:hover {
 td.indented {
     padding-left: 20px;
 }
+.secondary_lang {
+    padding:3px 0;
+    margin: 3px 0;
+    border-bottom: 1px dotted #ccc;
+}
+.saved-search {
+    padding: 5px;
+}
+
+.saved-search + .saved-search {
+    border-top: 1px dotted #ccc;
+}
+
+.accordian {
+  margin-bottom: 10px;
+}
+.accordian dt {
+    border-radius: 4px;
+    border: 1px solid #ccc;
+}
+.accordian dt.active {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+.accordian dt, dd {
+  padding: 5px;
+}
+.accordian dt a {
+  color: black;
+  font-weight: bold;
+  display: block;
+}
+.accordian dt.active a {
+  color: #184E81;
+}
+.accordian dt:not(.active) a i {
+  display: none;
+}
+.accordian dd {
+  border-top: 0;
+  font-size: 12px;
+  margin-left: 0;
+  border: 1px solid #ccc;
+  border-top: none;
+  box-shadow: inset 0px 10px 5px -10px rgba(0,0,0,0.1);
+  background-color:rgba(42,103,172,0.1);
+}
+.accordian dt ~ dt {
+  margin-top: 5px;
+}
+.accordian dd:last-of-type {
+   position: relative;
+   top: -1px;
+}
diff --git a/scp/js/jquery.dropdown.js b/scp/js/jquery.dropdown.js
index b885042086efee07eeb228c60abefd86ec278c0b..802a856c95e00041425a2be13178998b39de5bb7 100644
--- a/scp/js/jquery.dropdown.js
+++ b/scp/js/jquery.dropdown.js
@@ -37,7 +37,11 @@ if(jQuery) (function($) {
 		var trigger = $(this),
 			dropdown = $( $(this).attr('data-dropdown') ),
 			isOpen = trigger.hasClass('dropdown-open'),
-            rtl = $('html').hasClass('rtl');
+            rtl = $('html').hasClass('rtl'),
+            relative = trigger.offsetParent(),
+            offset = relative.offset();
+        if (relative.get(0) !== document.body)
+            offset.top -= relative.scrollTop();
 
 		event.preventDefault();
 		event.stopPropagation();
@@ -50,9 +54,9 @@ if(jQuery) (function($) {
             dropdown.removeClass('anchor-right');
 
 		dropdown.css({
-				left: dropdown.hasClass('anchor-right') ?
-				trigger.offset().left - (dropdown.outerWidth() - trigger.outerWidth() - 4) : trigger.offset().left,
-				top: trigger.offset().top + trigger.outerHeight()
+				left: -offset.left + (dropdown.hasClass('anchor-right') ?
+				trigger.offset().left - (dropdown.outerWidth() - trigger.outerWidth() - 4) : trigger.offset().left),
+				top: -offset.top + trigger.offset().top + trigger.outerHeight()
 			}).show();
 		trigger.addClass('dropdown-open');
 	}
diff --git a/scp/js/scp.js b/scp/js/scp.js
index 6649ddeb2613a0d918895bfea4d0bff2cf378e37..5a93f7326fe8a19725388a39c0b0ed133ddea478 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -573,6 +573,11 @@ $.dialog = function (url, codes, cb, options) {
                         $('div.body', $popup).empty();
                         if(cb) cb(xhr);
                     } else {
+                        try {
+                            var json = $.parseJSON(resp);
+                            if (json.redirect) return window.location.href = json.redirect;
+                        }
+                        catch (e) { }
                         $('div.body', $popup).html(resp);
                         $popup.effect('shake');
                         $('#msg_notice, #msg_error', $popup).delay(5000).slideUp();
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 58aba311cac8c0317e8a1aae9e1c3ab1a615b417..f1062b3715b919389119bcb264f7f26918edd860 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -774,6 +774,20 @@ CREATE TABLE `%TABLE_PREFIX%plugin` (
   primary key (`id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue`;
+CREATE TABLE `%TABLE_PREFIX%queue` (
+  `id` int(11) unsigned not null auto_increment,
+  `parent_id` int(11) unsigned not null default 0,
+  `flags` int(11) unsigned not null default 0,
+  `staff_id` int(11) unsigned not null default 0,
+  `sort` int(11) unsigned not null default 0,
+  `title` varchar(60),
+  `config` text,
+  `created` datetime not null,
+  `updated` datetime not null,
+  primary key (`id`)
+) DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%translation`;
 CREATE TABLE `%TABLE_PREFIX%translation` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,