From c7d7f4030abbafae1ed6ee71112b0e08eaf3a657 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Wed, 30 Sep 2015 19:09:12 -0500
Subject: [PATCH] queue: Fix dropdowns for data-source and filter

And add options for custom data form fields
---
 include/ajax.search.php                       |  6 +-
 include/class.dept.php                        |  4 ++
 include/class.list.php                        |  4 ++
 include/class.organization.php                |  4 ++
 include/class.queue.php                       | 63 +++++++++-------
 include/class.search.php                      | 71 ++++++++++++++++---
 include/class.staff.php                       |  4 ++
 include/class.ticket.php                      |  4 ++
 include/class.topic.php                       |  4 ++
 include/class.user.php                        |  4 ++
 include/staff/queue.inc.php                   | 10 +--
 .../templates/queue-column-condition.tmpl.php |  2 +-
 include/staff/templates/queue-column.tmpl.php |  3 +-
 13 files changed, 140 insertions(+), 43 deletions(-)

diff --git a/include/ajax.search.php b/include/ajax.search.php
index be38dbf4a..d670d6caf 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -256,7 +256,7 @@ class SearchAjaxAPI extends AjaxController {
                 Format::htmlchars($_GET['field']));
         }
       
-        $field = $fields[$_GET['field']];
+        list($label, $field) = $fields[$_GET['field']];
         // Ensure `name` is preserved
         $field_name = $_GET['field'];
         $id = $_GET['id'];
@@ -299,10 +299,10 @@ class SearchAjaxAPI extends AjaxController {
         }
 
         // Get the tabbed column configuration
-        $F = $fields[$field];
+        list($label, $F) = $fields[$field];
         $column = QueueColumn::create(array(
             "id"        => (int) $_GET['id'],
-            "heading"   => _S($F->getLabel()),
+            "heading"   => _S($field->getLabel()),
             "primary"   => $field,
             "width"     => 100,
         ));
diff --git a/include/class.dept.php b/include/class.dept.php
index b54913af4..93e1b4673 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -110,6 +110,10 @@ implements TemplateVariable, Searchable {
         );
     }
 
+    static function supportsCustomData() {
+        return false;
+    }
+
     function getId() {
         return $this->id;
     }
diff --git a/include/class.list.php b/include/class.list.php
index 20bc6b87b..3cd9eec9e 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -1285,6 +1285,10 @@ implements CustomListItem, TemplateVariable, Searchable {
         );
     }
 
