diff --git a/include/ajax.search.php b/include/ajax.search.php
index b0d99f18fac9f8b323908e62c26d9a9bb6774b3c..684a2e5a43debb86b0f6a50b7d6c182d1b1a7b91 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -22,17 +22,24 @@ 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();
-        $form = $search->getForm();
-        if (isset($_SESSION['advsearch']))
-            $form->loadState($_SESSION['advsearch']);
-        $matches = Filter::getSupportedMatches();
+        // Don't send the state as the souce because it is not in the
+        // ::parse format (it's in ::to_php format)
+        $form = $search->getFormFromSession('advsearch');
+        $form->loadState($_SESSION['advsearch']);
+        $matches = self::_getSupportedTicketMatches();
 
         include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
     }
@@ -43,18 +50,49 @@ class SearchAjaxAPI extends AjaxController {
         if (!$thisstaff)
             Http::response(403, 'Agent login required');
 
+        list($type, $id) = explode('!', $name, 2);
+
+        switch (strtolower($type)) {
+        case ':ticket':
+        case ':user':
+        case ':organization':
+            // Support nested field ids for list properties and such
+            if (strpos($id, '.') !== false)
+                list(,$id) = explode('!', $id, 2);
+            if (!($field = DynamicFormField::lookup($id)))
+                Http::response(404, 'No such field: ', print_r($id, true));
+            break;
+        default:
+            Http::response(400, 'No such field type');
+        }
+
+        self::ensureConsistentFormFieldIds($_GET['ff_uid']);
+        $fields = SavedSearch::getSearchField($field->getImpl(), $name);
+        $form = new Form($fields);
+        // Check the box to search the field by default
+        if ($F = $form->getField("{$name}+search"))
+            $F->value = true;
+
+        ob_start();
+        include STAFFINC_DIR . 'templates/advanced-search-field.tmpl.php';
+        $html = ob_get_clean();
+
+        return $this->encode(array(
+            'success' => true,
+            'html' => $html,
+            'ff_uid' => FormField::$uid,
+        ));
     }
 
     function doSearch() {
         global $thisstaff;
 
         $search = SavedSearch::create();
-
-        // Add "other" fields (via $_POST['other'][])
+        self::ensureConsistentFormFieldIds();
 
         $form = $search->getForm($_POST);
         if (!$form->isValid()) {
-            $matches = Filter::getSupportedMatches();
+            $matches = self::_getSupportedTicketMatches();
             include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
             return;
         }
@@ -103,6 +141,29 @@ class SearchAjaxAPI extends AjaxController {
         )));
     }
 
+    function _getSupportedTicketMatches() {
+        // User information
+        $matches = array();
+        foreach (array('ticket'=>'TicketForm', 'user'=>'UserForm', 'organization'=>'OrganizationForm') as $k=>$F) {
+            $form = $F::objects()->one();
+            $fields = &$matches[$form->getLocal('title')];
+            foreach ($form->getFields() as $f) {
+                if (!$f->hasData() || $f->isPresentationOnly())
+                    continue;
+                $fields[":$k!".$f->get('id')] = __(ucfirst($k)).' / '.$f->getLocal('label');
+                /* TODO: Support matches on list item properties
+                if (($fi = $f->getImpl()) && $fi->hasSubFields()) {
+                    foreach ($fi->getSubFields() as $p) {
+                        $fields[":$k.".$f->get('id').'.'.$p->get('id')]
+                            = __(ucfirst($k)).' / '.$f->getLocal('label').' / '.$p->getLocal('label');
+                    }
+                }
+                */
+            }
+        }
+        return $matches;
+    }
+
     function createSearch() {
         global $thisstaff;
 
@@ -124,11 +185,12 @@ 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);
 
-        $matches = Filter::getSupportedMatches();
+        $matches = self::_getSupportedTicketMatches();
         include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
     }
 
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 60e01cce7e4b020937235fb37ad433b3173ec9fb..ebe6c6bb01138f642f03c3ac06e9350ec6935f5d 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -1319,9 +1319,9 @@ class SelectionField extends FormField {
         $name = $name ?: $this->get('name');
         switch ($method) {
         case '!includes':
-            return Q::not(array("{$name}__in" => array_keys($value)));
+            return Q::not(array("{$name}__intersect" => array_keys($value)));
         case 'includes':
-            return new Q(array("{$name}__in" => array_keys($value)));
+            return new Q(array("{$name}__intersect" => array_keys($value)));
         default:
             return parent::getSearchQ($method, $value, $name);
         }
diff --git a/include/class.forms.php b/include/class.forms.php
index a590d84057ff493e94c927b8e18a0245cee44220..fbd136a9a1e780abb3c7d1487010e4c9c046755c 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -187,7 +187,7 @@ class Form {
             if (!$v)
                 continue;
 
-            $info[$f->get('name')] = $f->to_database($v);
+            $info[$f->get('name') ?: $f->get('id')] = $f->to_database($v);
         }
         return $info;
     }
