From 4a7e8c5458b56c167321e19459523be9787640c4 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Mon, 11 May 2015 21:18:02 -0500
Subject: [PATCH] events: Add auto notes for ticket updates

---
 include/class.dynamic_forms.php | 48 +++++++++++++++++++++++++++++++++
 include/class.forms.php         | 44 ++++++++++++++++++++++++++++++
 include/class.thread.php        | 22 +++++++++++++++
 include/class.ticket.php        | 11 ++++----
 4 files changed, 120 insertions(+), 5 deletions(-)

diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 43ae645c7..65777740f 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -1168,6 +1168,22 @@ class DynamicFormEntry extends VerySimpleModel {
         return $this->getForm()->render($staff, $title, $options);
     }
 
+    function getChanges() {
+        $fields = array();
+        foreach ($this->getAnswers() as $a) {
+            $field = $a->getField();
+            if (!$field->hasData() || $field->isPresentationOnly())
+                continue;
+            $val = $v = $field->to_database($field->getClean());
+            if (is_array($val))
+                $v = $val[0];
+            if ($a->value == $v)
+                continue;
+            $fields[$field->get('id')] = array($a->value, $val);
+        }
+        return $fields;
+    }
+
     /**
      * addMissingFields
      *
@@ -1489,6 +1505,38 @@ class SelectionField extends FormField {
         return $value;
     }
 
+    // PHP 5.4 Move this to a trait
+    function whatChanged($before, $after) {
+        $before = (array) $before;
+        $after = (array) $after;
+        $added = array_diff($after, $before);
+        $deleted = array_diff($before, $after);
+        $added = array_map(array($this, 'display'), $added);
+        $deleted = array_map(array($this, 'display'), $deleted);
+
+        if ($added && $deleted) {
+            $desc = sprintf(
+                __('added <strong>%1$s</strong> and removed <strong>%2$s</strong>'),
+                implode(', ', $added), implode(', ', $deleted));
+        }
+        elseif ($added) {
+            $desc = sprintf(
+                __('added <strong>%1$s</strong>'),
+                implode(', ', $added));
+        }
+        elseif ($deleted) {
+            $desc = sprintf(
+                __('removed <strong>%1$s</strong>'),
+                implode(', ', $deleted));
+        }
+        else {
+            $desc = sprintf(
+                __('changed to <strong>%1$s</strong>'),
+                $this->display($after));
+        }
+        return $desc;
+    }
+
     function asVar($value, $id=false) {
         $values = $this->to_php($value, $id);
         if (is_array($values)) {
diff --git a/include/class.forms.php b/include/class.forms.php
index 0b6da8f27..97b52d7cf 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -626,6 +626,19 @@ class FormField {
         return false;
     }
 
+    /**
+     * Describe the difference between the to two values. Note that the
+     * values should be passed through ::parse() or to_php() before
+     * utilizing this method.
+     */
+    function whatChanged($before, $after) {
+        if ($before)
+            $desc = __('changed from <strong>%2$s</strong> to <strong>%1$s</strong>');
+        else
+            $desc = __('set to <strong>%1$s</strong>');
+        return sprintf($desc, $this->display($after), $this->display($before));
+    }
+
     /**
      * Convert the field data to something matchable by filtering. The
      * primary use of this is for ticket filtering.
@@ -1312,6 +1325,37 @@ class ChoiceField extends FormField {
         return (string) $value;
     }
 
+    function whatChanged($before, $after) {
+        $before = (array) $before;
+        $after = (array) $after;
+        $added = array_diff($after, $before);
+        $deleted = array_diff($before, $after);
+        $added = array_map(array($this, 'display'), $added);
+        $deleted = array_map(array($this, 'display'), $deleted);
+
+        if ($added && $deleted) {
+            $desc = sprintf(
+                __('added <strong>%1$s</strong> and removed <strong>%2$s</strong>'),
+                implode(', ', $added), implode(', ', $deleted));
+        }
+        elseif ($added) {
+            $desc = sprintf(
+                __('added <strong>%1$s</strong>'),
+                implode(', ', $added));
+        }
+        elseif ($deleted) {
+            $desc = sprintf(
+                __('removed <strong>%1$s</strong>'),
+                implode(', ', $deleted));
+        }
+        else {
+            $desc = sprintf(
+                __('changed to <strong>%1$s</strong>'),
+                $this->display($after));
+        }
+        return $desc;
+    }
+
     /*
      Return criteria to which the choice should be filtered by
      */
