From bd8eeb550fb33f61578bcb10b43a79b835e6aaec Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Fri, 1 May 2015 21:47:34 -0500
Subject: [PATCH] variables: Implement %{message.files}

This allows for specifying that attachments from something like a new
message should be brokered with autoresponses and alerts.
---
 include/class.mailer.php   | 11 ++++++
 include/class.thread.php   | 22 +++--------
 include/class.variable.php | 79 +++++++++++++++++++++++++++++++++-----
 3 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/include/class.mailer.php b/include/class.mailer.php
index c398df9f5..22d8eaf1a 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -386,6 +386,17 @@ class Mailer {
         }
         $mime = new Mail_mime($eol);
 
+        // Add in extra attachments, if any from template variables
+        if ($message instanceof TextWithExtras
+            && ($files = $message->getFiles())
+        ) {
+            foreach ($files as $F) {
+                $file = $F->getFile();
+                $mime->addAttachment($file->getData(),
+                    $file->getType(), $file->getName(), false);
+            }
+        }
+
         // If the message is not explicitly declared to be a text message,
         // then assume that it needs html processing to create a valid text
         // body
diff --git a/include/class.thread.php b/include/class.thread.php
index b5d8bdbd4..9a2dde5b9 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -793,17 +793,6 @@ implements TemplateVariable {
         return $str;
     }
 
-    /* Returns file names with id as key */
-    function getFiles() {
-
-        $files = array();
-        foreach($this->attachments as $attachment)
-            $files[$attachment->file_id] = $attachment->file->name;
-
-        return $files;
-    }
-
-
     /* save email info
      * TODO: Refactor it to include outgoing emails on responses.
      */
@@ -851,17 +840,16 @@ implements TemplateVariable {
     }
 
     function getVar($tag) {
-        global $cfg;
-
-        if($tag && is_callable(array($this, 'get'.ucfirst($tag))))
+        if ($tag && is_callable(array($this, 'get'.ucfirst($tag))))
             return call_user_func(array($this, 'get'.ucfirst($tag)));
 
         switch(strtolower($tag)) {
             case 'create_date':
-                // XXX: Consider preferences of receiving user
-                return Format::datetime($this->getCreateDate(), true, 'UTC');
+                return new FormattedDate($this->getCreateDate());
             case 'update_date':
-                return Format::datetime($this->getUpdateDate(), true, 'UTC');
+                return new FormattedDate($this->getUpdateDate());
+            case 'files':
+                throw new OOBContent(OOBContent::FILES, $this->attachments->all());
         }
 
         return false;
diff --git a/include/class.variable.php b/include/class.variable.php
index e02680086..a7fc036d1 100644
--- a/include/class.variable.php
+++ b/include/class.variable.php
@@ -21,8 +21,9 @@ class VariableReplacer {
     var $start_delim;
     var $end_delim;
 
-    var $objects;
-    var $variables;
+    var $objects = array();
+    var $variables = array();
+    var $extras = array();
 
     var $errors;
 
@@ -30,9 +31,6 @@ class VariableReplacer {
 
         $this->start_delim = $start_delim;
         $this->end_delim = $end_delim;
-
-        $this->objects = array();
-        $this->variables = array();
     }
 
     function setError($error) {
@@ -97,13 +95,21 @@ class VariableReplacer {
 
     function replaceVars($input) {
 
+        // Preserve existing extras
+        if ($input instanceof TextWithExtras)
+            $this->extras = $input->extras;
+
         if($input && is_array($input))
             return array_map(array($this, 'replaceVars'), $input);
 
         if(!($vars=$this->_parse($input)))
             return $input;
 
-        return str_replace(array_keys($vars), array_values($vars), $input);
+        $text = str_replace(array_keys($vars), array_values($vars), $input);
+        if ($this->extras) {
+            return new TextWithExtras($text, $this->extras);
+        }
+        return $text;
     }
 
     function _resolveVar($var) {
@@ -113,9 +119,18 @@ class VariableReplacer {
             return $this->variables[$var];
 
         $parts = explode('.', $var, 2);
-        if($parts && ($obj=$this->getObj($parts[0])))
-            return $this->getVar($obj, $parts[1]);
-        elseif($parts[0] && @isset($this->variables[$parts[0]])) { //root override
+        try {
+            if ($parts && ($obj=$this->getObj($parts[0])))
+                return $this->getVar($obj, $parts[1]);
+        }
+        catch (OOBContent $content) {
+            $type = $content->getType();
+            $existing = @$this->extras[$type] ?: array();
+            $this->extras[$type] = array_merge($existing, $content->getContent());
+            return '';
+        }
+
+        if ($parts[0] && @isset($this->variables[$parts[0]])) { //root override
             if (is_array($this->variables[$parts[0]])
                     && isset($this->variables[$parts[0]][$parts[1]]))
                 return $this->variables[$parts[0]][$parts[1]];
@@ -299,6 +314,52 @@ class PlaceholderList
     }
 }
 
+/**
+ * Exception used in the variable replacement process to indicate non text
+ * content (such as attachments)
+ */
+class OOBContent extends Exception {
+    var $type;
+    var $content;
+
+    const FILES = 'files';
+
+    function __construct($type, $content) {
+        $this->type = $type;
+        $this->content = $content;
+    }
+
+    function getType() { return $this->type; }
+    function getContent() { return $this->content; }
+}
+
+class TextWithExtras {
+    var $text = '';
+    var $extras;
+
+    function __construct($text, array $extras) {
+        $this->setText($text);
+        $this->extras = $extras;
+    }
+
+    function setText($text) {
+        try {
+            $this->text = (string) $text;
+        }
+        catch (Exception $e) {
+            throw new InvalidArgumentException('String type is required', 0, $e);
+        }
+    }
+
+    function __toString() {
+        return $this->text;
+    }
+
+    function getFiles() {
+        return $this->extras[OOBContent::FILES];
+    }
+}
+
 interface TemplateVariable {
     // function asVar(); — not absolutely required
     // function getVar($name); — not absolutely required
-- 
GitLab