From d2bdc312b4d06ad86cdc43df522f16f43e5f4b30 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Tue, 4 Aug 2015 11:16:24 -0500
Subject: [PATCH] thread: Add flags for COLLABORATOR and BALANCED

And two flags, one which signals that stored HTML is correctly balanced and
does not need to be balanced when rendered in the ticket view. The second is
used to signal messages received from collaborators.

Additionally, this patch fixes showing external inline images (via the [Show
Images] button) when clicked.
---
 include/class.format.php                      |  8 +++++---
 include/class.thread.php                      | 19 +++++++++++++++----
 include/staff/templates/thread-entry.tmpl.php |  7 +++++--
 scp/css/scp.css                               |  3 +++
 scp/js/thread.js                              |  2 +-
 5 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/include/class.format.php b/include/class.format.php
index b8cfe03be..70b06a8a3 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -352,7 +352,7 @@ class Format {
     }
 
     //Format text for display..
-    function display($text, $inline_images=true) {
+    function display($text, $inline_images=true, $balance=true) {
         // Make showing offsite images optional
         $text = preg_replace_callback('/<img ([^>]*)(src="http[^"]+")([^>]*)\/>/',
             function($match) {
@@ -363,8 +363,10 @@ class Format {
             },
             $text);
 
-        //make urls clickable.
-        $text = self::html_balance($text, false);
+        if ($balance)
+            $text = self::html_balance($text, false);
+
+        // make urls clickable.
         $text = Format::clickableurls($text);
 
         if ($inline_images)
diff --git a/include/class.thread.php b/include/class.thread.php
index c3d58f3b9..5b412f235 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -378,6 +378,7 @@ class Thread extends VerySimpleModel {
         ) {
             $vars['userId'] = $mailinfo['userId'] ?: $C->getUserId();
             $vars['message'] = $body;
+            $vars['flags'] = ThreadEntry::FLAG_COLLABORATOR;
 
             if ($object instanceof Threadable)
                 return $object->postThreadEntry('M', $vars);
@@ -611,6 +612,9 @@ implements TemplateVariable {
     const FLAG_GUARDED                  = 0x0008;   // No replace on edit
     const FLAG_RESENT                   = 0x0010;
 
+    const FLAG_COLLABORATOR             = 0x0020;   // Message from collaborator
+    const FLAG_BALANCED                 = 0x0040;   // HTML does not need to be balanced on ::display()
+
     const PERM_EDIT     = 'thread.edit';
 
     var $_headers;
@@ -665,7 +669,9 @@ implements TemplateVariable {
     }
 
     function getBody() {
-        return ThreadEntryBody::fromFormattedText($this->body, $this->format);
+        return ThreadEntryBody::fromFormattedText($this->body, $this->format,
+            array('balanced' => $this->hasFlag(self::FLAG_BALANCED))
+        );
     }
 
     function setBody($body) {
@@ -1354,8 +1360,13 @@ implements TemplateVariable {
             'user_id' => $vars['userId'],
             'poster' => $poster,
             'source' => $vars['source'],
+            'flags' => $vars['flags'] ?: 0,
         ));
 
+        if ($entry->format == 'html')
+            // The current codebase properly balances html
+            $entry->flags |= self::FLAG_BALANCED;
+
         if (!isset($vars['attachments']) || !$vars['attachments'])
             // Otherwise, body will be configured in a block below (after
             // inline attachments are saved and updated in the database)
@@ -2113,12 +2124,12 @@ class ThreadEntryBody /* extends SplString */ {
         return Format::searchable($this->body);
     }
 
-    static function fromFormattedText($text, $format=false) {
+    static function fromFormattedText($text, $format=false, $options=array()) {
         switch ($format) {
         case 'text':
             return new TextThreadEntryBody($text);
         case 'html':
-            return new HtmlThreadEntryBody($text, array('strip-embedded'=>false));
+            return new HtmlThreadEntryBody($text, array('strip-embedded'=>false) + $options);
         default:
             return new ThreadEntryBody($text);
         }
@@ -2207,7 +2218,7 @@ class HtmlThreadEntryBody extends ThreadEntryBody {
         case 'pdf':
             return Format::clickableurls($this->body);
         default:
-            return Format::display($this->body);
+            return Format::display($this->body, true, !$this->options['balanced']);
         }
     }
 }
diff --git a/include/staff/templates/thread-entry.tmpl.php b/include/staff/templates/thread-entry.tmpl.php
index 5b9c3b132..3f3bfee7c 100644
--- a/include/staff/templates/thread-entry.tmpl.php
+++ b/include/staff/templates/thread-entry.tmpl.php
@@ -41,9 +41,12 @@ if ($user)
             echo sprintf(__('Edited on %s by %s'), Format::datetime($entry->updated),
                 ($editor = $entry->getEditor()) ? $editor->getName() : '');
                 ?>"><?php echo __('Edited'); ?></span>
-<?php   } ?>
-<?php   if ($entry->flags & ThreadEntry::FLAG_RESENT) { ?>
+<?php   }
+        if ($entry->flags & ThreadEntry::FLAG_RESENT) { ?>
             <span class="label label-bare"><?php echo __('Resent'); ?></span>
+<?php   }
+        if ($entry->flags & ThreadEntry::FLAG_COLLABORATOR) { ?>
+            <span class="label label-bare"><?php echo __('Collaborator'); ?></span>
 <?php   } ?>
         </span>
         </div>
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 9014081c9..12389fb26 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -971,6 +971,9 @@ img.avatar {
     display: inline-block;
     margin-left: 15px;
 }
+.thread-entry .header .button {
+    margin-top: -4px;
+}
 
 .thread-entry .thread-body {
     border: 1px solid #ddd;
diff --git a/scp/js/thread.js b/scp/js/thread.js
index 434c8d05d..c59032316 100644
--- a/scp/js/thread.js
+++ b/scp/js/thread.js
@@ -65,7 +65,7 @@ var thread = {
               .text(' ' + __('Show Images'))
               .click(function(ev) {
                 imgs.each(function(i, img) {
-                  this.showExternalImage(img);
+                  thread.showExternalImage(img);
                   $(img).removeClass('non-local-image')
                     // Remove placeholder sizing
                     .css({'display':'inline-block'})
-- 
GitLab