diff --git a/include/class.thread.php b/include/class.thread.php
index 0167e2aac..38eb59fca 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -1497,6 +1497,8 @@ class ThreadEvent extends VerySimpleModel {
             'collab'    => 'group',
             'created'   => 'magic',
             'overdue'   => 'time',
+            'transferred' => 'share-alt',
+            'edited'    => 'pencil',
         );
         return @$icons[$this->state] ?: 'chevron-sign-right';
     }
@@ -1535,6 +1537,26 @@ class ThreadEvent extends VerySimpleModel {
             'edited:status' => __('<b>{username}</b> changed the status to <strong>{<TicketStatus>data.status}</strong> {timestamp}'),
             'overdue' => __('Flagged as overdue by the system {timestamp}'),
             'transferred' => __('<b>{username}</b> transferred this to <strong>{dept}</strong> {timestamp}'),
+            'edited:fields' => function($evt) {
+                $base = __('Updated by <b>{username}</b> {timestamp} — %s');
+                $data = $evt->getData();
+                $fields = $changes = array();
+                foreach (DynamicFormField::objects()->filter(array(
+                    'id__in' => array_keys($data['fields'])
+                )) as $F) {
+                    $fields[$F->id] = $F;
+                }
+                foreach ($data['fields'] as $id=>$f) {
+                    $field = $fields[$id];
+                    list($old, $new) = $f;
+                    $impl = $field->getImpl($field);
+                    $before = $impl->to_php($old);
+                    $after = $impl->to_php($new);
+                    $changes[] = sprintf('<strong>%s</strong> %s',
+                        $field->getLocal('label'), $impl->whatChanged($before, $after));
+                }
+                return sprintf($base, implode(', ', $changes));
+            },
         );
         $self = $this;
         $data = $this->getData();
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 0f9ed9386..13626636f 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -2578,7 +2578,6 @@ implements RestrictedAccess, Threadable, TemplateVariable {
         $fields['slaId']    = array('type'=>'int',      'required'=>0, 'error'=>__('Select a valid SLA'));
         $fields['duedate']  = array('type'=>'date',     'required'=>0, 'error'=>__('Invalid date format - must be MM/DD/YY'));
 
-        $fields['note']     = array('type'=>'text',     'required'=>1, 'error'=>__('A reason for the update is required'));
         $fields['user_id']  = array('type'=>'int',      'required'=>0, 'error'=>__('Invalid user-id'));
 
         if(!Validator::process($fields, $vars, $errors) && !$errors['err'])
@@ -2629,16 +2628,16 @@ implements RestrictedAccess, Threadable, TemplateVariable {
         if(!db_query($sql) || !db_affected_rows())
             return false;
 
-        if(!$vars['note'])
-            $vars['note']=sprintf(_S('Ticket details updated by %s'), $thisstaff->getName());
-
-        $this->logNote(_S('Ticket Updated'), $vars['note'], $thisstaff);
+        if ($vars['note'])
+            $this->logNote(_S('Ticket Updated'), $vars['note'], $thisstaff);
 
         // Decide if we need to keep the just selected SLA
         $keepSLA = ($this->getSLAId() != $vars['slaId']);
 
         // Update dynamic meta-data
+        $changes = array();
         foreach ($forms as $f) {
+            $changes += $f->getChanges();
             // Drop deleted forms
             $idx = array_search($f->getId(), $vars['forms']);
             if ($idx === false) {
@@ -2650,6 +2649,8 @@ implements RestrictedAccess, Threadable, TemplateVariable {
             }
         }
 
+        $this->logEvent('edited', array('fields' => $changes));
+
         // Reload the ticket so we can do further checking
         $this->reload();
 
-- 
GitLab