From 9436536d33cd6df191c5096130411fefd05101df Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Sat, 31 Dec 2016 17:00:59 -0600
Subject: [PATCH] queue: Add conditions to whole row in queue

This allows a queue to define conditions which apply to the entire row, so
that all the columns in a row can react to a particular condition. For
instance, rows in the ticket queue for tickets due in the near future or
assigned to a particular team could be formatted a certain way.
---
 include/ajax.search.php                       |  11 +-
 include/class.queue.php                       | 143 ++++++++++++------
 include/staff/queue.inc.php                   |  60 +++++++-
 .../templates/queue-column-condition.tmpl.php |   6 +-
 include/staff/templates/queue-column.tmpl.php |   5 +-
 5 files changed, 168 insertions(+), 57 deletions(-)

diff --git a/include/ajax.search.php b/include/ajax.search.php
index 8d596e4fe..ccd861d1b 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -291,8 +291,13 @@ class SearchAjaxAPI extends AjaxController {
         if (!$thisstaff) {
             Http::response(403, 'Agent login is required');
         }
-        elseif (!isset($_GET['field']) || !isset($_GET['id']) || !isset($_GET['colid'])) {
-            Http::response(400, '`field`, `id`, and `colid` parameters required');
+        elseif (!isset($_GET['field']) || !isset($_GET['id'])
+            || !isset($_GET['object_id'])
+        ) {
+            Http::response(400, '`field`, `id`, and `object_id` parameters required');
+        }
+        elseif (!is_numeric($_GET['object_id'])) {
+            Http::response(400, '`object_id` should be an integer');
         }
         $fields = SavedSearch::getSearchableFields('Ticket');
         if (!isset($fields[$_GET['field']])) {
@@ -304,7 +309,7 @@ class SearchAjaxAPI extends AjaxController {
         // Ensure `name` is preserved
         $field_name = $_GET['field'];
         $id = $_GET['id'];
-        $column = new QueueColumn(array('id' => $_GET['colid']));
+        $object_id = $_GET['object_id'];
         $condition = new QueueColumnCondition();
         include STAFFINC_DIR . 'templates/queue-column-condition.tmpl.php';
     }
diff --git a/include/class.queue.php b/include/class.queue.php
index f0994e7b7..e63dbf9e7 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -63,6 +63,7 @@ class CustomQueue extends VerySimpleModel {
     const FLAG_INHERIT_EVERYTHING = 0x78; // Maskf or all INHERIT flags
 
     var $criteria;
+    var $_conditions;
 
     static function queues() {
         return parent::objects()->filter(array(
@@ -108,13 +109,19 @@ class CustomQueue extends VerySimpleModel {
             $this->criteria = is_string($this->config)
                 ? JsonDataParser::decode($this->config)
                 : $this->config;
-            // Auto-upgrade criteria to new format
-            if ($old) {
+            // Auto-upgrade v1.10 saved-search criteria to new format
+            // But support new style with `conditions` support
+            if ($old && is_array($this->criteria)
+                && !isset($this->criteria['conditions'])
+            ) {
                 // TODO: Upgrade old ORM path names
                 $this->criteria = $this->isolateCriteria($this->criteria);
             }
         }
         $criteria = $this->criteria ?: array();
+        // Support new style with `conditions` support
+        if (isset($criteria['criteria']))
+            $criteria = $criteria['criteria'];
         if ($include_parent && $this->parent_id && $this->parent) {
             $criteria = array_merge($this->parent->getCriteria(true),
                 $criteria);
@@ -479,6 +486,22 @@ class CustomQueue extends VerySimpleModel {
         return $items;
     }
 
+    function getConditions() {
+        if (!isset($this->_conditions)) {
+            $this->getCriteria();
+            $conds = array();
+            if (is_array($this->criteria)
+                && isset($this->criteria['conditions'])
+            ) {
+                $conds = $this->criteria['conditions'];
+            }
+            foreach ($conds as $C)
+                if ($T = QueueColumnCondition::fromJson($C))
+                    $this->_conditions[] = $T;
+        }
+        return $this->_conditions;
+    }
+
     function getColumns($use_template=false) {
         if ($this->columns_id
             && ($q = CustomQueue::lookup($this->columns_id))
@@ -920,15 +943,23 @@ class CustomQueue extends VerySimpleModel {
             }
         }
 
+        list($this->_conditions, $conditions)
+            = QueueColumn::getConditionsFromPost($vars, $this->id, $this->getRoot());
+
         // TODO: Move this to SavedSearch::update() and adjust
         //       AjaxSearch::_saveSearch()
         $form = $form ?: $this->getForm($vars);
-        if (!$vars || !$form->isValid()) {
+        if (!$vars) {
+            $errors['criteria'] = __('No criteria specified');
+        }
+        elseif (!$form->isValid()) {
             $errors['criteria'] = __('Validation errors exist on criteria');
         }
         else {
-            $this->config = JsonDataEncoder::encode(
-                $this->isolateCriteria($form->getClean()));
+            $this->config = JsonDataEncoder::encode([
+                'criteria' => $this->isolateCriteria($form->getClean()),
+                'conditions' => $conditions,
+            ]);
         }
 
         return 0 === count($errors);
@@ -1436,8 +1467,7 @@ class QueueColumnCondition {
     }
 
     function render($row, $text, &$styles=array()) {
-        $annotation = $this->getAnnotationName();
-        if ($V = $row[$annotation]) {
+        if ($V = $row[$this->getAnnotationName()]) {
             foreach ($this->getProperties() as $css=>$value) {
                 $field = QueueColumnConditionProperty::getField($css);
                 $field->value = $value;
@@ -1817,7 +1847,7 @@ extends VerySimpleModel {
         return $this->_annotations;
     }
 
-    function getConditions() {
+    function getConditions($include_queue=true) {
         if (!isset($this->_conditions)) {
             $this->_conditions = array();
             if ($this->conditions
@@ -1827,6 +1857,12 @@ extends VerySimpleModel {
                     if ($T = QueueColumnCondition::fromJson($C))
                         $this->_conditions[] = $T;
             }
+            // Support row-spanning conditions
+            if ($include_queue && ($q = $this->getQueue())
+                && ($q_conds = $q->getConditions())
+            ) {
+                $this->_conditions = array_merge($this->_conditions, $q_conds);
+            }
         }
         return $this->_conditions;
     }
@@ -1863,52 +1899,63 @@ extends VerySimpleModel {
         // Do the conditions
         $this->_conditions = $conditions = array();
         if (isset($vars['conditions'])) {
-            foreach (@$vars['conditions'] as $i=>$id) {
-                if ($vars['condition_column'][$i] != $this->id)
-                    // Not a condition for this column
-                    continue;
-                // Determine the criteria
-                $name = $vars['condition_field'][$i];
-                $fields = CustomQueue::getSearchableFields($root);
-                if (!isset($fields[$name]))
-                    // No such field exists for this queue root type
-                    continue;
-                $parts = CustomQueue::getSearchField($fields[$name], $name);
-                $search_form = new SimpleForm($parts, $vars, array('id' => $id));
-                $search_form->getField("{$name}+search")->value = true;
-                $crit = $search_form->getClean();
-                // Check the box to enable searching on the field
-                $crit["{$name}+search"] = true;
-
-                // Isolate only the critical parts of the criteria
-                $crit = QueueColumnCondition::isolateCriteria($crit);
-
-                // Determine the properties
-                $props = array();
-                foreach ($vars['properties'] as $i=>$cid) {
-                    if ($cid != $id)
-                        // Not a property for this condition
-                        continue;
-
-                    // Determine the property configuration
-                    $prop = $vars['property_name'][$i];
-                    if (!($F = QueueColumnConditionProperty::getField($prop))) {
-                        // Not a valid property
-                        continue;
-                    }
-                    $prop_form = new SimpleForm(array($F), $vars, array('id' => $cid));
-                    $props[$prop] = $prop_form->getField($prop)->getClean();
-                }
-                $json = array('crit' => $crit, 'prop' => $props);
-                $this->_conditions[] = QueueColumnCondition::fromJson($json);
-                $conditions[] = $json;
-            }
+            list($this->_conditions, $conditions)
+                = self::getConditionsFromPost($vars, $this->id, $root);
         }
 
         // Store as JSON array
         $this->annotations = JsonDataEncoder::encode($annotations);
         $this->conditions = JsonDataEncoder::encode($conditions);
     }
+
+    static function getConditionsFromPost(array $vars, $myid, $root='Ticket') {
+        $condition_objects = $conditions = array();
+
+        if (!isset($vars['conditions']))
+            return array($condition_objects, $conditions);
+
+        foreach (@$vars['conditions'] as $i=>$id) {
+            if ($vars['condition_column'][$i] != $myid)
+                // Not a condition for this column
+                continue;
+            // Determine the criteria
+            $name = $vars['condition_field'][$i];
+            $fields = CustomQueue::getSearchableFields($root);
+            if (!isset($fields[$name]))
+                // No such field exists for this queue root type
+                continue;
+            $parts = CustomQueue::getSearchField($fields[$name], $name);
+            $search_form = new SimpleForm($parts, $vars, array('id' => $id));
+            $search_form->getField("{$name}+search")->value = true;
+            $crit = $search_form->getClean();
+            // Check the box to enable searching on the field
+            $crit["{$name}+search"] = true;
+
+            // Isolate only the critical parts of the criteria
+            $crit = QueueColumnCondition::isolateCriteria($crit);
+
+            // Determine the properties
+            $props = array();
+            foreach ($vars['properties'] as $i=>$cid) {
+                if ($cid != $id)
+                    // Not a property for this condition
+                    continue;
+
+                // Determine the property configuration
+                $prop = $vars['property_name'][$i];
+                if (!($F = QueueColumnConditionProperty::getField($prop))) {
+                    // Not a valid property
+                    continue;
+                }
+                $prop_form = new SimpleForm(array($F), $vars, array('id' => $cid));
+                $props[$prop] = $prop_form->getField($prop)->getClean();
+            }
+            $json = array('crit' => $crit, 'prop' => $props);
+            $condition_objects[] = QueueColumnCondition::fromJson($json);
+            $conditions[] = $json;
+        }
+        return array($condition_objects, $conditions);
+    }
 }
 
 class QueueColumnGlue
diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php
index 25ef7faa9..ea01dd558 100644
--- a/include/staff/queue.inc.php
+++ b/include/staff/queue.inc.php
@@ -45,6 +45,8 @@ else {
       <?php echo __('Columns'); ?></a></li>
     <li><a href="#sorting-tab"><i class="icon-sort-by-attributes"></i>
       <?php echo __('Sort'); ?></a></li>
+    <li><a href="#conditions-tab"><i class="icon-exclamation-sign"></i>
+      <?php echo __('Conditions'); ?></a></li>
     <li><a href="#preview-tab"><i class="icon-eye-open"></i>
       <?php echo __('Preview'); ?></a></li>
   </ul>
@@ -311,7 +313,8 @@ var Q = setInterval(function() {
       $(function() {
         $('#preview-tab').on('afterShow', function() {
           $.ajax({
-            url: 'ajax.php/queue/preview',
+            url: 'ajax.php/queue<?php
+                if (isset($queue->id)) echo "/{$queue->id}"; ?>/preview',
             type: 'POST',
             data: $('#save').serializeArray(),
             success: function(html) {
@@ -321,6 +324,61 @@ var Q = setInterval(function() {
         });
       });
     </script>
+  </div>
+
+  <div class="hidden tab_content" id="conditions-tab">
+    <div style="margin-bottom: 15px"><?php echo __("Conditions are used to change the view of the data in a row based on some conditions of the data. For instance, a column might be shown bold if some condition is met.");
+      ?> <?php echo __("These conditions apply to an entire row in the queue."); 
+    ?></div>
+    <div class="conditions">
+<?php
+if ($queue->getConditions()) {
+  $fields = CustomQueue::getSearchableFields($queue->getRoot());
+  foreach ($queue->getConditions() as $i=>$condition) {
+     $id = QueueColumnCondition::getUid();
+     list($label, $field) = $condition->getField();
+     $field_name = $condition->getFieldName();
+     $object_id = $queue->id;
+     include STAFFINC_DIR . 'templates/queue-column-condition.tmpl.php';
+  }
+} ?>
+
+    <div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #bbb">
+      <i class="icon-plus-sign"></i>
+      <select class="add-condition">
+        <option value="0">— <?php echo __("Add a condition"); ?> —</option>
+<?php
+      foreach (CustomQueue::getSearchableFields('Ticket') as $path=>$f) {
+          list($label) = $f;
+          echo sprintf('<option value="%s">%s</option>', $path, Format::htmlchars($label));
+      }
+?>
+      </select>
+      <script>
+      $(function() {
+        var queueid = <?php echo $queue->id ?: 0; ?>,
+            nextid = <?php echo 1000 + QueueColumnCondition::getUid(); ?>;
+        $('#conditions-tab select.add-condition').change(function() {
+          var $this = $(this),
+              container = $this.closest('div'),
+              selected = $this.find(':selected');
+          if (selected.val() <= 0)
+            return;
+          $.ajax({
+            url: 'ajax.php/queue/condition/add',
+            data: { field: selected.val(), object_id: queueid, id: nextid },
+            dataType: 'html',
+            success: function(html) {
+              $(html).insertBefore(container);
+              $this.find('[value=0]').select();
+              nextid++;
+            }
+          });
+        });
+      });
+      </script>
+    </div>
+  </div>
 
   </div>
 
diff --git a/include/staff/templates/queue-column-condition.tmpl.php b/include/staff/templates/queue-column-condition.tmpl.php
index bc69978c8..59421ed46 100644
--- a/include/staff/templates/queue-column-condition.tmpl.php
+++ b/include/staff/templates/queue-column-condition.tmpl.php
@@ -3,13 +3,13 @@
 //
 // $field - field for the condition (Ticket / Last Update)
 // $condition - <QueueColumnCondition> instance for this condition
-// $column - <QueueColumn> to which the condition belongs
+// $object_id - ID# of the object to which the condition belongs
 // $id - temporary ID number for the condition
 // $field_name - search path / name for the field
 ?>
 <div class="condition">
   <input name="conditions[]" value="<?php echo $id; ?>" type="hidden" />
-  <input name="condition_column[]" value="<?php echo $column->getId(); ?>"
+  <input name="condition_column[]" value="<?php echo $object_id; ?>"
     type="hidden" />
   <input name="condition_field[]" value="<?php echo $field_name; ?>" type="hidden" />
   <div class="pull-right">
@@ -17,7 +17,7 @@
       return false;
       "><i class="icon-trash"></i></a>
   </div>
-  <?php echo $label ?: $field->getLabel(); ?>
+  <div><strong><?php echo $label ?: $field->getLabel(); ?></div></strong>
   <div class="advanced-search">
 <?php
 $parts = CustomQueue::getSearchField(array($label, $field), $field_name);
diff --git a/include/staff/templates/queue-column.tmpl.php b/include/staff/templates/queue-column.tmpl.php
index 851d374cd..806825ad4 100644
--- a/include/staff/templates/queue-column.tmpl.php
+++ b/include/staff/templates/queue-column.tmpl.php
@@ -117,12 +117,13 @@ foreach (Internationalization::sortKeyedList($annotations) as $class=>$desc) {
   ?></div>
   <div class="conditions">
 <?php
-if ($column->getConditions()) {
+if ($column->getConditions(false)) {
   $fields = CustomQueue::getSearchableFields($root);
   foreach ($column->getConditions() as $i=>$condition) {
      $id = QueueColumnCondition::getUid();
      list($label, $field) = $condition->getField();
      $field_name = $condition->getFieldName();
+     $object_id = $column->getId();
      include STAFFINC_DIR . 'templates/queue-column-condition.tmpl.php';
   }
 } ?>
@@ -147,7 +148,7 @@ if ($column->getConditions()) {
               selected = $this.find(':selected');
           $.ajax({
             url: 'ajax.php/queue/condition/add',
-            data: { field: selected.val(), colid: colid, id: nextid },
+            data: { field: selected.val(), object_id: colid, id: nextid },
             dataType: 'html',
             success: function(html) {
               $(html).insertBefore(container);
-- 
GitLab