diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 708bb6dbdc88b3f6adadba7205cc60efb5da8b4c..2e90137a521d19134498e51e795bd2ce8eeb20e0 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -275,6 +275,20 @@ 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 fb5b3cf6f32136e8005a7a04256f30e9642bc10e..b0dd1913aa10ea95cac38a31cde0bed3b2d9d63a 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -420,10 +420,76 @@ class FormField {
         return $this->toString($this->getClean());
     }
 
+    /**
+     * Fetches a value that represents this content in a consistent,
+     * searchable format. This is used by the search engine system and
+     * backend.
+     */
     function searchable($value) {
         return Format::searchable($this->toString($value));
     }
 
+    /**
+     * Fetches a list of options for searching. The values returned from
+     * this method are passed to the widget's `::render()` method so that
+     * the widget can be affected by this setting. For instance, date fields
+     * might have a 'between' search option which should trigger rendering
+     * of two date widgets for search results.
+     */
+    function getSearchMethods() {
+        return array(
+            'set' =>        __('has a value'),
+            'set.not' =>    __('does not have a value'),
+            'equal' =>      __('is'),
+            'equal.not' =>  __('is not'),
+            'contains' =>   __('contains'),
+            'match' =>      __('matches'),
+        );
+    }
+
+    function getSearchMethodWidgets() {
+        return array(
+            'set' => null,
+            'set.not' => null,
+            'equal' => array('TextboxField', array()),
+            'equal.not' => array('TextboxField', array()),
+            'contains' => array('TextboxField', array()),
+            'match' => array('TextboxField', array(
+                'validators' => function($self, $v) {
+                    if (false === @preg_match($v, ' '))
+                        $self->addError(__('Cannot compile this regular expression'));
+                })),
+        );
+    }
+
+    function getSearchWidget($method) {
+        $methods = $this->getSearchMethodWidgets();
+        $info = $methods[$method];
+        if (is_array($info)) {
+            $class = $info[0];
+            return new $class($info[1]);
+        }
+        return $info;
+    }
+
+    /**
+     * This is used by the searching system to build a query for the search
+     * engine. The function should return a criteria listing to match
+     * content saved by the field by the `::to_database()` function.
+     */
+    function getSearchCriteria($method, $name, $value) {
+        switch ($method) {
+        case 'search':
+            return array($name => $value);
+        case 'search.set':
+            return array("{$name}__isnull" => false);
+        case 'search.notset':
+            return array("{$name}__isnull" => true);
+        default:
+            throw new Exception('Search method not supported by this field');
+        }
+    }
+
     function getLabel() { return $this->get('label'); }
 
     /**
@@ -912,6 +978,20 @@ class BooleanField extends FormField {
     function toString($value) {
         return ($value) ? __('Yes') : __('No');
     }
+
+    function getSearchMethods() {
+        return array(
+            'set' =>        __('checked'),
+            'set.not' =>    __('unchecked'),
+        );
+    }
+
+    function getSearchMethodWidgets() {
+        return array(
+            'set' => null,
+            'set.not' => null,
+        );
+    }
 }
 
 class ChoiceField extends FormField {
@@ -1029,7 +1109,26 @@ class ChoiceField extends FormField {
             }
         }
         return $this->_choices;
-     }
+    }
+
+    function getSearchMethods() {
+        return array(
+            'set' =>        __('has a value'),
+            'set.not' =>    __('does not have a value'),
+            'includes' =>   __('includes'),
+        );
+    }
+
+    function getSearchMethodWidgets() {
+        return array(
+            'set' => null,
+            'set.not' => null,
+            'includes' => array('ChoiceField', array(
+                'choices' => $this->getChoices(),
+                'configuration' => array('multiselect' => true),
+            )),
+        );
+    }
 }
 
 class DatetimeField extends FormField {
diff --git a/include/class.search.php b/include/class.search.php
index fd8c7e2cff06868b63439c980252d4b4719a71a0..108a77d9365264b6b9fbddee488c824e6da34807 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -629,3 +629,113 @@ Signal::connect('system.install',
         array('MysqlSearchBackend', '__init'));
 
 MysqlSearchBackend::register();
+
+// Advanced search special fields
+
+class HelpTopicChoiceField extends ChoiceField {
+    function hasIdValue() {
+        return true;
+    }
+
+    function getChoices() {
+        return Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
+    }
+}
+
+require_once INCLUDE_DIR . 'class.dept.php';
+class DepartmentChoiceField extends ChoiceField {
+    function getChoices() {
+        return Dept::getDepartments();
+    }
+
+    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),
+            )),
+        );
+    }
+}
+
+class AssigneeChoiceField extends ChoiceField {
+    function getChoices() {
+        $items = array(
+            'me' => __('Me'),
+            'myteam' => __('One of my teams'),
+        );
+        foreach (Staff::getStaffMembers() as $id=>$name) {
+            $items['s' . $id] = $name;
+        }
+        foreach (Team::getTeams() as $id=>$name) {
+            $items['t' . $id] = $name;
+        }
+        return $items;
+    }
+
+    function getSearchMethods() {
+        return array(
+            'assigned' => __('assigned'),
+            'assigned.not' => __('unassigned'),
+            'is' =>     __('is'),
+            'isnot' => __('is not'),
+        );
+    }
+
+    function getSearchMethodWidgets() {
+        return array(
+            'assigned' => null,
+            'assigned.not' => null,
+            'is' => array('ChoiceField', array(
+                'choices' => $this->getChoices(),
+                'configuration' => array('multiselect' => true),
+            )),
+            'isnot' => array('ChoiceField', array(
+                'choices' => $this->getChoices(),
+                'configuration' => array('multiselect' => true),
+            )),
+        );
+    }
+}
+
+class TicketStatusChoiceField extends SelectionField {
+    static $widget = 'ChoicesWidget';
+
+    function getList() {
+        return new TicketStatusList(
+            DynamicList::lookup(
+                array('type' => 'ticket-status'))
+        );
+    }
+
+    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),
+            )),
+        );
+    }
+}
diff --git a/include/staff/templates/advanced-search.tmpl.php b/include/staff/templates/advanced-search.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..de13b7a088dcc4eb26ef123e9008b17998a98296
--- /dev/null
+++ b/include/staff/templates/advanced-search.tmpl.php
@@ -0,0 +1,86 @@
+<?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">
+    <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 ($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; ?>">
+<?php
+        if ($f = $field->getSearchWidget($method))
+            print $f->render();
+?>
+            </span>
+<?php } ?>
+        </div>
+    </fieldset>
+<?php
+} ?>
+</form>
+
+<hr/>
+<div>
+    <div class="pull-right">
+        <button><i class="icon-save"></i> Save Search</button>
+    </div>
+</div>
+<link rel="stylesheet" type="text/css" href="<?php echo ROOT_PATH; ?>css/jquery.multiselect.css"/>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 1c5b5f003c453e4c131517c908d8efb91836a15f..f27de7cf3f5a79c9b902f0ecc72a97615651bba7 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -316,7 +316,9 @@ if ($results) {
             <td><input type="text" id="basic-ticket-search" name="query" size=30 value="<?php echo Format::htmlchars($_REQUEST['query']); ?>"
                 autocomplete="off" autocorrect="off" autocapitalize="off"></td>
             <td><input type="submit" name="basic_search" class="button" value="<?php echo __('Search'); ?>"></td>
-            <td>&nbsp;&nbsp;<a href="#" id="go-advanced">[<?php echo __('advanced'); ?>]</a>&nbsp;<i class="help-tip icon-question-sign" href="#advanced"></i></td>
+            <td>&nbsp;&nbsp;<a href="#" onclick="javascript:
+                $.dialog('ajax.php/tickets/advanced-search', 201);"
+                >[<?php echo __('advanced'); ?>]</a>&nbsp;<i class="help-tip icon-question-sign" href="#advanced"></i></td>
         </tr>
     </table>
     </form>
@@ -536,142 +538,6 @@ if ($results) {
      </p>
     <div class="clear"></div>
 </div>
-
-<div class="dialog" style="display:none;" id="advanced-search">
-    <h3><?php echo __('Advanced Ticket Search');?></h3>
-    <a class="close" href=""><i class="icon-remove-circle"></i></a>
-    <hr/>
-    <form action="tickets.php" method="post" id="search" name="search">
-        <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>
-        <fieldset class="span6">
-            <label for="statusId"><?php echo __('Statuses');?>:</label>
-            <select id="statusId" name="statusId">
-                 <option value="">&mdash; <?php echo __('Any Status');?> &mdash;</option>
-                <?php
-                foreach (TicketStatusList::getStatuses(
-                            array('states' => array('open', 'closed'))) as $s) {
-                    echo sprintf('<option data-state="%s" value="%d">%s</option>',
-                            $s->getState(), $s->getId(), __($s->getName()));
-                }
-                ?>
-            </select>
-        </fieldset>
-        <fieldset class="span6">
-            <label for="deptId"><?php echo __('Departments');?>:</label>
-            <select id="deptId" name="deptId">
-                <option value="">&mdash; <?php echo __('All Departments');?> &mdash;</option>
-                <?php
-                if(($mydepts = $thisstaff->getDepts()) && ($depts=Dept::getDepartments())) {
-                    foreach($depts as $id =>$name) {
-                        if(!in_array($id, $mydepts)) continue;
-                        echo sprintf('<option value="%d">%s</option>', $id, $name);
-                    }
-                }
-                ?>
-            </select>
-        </fieldset>
-        <fieldset class="span6">
-            <label for="flag"><?php echo __('Flags');?>:</label>
-            <select id="flag" name="flag">
-                 <option value="">&mdash; <?php echo __('Any Flags');?> &mdash;</option>
-                 <?php
-                 if (!$cfg->showAnsweredTickets()) { ?>
-                 <option data-state="open" value="answered"><?php echo __('Answered');?></option>
-                 <?php
-                 } ?>
-                 <option data-state="open" value="overdue"><?php echo __('Overdue');?></option>
-            </select>
-        </fieldset>
-        <fieldset class="owner span6">
-            <label for="assignee"><?php echo __('Assigned To');?>:</label>
-            <select id="assignee" name="assignee">
-                <option value="">&mdash; <?php echo __('Anyone');?> &mdash;</option>
-                <option value="0">&mdash; <?php echo __('Unassigned');?> &mdash;</option>
-                <option value="s<?php echo $thisstaff->getId(); ?>"><?php echo __('Me');?></option>
-                <?php
-                if(($users=Staff::getStaffMembers())) {
-                    echo '<OPTGROUP label="'.sprintf(__('Agents (%d)'),count($users)).'">';
-                    foreach($users as $id => $name) {
-                        $k="s$id";
-                        echo sprintf('<option value="%s">%s</option>', $k, $name);
-                    }
-                    echo '</OPTGROUP>';
-                }
-
-                if(($teams=Team::getTeams())) {
-                    echo '<OPTGROUP label="'.__('Teams').' ('.count($teams).')">';
-                    foreach($teams as $id => $name) {
-                        $k="t$id";
-                        echo sprintf('<option value="%s">%s</option>', $k, $name);
-                    }
-                    echo '</OPTGROUP>';
-                }
-                ?>
-            </select>
-        </fieldset>
-        <fieldset class="span6">
-            <label for="topicId"><?php echo __('Help Topics');?>:</label>
-            <select id="topicId" name="topicId">
-                <option value="" selected >&mdash; <?php echo __('All Help Topics');?> &mdash;</option>
-                <?php
-                if($topics=Topic::getHelpTopics()) {
-                    foreach($topics as $id =>$name)
-                        echo sprintf('<option value="%d" >%s</option>', $id, $name);
-                }
-                ?>
-            </select>
-        </fieldset>
-        <fieldset class="owner span6">
-            <label for="staffId"><?php echo __('Closed By');?>:</label>
-            <select id="staffId" name="staffId">
-                <option value="0">&mdash; <?php echo __('Anyone');?> &mdash;</option>
-                <option value="<?php echo $thisstaff->getId(); ?>"><?php echo __('Me');?></option>
-                <?php
-                if(($users=Staff::getStaffMembers())) {
-                    foreach($users as $id => $name)
-                        echo sprintf('<option value="%d">%s</option>', $id, $name);
-                }
-                ?>
-            </select>
-        </fieldset>
-        <fieldset class="date_range">
-            <label><?php echo __('Date Range').' &mdash; '.__('Create Date');?>:</label>
-            <input class="dp" type="input" size="20" name="startDate">
-            <span class="between"><?php echo __('TO');?></span>
-            <input class="dp" type="input" size="20" name="endDate">
-        </fieldset>
-        <?php
-        $tform = TicketForm::objects()->one();
-        echo $tform->getForm()->getMedia();
-        foreach ($tform->getInstance()->getFields() as $f) {
-            if (!$f->hasData())
-                continue;
-            elseif (!$f->getImpl()->hasSpecialSearch())
-                continue;
-            ?><fieldset class="span6">
-            <label><?php echo $f->getLabel(); ?>:</label><div><?php
-                     $f->render('search'); ?></div>
-            </fieldset>
-        <?php } ?>
-        <hr/>
-        <div id="result-count" class="clear"></div>
-        <p>
-            <span class="buttons pull-right">
-                <input type="submit" value="<?php echo __('Search');?>">
-            </span>
-            <span class="buttons pull-left">
-                <input type="reset" value="<?php echo __('Reset');?>">
-                <input type="button" value="<?php echo __('Cancel');?>" class="close">
-            </span>
-            <span class="spinner">
-                <img src="./images/ajax-loader.gif" width="16" height="16">
-            </span>
-        </p>
-    </form>
-</div>
 <script type="text/javascript">
 $(function() {
     $(document).off('.tickets');
diff --git a/scp/ajax.php b/scp/ajax.php
index 4ca0abf850e615d3f09479db47a904ecd511169c..ac53db3f710154af18ae70a6751529dbcbeecf92 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -137,6 +137,8 @@ $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'),
diff --git a/scp/js/scp.js b/scp/js/scp.js
index 74061b615bf10aca5cc6a603bd196e7a20d77bd1..6649ddeb2613a0d918895bfea4d0bff2cf378e37 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -338,26 +338,12 @@ var scp_prep = function() {
         return false;
     });
 
-    /* advanced search */
-    $('.dialog#advanced-search').css({
-        top  : ($(window).height() / 6),
-        left : ($(window).width() / 2 - 300)
-    });
-
     /* loading ... */
     $("#loading").css({
         top  : ($(window).height() / 3),
         left : ($(window).width() - $("#loading").outerWidth()) / 2
     });
 
-    $('#go-advanced').click(function(e) {
-        e.preventDefault();
-        $('#result-count').html('');
-        $.toggleOverlay(true);
-        $('#advanced-search').show();
-    });
-
-
     $('#advanced-search').delegate('#statusId, #flag', 'change', function() {
         switch($(this).children('option:selected').data('state')) {
             case 'closed':