diff --git a/include/ajax.search.php b/include/ajax.search.php
index d228018e911f963d9ec8b201aca8d2921e201c70..23e4a6bec9cef6ff24b535c4bcb7f15674186330 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -22,18 +22,12 @@ require_once(INCLUDE_DIR.'class.ajax.php');
 
 class SearchAjaxAPI extends AjaxController {
 
-    static function ensureConsistentFormFieldIds($reset=false) {
-        // Maintain unique form field IDs over the life of the session
-        FormField::$uid = $reset ?: 1000;
-    }
-
     function getAdvancedSearchDialog() {
         global $thisstaff;
 
         if (!$thisstaff)
             Http::response(403, 'Agent login required');
 
-        self::ensureConsistentFormFieldIds();
         $search = SavedSearch::create();
         // Don't send the state as the souce because it is not in the
         // ::parse format (it's in ::to_php format)
@@ -50,7 +44,7 @@ class SearchAjaxAPI extends AjaxController {
         if (!$thisstaff)
             Http::response(403, 'Agent login required');
 
-        list($type, $id) = explode('!', $name, 2);
+        @list($type, $id) = explode('!', $name, 2);
 
         switch (strtolower($type)) {
         case ':ticket':
@@ -62,17 +56,25 @@ class SearchAjaxAPI extends AjaxController {
                 list(,$id) = explode('!', $id, 2);
             if (!($field = DynamicFormField::lookup($id)))
                 Http::response(404, 'No such field: ', print_r($id, true));
+
+            $impl = $field->getImpl();
+            $impl->set('label', sprintf('%s / %s',
+                $field->form->getLocal('title'), $field->getLocal('label')
+            ));
             break;
+
         default:
+            // The "extended" fields are build automatically and rooted from
+            // the base of the ID numbers
+            $extended = SavedSearch::getExtendedTicketFields();
+
+            if (isset($extended[$name])) {
+                $impl = $extended[$name];
+                break;
+            }
             Http::response(400, 'No such field type');
         }
 
-        self::ensureConsistentFormFieldIds($_GET['ff_uid']);
-
-        $impl = $field->getImpl();
-        $impl->set('label', sprintf('%s / %s',
-            $field->form->getLocal('title'), $field->getLocal('label')
-        ));
         $fields = SavedSearch::getSearchField($impl, $name);
         $form = new SimpleForm($fields);
         // Check the box to search the field by default
@@ -96,7 +98,6 @@ class SearchAjaxAPI extends AjaxController {
         global $thisstaff;
 
         $search = SavedSearch::create();
-        self::ensureConsistentFormFieldIds();
 
         $form = $search->getForm($_POST);
         if (!$form->isValid()) {
@@ -132,7 +133,6 @@ class SearchAjaxAPI extends AjaxController {
             else
                 $data[$name] = $info['value'];
         }
-        self::ensureConsistentFormFieldIds();
         $form = $search->getForm($data);
         if (!$data || !$form->isValid()) {
             Http::response(422, 'Validation errors exist on form');
@@ -152,7 +152,9 @@ class SearchAjaxAPI extends AjaxController {
 
     function _getSupportedTicketMatches() {
         // User information
-        $matches = array();
+        $matches = array(
+            __('Ticket Built-In') => SavedSearch::getExtendedTicketFields(),
+        );
         foreach (array('ticket'=>'TicketForm', 'user'=>'UserForm', 'organization'=>'OrganizationForm') as $k=>$F) {
             $form = $F::objects()->one();
             $fields = &$matches[$form->getLocal('title')];
@@ -203,7 +205,6 @@ class SearchAjaxAPI extends AjaxController {
             Http::response(404, 'No such saved search');
         }
 
-        self::ensureConsistentFormFieldIds();
         $form = $search->getForm();
         if ($state = JsonDataParser::parse($search->config))
             $form->loadState($state);
diff --git a/include/class.forms.php b/include/class.forms.php
index f4f5491226e4e63f36b338bb3b18a49c1174ff48..a0192de63c2affad1f24e7db991a0b6ab2a5c51b 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -388,6 +388,10 @@ class FormField {
         $this->ht[$field] = $value;
     }
 
+    function getId() {
+        return $this->ht['id'];
+    }
+
     /**
      * getClean
      *
@@ -422,7 +426,7 @@ class FormField {
         return $this->_clean;
     }
     function reset() {
-        $this->_clean = $this->_widget = null;
+        $this->value = $this->_clean = $this->_widget = null;
     }
 
     function getValue() {
@@ -2918,7 +2922,9 @@ class CheckboxWidget extends Widget {
         $data = $this->field->getSource();
         if (count($data)) {
             if (!isset($data[$this->name]))
-                return false;
+                // Indeterminite. Likely false, but consider current field
+                // value
+                return null;
             return @in_array($this->field->get('id'), $data[$this->name]);
         }
         return parent::getValue();
diff --git a/include/class.orm.php b/include/class.orm.php
index eab05d3a074fa805a428a5c1ab93ad50efd07a19..cd8992ae881e1917e18ad89cd6014282f5bdc639 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -158,6 +158,11 @@ class ModelMeta implements ArrayAccess {
         $j['constraint'] = $constraint;
     }
 
+    function addJoin($name, array $join) {
+        $this->base['joins'][$name] = $join;
+        $this->processJoin($this->base['joins'][$name]);
+    }
+
     function offsetGet($field) {
         if (!isset($this->base[$field]))
             $this->setupLazy($field);
diff --git a/include/class.search.php b/include/class.search.php
index bfc59f4c07101a850e91f92ba983457d4e44de88..43a9cb30c182d76b849ece53538812ea5abe44f5 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -327,6 +327,8 @@ class MysqlSearchBackend extends SearchBackend {
     function find($query, QuerySet $criteria) {
         global $thisstaff;
 
+        $criteria = clone $criteria;
+
         $mode = ' IN BOOLEAN MODE';
         #if (count(explode(' ', $query)) == 1)
         #    $mode = ' WITH QUERY EXPANSION';
@@ -627,11 +629,11 @@ class SavedSearch extends VerySimpleModel {
             // added to the form. The state will be loaded below
             foreach ($state as $k=>$v) {
                 $info = array();
-                if (!preg_match('/^:(\w+)!(\d+)\+search/', $k, $info)) {
+                if (!preg_match('/^:(\w+)(?:!(\d+))?\+search/', $k, $info)) {
                     continue;
                 }
                 list($k,) = explode('+', $k, 2);
-                $source['fields'][] = ":{$info[1]}!{$info[2]}";
+                $source['fields'][] = $k;
             }
         }
         return $this->getForm($source);
@@ -644,6 +646,7 @@ class SavedSearch extends VerySimpleModel {
         $searchable = $this->getCurrentSearchFields($source);
         $fields = array(
             'keywords' => new TextboxField(array(
+                'id' => 3001,
                 'configuration' => array(
                     'size' => 40,
                     'length' => 400,
@@ -675,36 +678,42 @@ class SavedSearch extends VerySimpleModel {
 
     function getCurrentSearchFields($source=false) {
         $core = array(
-            'state' =>      new TicketStateChoiceField(array(
-                'label' => __('State'),
-            )),
             'status_id' =>  new TicketStatusChoiceField(array(
+                'id' => 3101,
                 'label' => __('Status'),
             )),
-            'flags' =>      new TicketFlagChoiceField(array(
-                'label' => __('Flags'),
-            )),
             'dept_id'   =>  new DepartmentChoiceField(array(
+                'id' => 3102,
                 'label' => __('Department'),
             )),
             'assignee'  =>  new AssigneeChoiceField(array(
+                'id' => 3103,
                 'label' => __('Assignee'),
             )),
             'topic_id'  =>  new HelpTopicChoiceField(array(
+                'id' => 3104,
                 'label' => __('Help Topic'),
             )),
             'created'   =>  new DateTimeField(array(
+                'id' => 3105,
                 'label' => __('Created'),
             )),
             'duedate'   =>  new DateTimeField(array(
+                'id' => 3106,
                 'label' => __('Due Date'),
             )),
         );
 
+        $extended = self::getExtendedTicketFields();
+
         // Add 'other' fields added dynamically
         if (is_array($source) && isset($source['fields'])) {
             foreach ($source['fields'] as $f) {
                 $info = array();
+                if (isset($extended[$f])) {
+                    $core[$f] = $extended[$f];
+                    continue;
+                }
                 if (!preg_match('/^:(\w+)!(\d+)/', $f, $info)) {
                     continue;
                 }
@@ -718,27 +727,56 @@ class SavedSearch extends VerySimpleModel {
                 }
             }
         }
-
         return $core;
     }
 
+    static function getExtendedTicketFields() {
+        return array(
+#            ':user' =>       new UserChoiceField(array(
+#                'label' => __('Ticket Owner'),
+#            )),
+#            ':org' =>        new OrganizationChoiceField(array(
+#                'label' => __('Organization'),
+#            )),
+            ':source' =>     new TicketSourceChoiceField(array(
+                'id' => 3201,
+                'label' => __('Source'),
+            )),
+            ':state' =>      new TicketStateChoiceField(array(
+                'id' => 3202,
+                'label' => __('State'),
+            )),
+            ':flags' =>      new TicketFlagChoiceField(array(
+                'id' => 3203,
+                'label' => __('Flags'),
+            )),
+        );
+    }
+
     static function getSearchField($field, $name) {
+        $baseId = $field->getId() * 20;
         $pieces = array();
         $pieces["{$name}+search"] = new BooleanField(array(
-            'configuration' => array('desc' => $field->get('label'))
+            'id' => $baseId + 50000,
+            'configuration' => array(
+                'desc' => $field->get('label'),
+            ),
         ));
         $methods = $field->getSearchMethods();
         $pieces["{$name}+method"] = new ChoiceField(array(
+            'id' => $baseId + 50001,
             'choices' => $methods,
             'default' => key($methods),
             'visibility' => new VisibilityConstraint(new Q(array(
                 "{$name}+search__eq" => true,
             )), VisibilityConstraint::HIDDEN),
         ));
+        $offs = 0;
         foreach ($field->getSearchMethodWidgets() as $m=>$w) {
             if (!$w)
                 continue;
             list($class, $args) = $w;
+            $args['id'] = $baseId + 50002 + $offs++;
             $args['required'] = true;
             $args['visibility'] = new VisibilityConstraint(new Q(array(
                     "{$name}+method__eq" => $m,
@@ -1009,6 +1047,29 @@ class TicketFlagChoiceField extends ChoiceField {
     }
 }
 
+class TicketSourceChoiceField extends ChoiceField {
+    function getChoices() {
+        return array(
+            'w' => __('Web'),
+            'e' => __('Email'),
+            'p' => __('Phone'),
+            'a' => __('API'),
+            'o' => __('Other'),
+        );
+    }
+
+    function getSearchMethods() {
+        return array(
+            'includes' =>   __('is'),
+            '!includes' =>  __('is not'),
+        );
+    }
+
+    function getSearchQ($method, $value, $name=false) {
+        return parent::getSearchQ($method, $value, 'source');
+    }
+}
+
 class TicketStatusChoiceField extends SelectionField {
     static $widget = 'ChoicesWidget';
 
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 35e5f1e794f01d83b20163f449cd083c1ff535ed..86634603200f535244e7bacfc261d4e6566f0d26 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -155,7 +155,8 @@ class DynamicForm{$form->id} extends DynamicForm {
         return \$instance;
     }
 }
-class TicketCdataForm{$form->id} {
+class TicketCdataForm{$form->id}
+extends VerySimpleModel {
     static \$meta = array(
         'view' => true,
         'pk' => array('ticket_id'),
@@ -171,13 +172,19 @@ class TicketCdataForm{$form->id} {
 }
 EOF;
             eval($cdata_class);
-            static::$meta['joins']['cdata+'.$form->id] = array(
-                'reverse' => 'TicketCdataForm'.$form->id.'.ticket',
-                'null' => true,
+            $join = array(
+                'constraint' => array('ticket_id' => 'TicketCdataForm'.$form->id.'.ticket_id'),
+                'list' => true,
             );
             // This may be necessary if the model has already been inspected
             if (static::$meta instanceof ModelMeta)
-                static::$meta->processJoin(static::$meta['joins']['cdata+'.$form->id]);
+                static::$meta->addJoin('cdata+'.$form->id, $join);
+            else {
+                static::$meta['joins']['cdata+'.$form->id] = array(
+                    'constraint' => array('ticket_id' => 'TicketCdataForm'.$form->id.'.ticket_id'),
+                    'list' => true,
+                );
+            }
         }
     }
 
diff --git a/include/staff/tasks.inc.php b/include/staff/tasks.inc.php
index 01c0be6986e239d6ac055d5559d598f9c200daee..ab826da6aeb550dd0fdcb42c2a4c62a0d86f410b 100644
--- a/include/staff/tasks.inc.php
+++ b/include/staff/tasks.inc.php
@@ -42,10 +42,10 @@ case 'search':
             'cdata__title__contains' => $_REQUEST['query'],
         )));
         break;
-    } elseif (isset($_SESSION['advsearch'])) {
+    } elseif (isset($_SESSION['advsearch:tasks'])) {
         // XXX: De-duplicate and simplify this code
-        $form = $search->getFormFromSession('advsearch');
-        $form->loadState($_SESSION['advsearch']);
+        $form = $search->getFormFromSession('advsearch:tasks');
+        $form->loadState($_SESSION['advsearch:tasks']);
         $tasks = $search->mangleQuerySet($tasks, $form);
         $results_type=__('Advanced Search')
             . '<a class="action-button" href="?clear_filter"><i class="icon-ban-circle"></i> <em>' . __('clear') . '</em></a>';
diff --git a/include/staff/templates/advanced-search.tmpl.php b/include/staff/templates/advanced-search.tmpl.php
index cfe1971b1e6d8325c24df867a5ecd5dd235bebca..9ce8ff2dca0baecbd775c9802195f85b9ce09f26 100644
--- a/include/staff/templates/advanced-search.tmpl.php
+++ b/include/staff/templates/advanced-search.tmpl.php
@@ -16,14 +16,16 @@ foreach ($form->errors(true) ?: array() as $message) {
 
 foreach ($form->getFields() as $name=>$field) { ?>
     <fieldset id="field<?php echo $field->getWidget()->id;
-        ?>" <?php if (!$field->isVisible()) echo 'style="display:none;"'; ?>>
+        ?>" <?php if (!$field->isVisible()) echo 'class="hidden"'; ?>>
         <?php echo $field->render(); ?>
         <?php foreach ($field->errors() as $E) {
             ?><div class="error"><?php echo $E; ?></div><?php
         } ?>
     </fieldset>
-    <?php if ($name[0] == ':') { ?>
-    <input type="hidden" name="fields[]" value="<?php echo $name; ?>"/>
+    <?php if ($name[0] == ':' && substr($name, -7) == '+search') {
+        list($N,) = explode('+', $name, 2);
+?>
+    <input type="hidden" name="fields[]" value="<?php echo $N; ?>"/>
     <?php }
 }
 ?>
@@ -38,7 +40,7 @@ foreach ($matches as $name => $fields) { ?>
     foreach ($fields as $id => $desc) { ?>
         <option value="<?php echo $id; ?>" <?php
             if (isset($state[$id])) echo 'disabled="disabled"';
-        ?>><?php echo $desc; ?></option>
+        ?>><?php echo ($desc instanceof FormField ? $desc->getLocal('label') : $desc); ?></option>
 <?php } ?>
     </optgroup>
 <?php } ?>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index d3a57023703a7427d13d75df43df021622235d09..ff8b36ffdcae657b0ffc3c3e0f903d45345a9db4 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -97,6 +97,9 @@ case 'search':
                 $tickets = $ost->searcher->find($_REQUEST['query'], $tickets);
                 $keywords = array_pop($tickets->constraints);
                 $basic_search->add($keywords);
+                // FIXME: The subquery technique below will crash with
+                //        keyword search
+                $use_subquery = false;
             }
             $tickets->filter($basic_search);
         }
diff --git a/scp/tasks.php b/scp/tasks.php
index ab0996e8822a3370f33b8b8e8d64cb62ba297d9c..2f4791356b83b0bc46dcde1b96b4e36624828f8f 100644
--- a/scp/tasks.php
+++ b/scp/tasks.php
@@ -82,7 +82,7 @@ $stats= $thisstaff->getTasksStats();
 
 // Clear advanced search upon request
 if (isset($_GET['clear_filter']))
-    unset($_SESSION['advsearch']);
+    unset($_SESSION['advsearch:tasks']);
 
 //Navigation
 $nav->setTabActive('tasks');
@@ -94,7 +94,7 @@ $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']).')'
                        'title'=>__('Open Tasks'),
                        'href'=>'tasks.php?status=open',
                        'iconclass'=>'Ticket'),
-                    ((!$_REQUEST['status'] && !isset($_SESSION['advsearch'])) || $_REQUEST['status']=='open'));
+                    ((!$_REQUEST['status'] && !isset($_SESSION['advsearch:tasks'])) || $_REQUEST['status']=='open'));
 
 if ($stats['assigned']) {
 
@@ -124,11 +124,11 @@ if ($stats['closed']) {
                         ($_REQUEST['status']=='closed'));
 }
 
-if (isset($_SESSION['advsearch'])) {
+if (isset($_SESSION['advsearch:tasks'])) {
     // XXX: De-duplicate and simplify this code
     $search = SavedSearch::create();
-    $form = $search->getFormFromSession('advsearch');
-    $form->loadState($_SESSION['advsearch']);
+    $form = $search->getFormFromSession('advsearch:tasks');
+    $form->loadState($_SESSION['advsearch:tasks']);
     $tasks = Task::objects();
     $tasks = $search->mangleQuerySet($tasks, $form);
     $count = $tasks->count();