@@ -245,7 +245,7 @@ class FormField {
         ),
     );
     static $more_types = array();
-    static $uid = 100;
+    static $uid = null;
 
     function __construct($options=array()) {
         $this->ht = array_merge($this->ht, $options);
@@ -280,8 +280,10 @@ class FormField {
                 return $types[$type];
     }
 
-    function get($what) {
-        return $this->ht[$what];
+    function get($what, $default=null) {
+        return array_key_exists($what, $this->ht)
+            ? $this->ht[$what]
+            : $default;
     }
     function set($field, $value) {
         $this->ht[$field] = $value;
@@ -504,7 +506,7 @@ class FormField {
     function getSearchMethods() {
         return array(
             'set' =>        __('has a value'),
-            'notset' =>    __('does not have a value'),
+            'notset' =>     __('does not have a value'),
             'equal' =>      __('is'),
             'equal.not' =>  __('is not'),
             'contains' =>   __('contains'),
@@ -520,6 +522,8 @@ class FormField {
             'equal.not' => array('TextboxField', array()),
             'contains' => array('TextboxField', array()),
             'match' => array('TextboxField', array(
+                'placeholder' => __('Valid regular expression'),
+                'configuration' => array('size'=>30),
                 'validators' => function($self, $v) {
                     if (false === @preg_match($v, ' '))
                         $self->addError(__('Cannot compile this regular expression'));
@@ -1314,15 +1318,16 @@ class DatetimeField extends FormField {
     }
 
     function getSearchMethodWidgets() {
-        $config = $this->getConfiguration();
+        $config_notime = $config = $this->getConfiguration();
+        $config_notime['time'] = false;
         return array(
             'set' => null,
             'notset' => null,
             'equal' => array('DatetimeField', array(
-                'configuration' => $config,
+                'configuration' => $config_notime,
             )),
             'notequal' => array('DatetimeField', array(
-                'configuration' => $config,
+                'configuration' => $config_notime,
             )),
             'before' => array('DatetimeField', array(
                 'configuration' => $config,
@@ -2660,7 +2665,9 @@ class VisibilityConstraint {
     }
 
     function compileQPhp(Q $Q, $field) {
-        $form = $field->getForm();
+        if (!($form = $field->getForm())) {
+            return $this->initial == self::VISIBLE;
+        }
         $expr = array();
         foreach ($Q->constraints as $c=>$value) {
             if ($value instanceof Q) {
diff --git a/include/class.list.php b/include/class.list.php
index 8a6e4667558eb2733dfdd8cc3e9e50145bab08e9..a95429a6f2c5e2425f2c85612c75d2ae29bae964 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -1176,6 +1176,4 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
         include(STAFFINC_DIR . 'templates/status-options.tmpl.php');
     }
 }
-
-TicketStatus::_inspect();
 ?>
diff --git a/include/class.organization.php b/include/class.organization.php
index 785b6d36b489416b99c164efcb6f20865cc6f250..214f7fb2e35c7cd89cb63a6ba4fe9e375ec9bcd2 100644
--- a/include/class.organization.php
+++ b/include/class.organization.php
@@ -25,6 +25,9 @@ class OrganizationModel extends VerySimpleModel {
             'users' => array(
                 'reverse' => 'User.org',
             ),
+            'cdata' => array(
+                'constraint' => array('id' => 'OrganizationCdata.org_id'),
+            ),
         )
     );
 
@@ -100,6 +103,21 @@ class OrganizationModel extends VerySimpleModel {
     }
 }
 
+class OrganizationCdata extends VerySimpleModel {
+    static $meta = array(
+        'table' => 'org__cdata',
+        'view' => true,
+        'pk' => array('org_id'),
+    );
+
+    function getQuery($compiler) {
+        $form = OrganizationForm::getDefaultForm();
+        $exclude = array('name');
+        return '('.$form->getCrossTabQuery($form->type, 'org_id', $exclude).')';
+    }
+}
+
+
 class Organization extends OrganizationModel {
     var $_entries;
     var $_forms;
diff --git a/include/class.search.php b/include/class.search.php
index 3a75ad09d950fcfefc36b53b49af9de5c2f989dc..b43798495d2847ae270737fe91a7bd54587c05f3 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -591,10 +591,27 @@ class SavedSearch extends VerySimpleModel {
         )));
     }
 
+    function getFormFromSession($key, $source=false) {
+        if (isset($_SESSION[$key])) {
+            $source = $source ?: array();
+            $state = $_SESSION[$key];
+            // Pull out 'other' fields from the state so the fields will be
+            // 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)) {
+                    continue;
+                }
+                list($k,) = explode('+', $k, 2);
+                $source['fields'][] = ":{$info[1]}!{$info[2]}";
+            }
+        }
+        return $this->getForm($source);
+    }
+
     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(
@@ -608,8 +625,9 @@ class SavedSearch extends VerySimpleModel {
             )),
         );
         foreach ($searchable as $name=>$field) {
-            $fields = array_merge($fields, $this->getSearchField($field, $name));
+            $fields = array_merge($fields, self::getSearchField($field, $name));
         }
+
         $form = new Form($fields, $source);
         $form->addValidator(function($form) {
             $selected = 0;
@@ -621,7 +639,7 @@ class SavedSearch extends VerySimpleModel {
                     $selected += 1;
             }
             if (!$selected)
-                $form->addError('No fields selected for searching');
+                $form->addError(__('No fields selected for searching'));
         });
         return $form;
     }
@@ -654,12 +672,24 @@ class SavedSearch extends VerySimpleModel {
             )),
         );
 
-        // TODO: Add "other" fields to the basic set
+        // Add 'other' fields added dynamically
+        if (is_array($source) && isset($source['fields'])) {
+            foreach ($source['fields'] as $f) {
+                $info = array();
+                if (!preg_match('/^:(\w+)!(\d+)/', $f, $info)) {
+                    continue;
+                }
+                $id = $info[2];
+                if (is_numeric($id) && ($field = DynamicFormField::lookup($id))) {
+                    $core[":{$info[1]}!{$info[2]}"] = $field->getImpl();
+                }
+            }
+        }
 
         return $core;
     }
 
-    function getSearchField($field, $name) {
+    static function getSearchField($field, $name) {
         $pieces = array();
         $pieces["{$name}+search"] = new BooleanField(array(
             'configuration' => array('desc' => $field->get('label'))
@@ -704,6 +734,35 @@ class SavedSearch extends VerySimpleModel {
                     if ($value = $form->getField("{$name}+{$method}"))
                         $value = $value->getClean();
 
+                    if ($name[0] == ':') {
+                        // This was an 'other' field, fetch a special "name"
+                        // for it which will be the ORM join path
+                        static $other_paths = array(
+                            ':ticket' => 'cdata__',
+                            ':user' => 'user__cdata__',
+                            ':organization' => 'user__org__cdata__',
+                        );
+                        $column = $field->get('name') ?: 'field_'.$field->get('id');
+                        foreach ($other_paths as $k => $OP) {
+                            if (substr($name, 0, strlen($k)) == $k) {
+                                // XXX: Last mile — find a better idea
+                                switch (array($k, $column)) {
+                                case array(':user', 'name'):
+                                    $name = 'user__name';
+                                    break;
+                                case array(':user', 'email'):
+                                    $name = 'user__emails__address';
+                                    break;
+                                case array(':organization', 'name'):
+                                    $name = 'user__org__name';
+                                    break;
+                                default:
+                                    $name = $OP . $column;
+                                }
+                            }
+                        }
+                    }
+
                     // Add the criteria to the QuerySet
                     if ($Q = $field->getSearchQ($method, $value, $name))
                         $qs = $qs->filter($Q);
diff --git a/include/class.staff.php b/include/class.staff.php
index 77d5384befe7bc3f17dac41b6075f9b78766a2e7..1f7a3a3dcb711c34c045b97945cf663a78280192 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -608,7 +608,7 @@ implements AuthenticatedUser {
         return $users;
     }
 
-    function getAvailableStaffMembers() {
+    static function getAvailableStaffMembers() {
         return self::getStaffMembers(true);
     }
 
diff --git a/include/class.user.php b/include/class.user.php
index 13521ad42cf1ff2b95238767f833face6a1357cc..c1cae53106c8e75c49f2782f0017bf080236bc66 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -125,6 +125,20 @@ class UserModel extends VerySimpleModel {
     }
 }
 
+class UserCdata extends VerySimpleModel {
+    static $meta = array(
+        'table' => 'user__cdata',
+        'view' => true,
+        'pk' => array('user_id'),
+    );
+
+    static function getQuery($compiler) {
+        $form = UserForm::getUserForm();
+        $exclude = array('name', 'email');
+        return '('.$form->getCrossTabQuery($form->type, 'user_id', $exclude).')';
+    }
+}
+
 class User extends UserModel {
 
     var $_entries;
diff --git a/include/staff/templates/advanced-search.tmpl.php b/include/staff/templates/advanced-search.tmpl.php
index beb0c5cf2e3196dbcc79381c847df513b2d794ad..e9f275f027eb8093a9cf383db3b9b5a07aad354c 100644
--- a/include/staff/templates/advanced-search.tmpl.php
+++ b/include/staff/templates/advanced-search.tmpl.php
@@ -1,4 +1,5 @@
 <?php
+  $ff_uid = FormField::$uid;
 ?>
 <div id="advanced-search">
 <h3><?php echo __('Advanced Ticket Search');?></h3>
@@ -21,17 +22,23 @@ foreach ($form->getFields() as $name=>$field) { ?>
             ?><div class="error"><?php echo $E; ?></div><?php
         } ?>
     </fieldset>
-<?php }
+    <?php if ($name[0] == ':') { ?>
+    <input type="hidden" name="fields[]" value="<?php echo $name; ?>"/>
+    <?php }
+}
 ?>
+<div id="extra-fields"></div>
 <hr/>
-<select name="new-field" style="max-width: 100%;">
+<select id="search-add-new-field" name="new-field" style="max-width: 100%;">
     <option value="">— <?php echo __('Add Other Field'); ?> —</option>
 <?php
 foreach ($matches as $name => $fields) { ?>
     <optgroup label="<?php echo $name; ?>">
 <?php
     foreach ($fields as $id => $desc) { ?>
-        <option value="<?php echo $id; ?>"><?php echo $desc; ?></option>
+        <option value="<?php echo $id; ?>" <?php
+            if (isset($state[$id])) echo 'disabled="disabled"';
+        ?>><?php echo $desc; ?></option>
 <?php } ?>
     </optgroup>
 <?php } ?>
@@ -158,5 +165,23 @@ $(function() {
       return false;
     });
   }, 200);
+
+  var ff_uid = <?php echo $ff_uid; ?>;
+  $('#search-add-new-field').on('change', function() {
+    var that=this;
+    $.ajax({
+      url: 'ajax.php/tickets/search/field/'+$(this).val(),
+      type: 'get',
+      data: {ff_uid: ff_uid},
+      dataType: 'json',
+      success: function(json) {
+        if (!json.success)
+          return false;
+        ff_uid = json.ff_uid;
+        $(that).find(':selected').prop('disabled', true);
+        $('#extra-fields').append($(json.html));
+      }
+    });
+  });
 });
 </script>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index cb0c1011c644a881a8f187bfd8887b1f6b20a8b4..a1154dbcb564e7bd8161ad1a5ed3eb1fbfb21da4 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -31,8 +31,8 @@ default:
     if (isset($_GET['clear_filter']))
         unset($_SESSION['advsearch']);
     if (isset($_SESSION['advsearch'])) {
-        $form = $search->getForm();
-            $form->loadState($_SESSION['advsearch']);
+        $form = $search->getFormFromSession('advsearch');
+        $form->loadState($_SESSION['advsearch']);
         $tickets = $search->mangleQuerySet($tickets, $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/scp/ajax.php b/scp/ajax.php
index d040343e28995e0601cd50836e7577939b428b92..dc0d35e34821a50a57ff886ff52377c806781846 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -153,7 +153,7 @@ $dispatcher = patterns('',
             url_post('^/(?P<id>\d+)$', 'saveSearch'),
             url_delete('^/(?P<id>\d+)$', 'deleteSearch'),
             url_post('^/create$', 'createSearch'),
-            url_get('^/field/(?P<id>\d+)$', 'getField')
+            url_get('^/field/(?P<id>[\w_!:]+)$', 'addField')
         ))
     )),
     url('^/collaborators/', patterns('ajax.tickets.php:TicketsAjaxAPI',