+    static function supportsCustomData() {
+        return false;
+    }
+
     function getList() {
         if (!isset($this->_list))
             $this->_list = DynamicList::lookup(array('type' => 'ticket-status'));
diff --git a/include/class.organization.php b/include/class.organization.php
index db49618cb..2a9571367 100644
--- a/include/class.organization.php
+++ b/include/class.organization.php
@@ -356,6 +356,10 @@ implements TemplateVariable, Searchable {
         return $base;
     }
 
+    static function supportsCustomData() {
+        return true;
+    }
+
     function update($vars, &$errors) {
 
         $valid = true;
diff --git a/include/class.queue.php b/include/class.queue.php
index e18749e29..d028eef95 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -185,7 +185,7 @@ class CustomQueue extends SavedSearch {
     function getQuickFilterField($value=null) {
         if ($this->filter
             && ($fields = SavedSearch::getSearchableFields($this->getRoot()))
-            && ($f = @$fields[$this->filter])
+            && (list(,$f) = @$fields[$this->filter])
             && $f->supportsQuickFilter()
         ) {
             $f->value = $value;
@@ -407,7 +407,8 @@ extends ChoiceField {
         $root = $config['root'];
         $fields = array();
         foreach (SavedSearch::getSearchableFields($root) as $path=>$f) {
-            $fields[$path] = $f->get('label');
+            list($label,) = $f;
+            $fields[$path] = $label;
         }
         return $fields;
     }
@@ -439,16 +440,20 @@ class QueueColumnCondition {
         ));
     }
 
-    function getField() {
-      // FIXME
-      #$root = $this->getColumn()->getQueue()->getRoot();
-      $root = 'Ticket';
-      $searchable = SavedSearch::getSearchableFields($root);
-      list($name, $method, $value) = $this->config['crit'];
+    function getField($name=null) {
+        // FIXME
+        #$root = $this->getColumn()->getQueue()->getRoot();
+        $root = 'Ticket';
+        $searchable = SavedSearch::getSearchableFields($root);
+
+        if (!isset($name))
+            list($name) = $this->config['crit'];
 
-      // Lookup the field to search this condition
-      if (isset($searchable[$name]))
-          return $searchable[$name];
+        // Lookup the field to search this condition
+        if (isset($searchable[$name])) {
+            list(,$field) = $searchable[$name];
+            return $field;
+        }
     }
 
     function getFieldName() {
@@ -460,7 +465,7 @@ class QueueColumnCondition {
         list($name, $method, $value) = $this->config['crit'];
 
         // Fetch a criteria Q for the query
-        if ($field = $this->getField())
+        if ($field = $this->getField($name))
             return $field->getSearchQ($method, $value, $name);
     }
 
@@ -478,7 +483,7 @@ class QueueColumnCondition {
                     continue;
 
                 // Lookup the field to search this condition
-                $field = $searchable[$name];
+                list($label, $field) = $searchable[$name];
 
                 // Get the search method and value
                 $method = $v;
@@ -709,11 +714,18 @@ extends VerySimpleModel {
 
         // TODO: Consider data filter if configured
 
-        if (($F = $fields[$primary]) && ($T = $F->from_query($row, $primary)))
-            return $F->display($F->to_php($T));
-
-        if (($F = $fields[$secondary]) && ($T = $F->from_query($row, $secondary)))
-            return $F->display($F->to_php($T));
+        if (($F = $fields[$primary])
+            && (list(,$field) = $F)
+            && ($T = $field->from_query($row, $primary))
+        ) {
+            return $field->display($field->to_php($T));
+        }
+        if (($F = $fields[$secondary])
+            && (list(,$field) = $F)
+            && ($T = $F->from_query($row, $secondary))
+        ) {
+            return $field->display($field->to_php($T));
+        }
     }
 
     function getTruncateClass() {
@@ -731,13 +743,16 @@ extends VerySimpleModel {
     function mangleQuery($query) {
         // Basic data
         $fields = SavedSearch::getSearchableFields($this->getQueue()->getRoot());
-        if ($primary = $fields[$this->primary])
-            $query = $primary->addToQuery($query,
+        if ($primary = $fields[$this->primary]) {
+            list(,$field) = $primary;
+            $query = $field->addToQuery($query,
                 $this->getOrmPath($this->primary));
-
-        if ($secondary = $fields[$this->secondary])
-            $query = $secondary->addToQuery($query,
+        }
+        if ($secondary = $fields[$this->secondary]) {
+            list(,$field) = $secondary;
+            $query = $field->addToQuery($query,
                 $this->getOrmPath($this->secondary));
+        }
 
         switch ($this->link) {
         // XXX: Consider the ROOT of the related queue
@@ -824,7 +839,7 @@ extends VerySimpleModel {
                 if (!isset($fields[$name]))
                     // No such field exists for this queue root type
                     continue;
-                $field = $fields[$name];
+                list(,$field) = $fields[$name];
                 $parts = SavedSearch::getSearchField($field, $name);
                 $search_form = new SimpleForm($parts, $vars, array('id' => $id));
                 $search_form->getField("{$name}+search")->value = true;
diff --git a/include/class.search.php b/include/class.search.php
index 862567654..eea827a83 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -881,31 +881,78 @@ class SavedSearch extends VerySimpleModel {
         );
     }
 
-    static function getSearchableFields($base, $recurse=2, $cache=true) {
-        static $cache;
+    /**
+     * Parameters:
+     * $base - Class, name of a class implementing Searchable
+     * $recurse - int, number of levels to recurse, default is 2
+     * $cache - bool, cache results for future class for the same base
+     * $customData - bool, include all custom data fields for all general
+     *      forms
+     */
+    static function getSearchableFields($base, $recurse=2, $cache=true,
+        $customData=true
+    ) {
+        static $cache, $otherFields;
 
         if (!in_array('Searchable', class_implements($base)))
             return array();
 
-        // FIXME: The fields from dynamicFormFields seem to be cached, and
-        // setting the label is preserved across multiple calls to this
-        // function. The caching helps with this phenomenon, but a better
-        // mechanism should be employed
+        // Early exit if already cached
         if ($cache && isset($cache[$base]))
             return $cache[$base];
 
-        $fields = $base::getSearchableFields();
+        $fields = array();
+        foreach ($base::getSearchableFields() as $path=>$F) {
+            if (is_array($F)) {
+                list($label, $field) = $F;
+            }
+            else {
+                $label = $F->get('label');
+                $field = $F;
+            }
+            $fields[$path] = array($label, $field);
+        }
+
         if ($recurse) {
             foreach ($base::getMeta('joins') as $path=>$j) {
                 $fc = $j['fkey'][0];
                 if ($fc == $base || $j['list'] || $j['reverse'])
                     continue;
-                foreach (static::getSearchableFields($fc, $recurse-1, false) as $path2=>$F) {
-                    $fields["{$path}__{$path2}"] = $F;
-                    $F->set('label', sprintf("%s / %s", $fc, $F->get('label')));
+                foreach (static::getSearchableFields($fc, $recurse-1, false)
+                as $path2=>$F) {
+                    if (is_array($F)) {
+                        list($label, $field) = $F;
+                    }
+                    else {
+                        $label = $F->get('label');
+                        $field = $F;
+                    }
+                    $fields["{$path}__{$path2}"] = array(
+                        sprintf("%s / %s", $fc, $label),
+                        $field);
                 }
             }
         }
+
+        if ($customData && $base::supportsCustomData()) {
+            if (!isset($otherFields)) {
+                $otherFields = array();
+                $forms = DynamicForm::objects()->filter(array('type'=>'G'));
+                foreach ($forms as $F) {
+                    foreach ($F->getFields() as $field) {
+                        $otherFields[$field->getId()] = array($F, $field);
+                    }
+                }
+            }
+            foreach ($otherFields as $id=>$F) {
+                list($form, $field) = $F;
+                $label = sprintf("%s / %s",
+                    $form->getTitle(), $field->get('label'));
+                $fields["entries__answers!{$id}__value"] = array(
+                    $label, $field);
+            }
+        }
+
         if ($cache)
             $cache[$base] = $fields;
         return $fields;
@@ -1355,4 +1402,8 @@ interface Searchable {
     // used when this list is rendered in a dropdown, and the field search
     // mechanisms are use to apply query filtering based on the field.
     static function getSearchableFields();
+
+    // Determine if the object supports abritrary form additions, through
+    // the "Manage Forms" dialog usually
+    static function supportsCustomData();
 }
diff --git a/include/class.staff.php b/include/class.staff.php
index ce922b1af..4f0784b52 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -135,6 +135,10 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         );
     }
 
+    static function supportsCustomData() {
+        return false;
+    }
+
     function getHashtable() {
         $base = $this->ht;
         unset($base['teams']);
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 0fc4f021d..773296f5a 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -1998,6 +1998,10 @@ implements RestrictedAccess, Threadable, Searchable {
         return $base;
     }
 
+    static function supportsCustomData() {
+        return true;
+    }
+
     //Replace base variables.
     function replaceVars($input, $vars = array()) {
         global $ost;
diff --git a/include/class.topic.php b/include/class.topic.php
index ead2e57b6..c2506b421 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -99,6 +99,10 @@ implements TemplateVariable, Searchable {
         );
     }
 
+    static function supportsCustomData() {
+        return false;
+    }
+
     function getId() {
         return $this->topic_id;
     }
diff --git a/include/class.user.php b/include/class.user.php
index 7a5fe572a..22b996bc4 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -377,6 +377,10 @@ implements TemplateVariable, Searchable {
         return $base;
     }
 
+    static function supportsCustomData() {
+        return true;
+    }
+
     function addDynamicData($data) {
         return $this->addForm(UserForm::objects()->one(), 1, $data);
     }
diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php
index 8d4cdd509..bfe6b4476 100644
--- a/include/staff/queue.inc.php
+++ b/include/staff/queue.inc.php
@@ -88,12 +88,13 @@ else {
         <select name="filter">
           <option value="::">— <?php echo __('Inherit from parent'); ?> —</option>
 <?php foreach (SavedSearch::getSearchableFields('Ticket') as $path=>$f) {
-        if (!$f->supportsQuickFilter())
+        list($label, $field) = $f;
+        if (!$field->supportsQuickFilter())
           continue;
 ?>
           <option value="<?php echo $path; ?>"
             <?php if ($path == $queue->filter) echo 'selected="selected"'; ?>
-            ><?php echo $f->get('label'); ?></option>
+            ><?php echo $label; ?></option>
 <?php } ?>
         </select>
         <br/>
@@ -140,8 +141,9 @@ else {
         });
       ">
         <option value="">— <?php echo __('Add a column'); ?> —</option>
-<?php foreach (SavedSearch::getSearchableFields('Ticket') as $path=>$f) { ?>
-        <option value="<?php echo $path; ?>"><?php echo $f->get('label'); ?></option>
+<?php foreach (SavedSearch::getSearchableFields('Ticket') as $path=>$f) {
+        list($label,) = $f; ?>
+        <option value="<?php echo $path; ?>"><?php echo $label; ?></option>
 <?php } ?>
       </select>
 
diff --git a/include/staff/templates/queue-column-condition.tmpl.php b/include/staff/templates/queue-column-condition.tmpl.php
index 659158d06..ea090ac36 100644
--- a/include/staff/templates/queue-column-condition.tmpl.php
+++ b/include/staff/templates/queue-column-condition.tmpl.php
@@ -16,7 +16,7 @@
     <a href="#" onclick="javascript: $(this).closest('.condition').remove();
       "><i class="icon-trash"></i></a>
   </div>
-  <?php echo $field->get('label'); ?>
+  <?php echo $label ?: $field->getLabel(); ?>
   <div class="advanced-search">
 <?php
 $parts = SavedSearch::getSearchField($field, $field_name);
diff --git a/include/staff/templates/queue-column.tmpl.php b/include/staff/templates/queue-column.tmpl.php
index d8d9f6de7..65b251338 100644
--- a/include/staff/templates/queue-column.tmpl.php
+++ b/include/staff/templates/queue-column.tmpl.php
@@ -125,7 +125,8 @@ if ($column->getConditions()) {
         <option>— <?php echo __("Add a condition"); ?> —</option>
 <?php
       foreach (SavedSearch::getSearchableFields('Ticket') as $path=>$f) {
-          echo sprintf('<option value="%s">%s</option>', $path, Format::htmlchars($f->get('label')));
+          list($label) = $f;
+          echo sprintf('<option value="%s">%s</option>', $path, Format::htmlchars($label));
       }
 ?>
       </select>
-- 
GitLab