From 4742bc178c819c1c962c8d9429140217b54aafdc Mon Sep 17 00:00:00 2001
From: aydreeihn <adriane@enhancesoft.com>
Date: Tue, 19 Sep 2017 16:15:13 -0500
Subject: [PATCH] new branch with bcc/cc and security updates

---
 assets/default/css/theme.css                  |  22 ++
 include/ajax.content.php                      |   1 +
 include/ajax.thread.php                       |   6 +-
 include/class.client.php                      |  24 +-
 include/class.collaborator.php                |  84 +++-
 include/class.email.php                       |   4 +-
 include/class.mailer.php                      |  15 +-
 include/class.task.php                        |   8 +
 include/class.thread.php                      | 139 ++++++-
 include/class.thread_actions.php              |  67 +++-
 include/class.ticket.php                      | 362 ++++++++++++++----
 include/class.user.php                        |  16 +
 .../client/templates/thread-entry.tmpl.php    |  10 +-
 .../client/templates/thread-event.tmpl.php    |   2 +-
 include/client/tickets.inc.php                |   7 +-
 include/client/view.inc.php                   |  27 +-
 .../en_US/templates/email/message.alert.yaml  |   2 +-
 .../staff/templates/collaborators.tmpl.php    |  75 ++--
 include/staff/templates/task-view.tmpl.php    |   1 -
 .../thread-email-recipients.tmpl.php          |  30 ++
 include/staff/templates/thread-entry.tmpl.php |  16 +-
 include/staff/ticket-open.inc.php             | 104 ++++-
 include/staff/ticket-view.inc.php             | 190 ++++++++-
 .../streams/core/98ad7d55-b2ce8ba7.patch.sql  |  31 ++
 scp/css/scp.css                               |  25 ++
 scp/tasks.php                                 |  19 +-
 scp/tickets.php                               |  28 ++
 setup/inc/streams/core/install-mysql.sql      |   3 +-
 28 files changed, 1152 insertions(+), 166 deletions(-)
 create mode 100644 include/staff/templates/thread-email-recipients.tmpl.php
 create mode 100644 include/upgrader/streams/core/98ad7d55-b2ce8ba7.patch.sql

diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index 86e8dabb4..7e7027a21 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -1127,6 +1127,28 @@ img.avatar {
     margin-left: 1px;
 }
 
+.thread-entry.bccmessage .header {
+    background:#DDFDAC;
+}
+.thread-entry.avatar.bccmessage .header:before {
+    top: 7px;
+    left: -8px;
+    right: initial;
+    border-left: none;
+    border-right: 8px solid #CCC;
+}
+.thread-entry.avatar.bccmessage .header:before {
+    border-right-color: #CCC;
+}
+.thread-entry.avatar.bccmessage .header:after {
+    top: 7px;
+    left: -8px;
+    right: initial;
+    border-left: none;
+    border-right: 7px solid #DDFDAC;
+    margin-left: 1px;
+}
+
 .thread-entry .header .title {
     max-width: 500px;
     vertical-align: bottom;
diff --git a/include/ajax.content.php b/include/ajax.content.php
index 38112a115..48cb6f2b0 100644
--- a/include/ajax.content.php
+++ b/include/ajax.content.php
@@ -101,6 +101,7 @@ class ContentAjaxAPI extends AjaxController {
                     <tr><td>.lastmessage</td><td>'.__('Last Message').'</td></tr>
                     <tr><td colspan="2" style="padding:5px 0 5px 0;"><em><b>'.__('Thread Entry expansions').'</b></em></td></tr>
                     <tr><td>.poster</td><td>'.__('Poster').'</td></tr>
+                    <tr><td>.posterType</td><td>'.__('Can be User or Agent').'</td></tr>
                     <tr><td>.create_date</td><td>'.__('Date Created').'</td></tr>
                 </table>
             </td>
diff --git a/include/ajax.thread.php b/include/ajax.thread.php
index 0523d851b..e1e8a2663 100644
--- a/include/ajax.thread.php
+++ b/include/ajax.thread.php
@@ -94,7 +94,6 @@ class ThreadAjaxAPI extends AjaxController {
                 || !$object->checkStaffPerm($thisstaff))
             Http::response(404, __('No such thread'));
 
-
         $user = $uid? User::lookup($uid) : null;
 
         //If not a post then assume new collaborator form
@@ -116,6 +115,8 @@ class ThreadAjaxAPI extends AjaxController {
                             array('isactive'=>1), $errors))) {
                 $info = array('msg' => sprintf(__('%s added as a collaborator'),
                             Format::htmlchars($c->getName())));
+                $c->setCc();
+                $c->save();
                 return self::_collaborators($thread, $info);
             }
         }
@@ -232,8 +233,7 @@ class ThreadAjaxAPI extends AjaxController {
         if ($thread->updateCollaborators($_POST, $errors))
             Http::response(201, $this->json_encode(array(
                             'id' => $thread->getId(),
-                            'text' => sprintf('Recipients (%d of %d)',
-                                $thread->getNumActiveCollaborators(),
+                            'text' => sprintf('(%d)',
                                 $thread->getNumCollaborators())
                             )
                         ));
diff --git a/include/class.client.php b/include/class.client.php
index 53b6376c9..36fa13746 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -59,13 +59,23 @@ implements EmailContact, ITicketUser, TemplateVariable {
         case 'ticket_link':
             $qstr = array();
             if ($cfg && $cfg->isAuthTokenEnabled()
-                    && ($ticket=$this->getTicket()))
-                $qstr['auth'] = $ticket->getAuthToken($this);
+                    && ($ticket=$this->getTicket())
+                    && !$ticket->getThread()->getNumCollaborators()) {
+                      $qstr['auth'] = $ticket->getAuthToken($this);
+                      return sprintf('%s/view.php?%s',
+                              $cfg->getBaseUrl(),
+                              Http::build_query($qstr, false)
+                              );
+                    }
+                    else {
+                      return sprintf('%s/tickets.php?id=%s',
+                              $cfg->getBaseUrl(),
+                              $ticket->getId()
+                              );
+                    }
+
+
 
-            return sprintf('%s/view.php?%s',
-                    $cfg->getBaseUrl(),
-                    Http::build_query($qstr, false)
-                    );
             break;
         }
     }
@@ -162,7 +172,7 @@ class TicketOwner extends  TicketUser {
  *
  */
 
-class  EndUser extends BaseAuthenticatedUser {
+class EndUser extends BaseAuthenticatedUser {
 
     protected $user;
     protected $_account = false;
diff --git a/include/class.collaborator.php b/include/class.collaborator.php
index 0de95b6e4..4bd0e7cf8 100644
--- a/include/class.collaborator.php
+++ b/include/class.collaborator.php
@@ -34,6 +34,9 @@ implements EmailContact, ITicketUser {
         ),
     );
 
+    const FLAG_ACTIVE = 0x0001;
+    const FLAG_CC = 0x0002;
+
     function __toString() {
         return Format::htmlchars($this->toString());
     }
@@ -46,7 +49,7 @@ implements EmailContact, ITicketUser {
     }
 
     function isActive() {
-        return $this->isactive;
+        return !!($this->flags & self::FLAG_ACTIVE);
     }
 
     function getCreateDate() {
@@ -80,21 +83,36 @@ implements EmailContact, ITicketUser {
         return $this->user->getName();
     }
 
+    static function getIdByUserId($userId, $threadId) {
+        $row = Collaborator::objects()
+            ->filter(array('user_id'=>$userId, 'thread_id'=>$threadId))
+            ->values_flat('id')
+            ->first();
+
+        return $row ? $row[0] : 0;
+    }
+
     // VariableReplacer interface
     function getVar($what) {
         global $cfg;
 
         switch (strtolower($what)) {
         case 'ticket_link':
-            return sprintf('%s/view.php?%s',
-                $cfg->getBaseUrl(),
-                Http::build_query(
-                    // TODO: Chance to $this->getTicket when
-                    array('auth' => $this->getTicket()->getAuthToken($this)),
-                    false
-                )
-            );
-            break;
+            if ($this->getTicket()->getAuthToken($this)
+                && ($ticket=$this->getTicket())
+                && !$ticket->getThread()->getNumCollaborators()) {
+                  $qstr['auth'] = $ticket->getAuthToken($this);
+                  return sprintf('%s/view.php?%s',
+                          $cfg->getBaseUrl(),
+                          Http::build_query($qstr, false)
+                          );
+                }
+                else {
+                  return sprintf('%s/tickets.php?id=%s',
+                          $cfg->getBaseUrl(),
+                          $ticket->getId()
+                          );
+                }
         }
     }
 
@@ -114,6 +132,45 @@ implements EmailContact, ITicketUser {
         return $this->user_id;
     }
 
+    function hasFlag($flag) {
+        return ($this->get('flags', 0) & $flag) != 0;
+    }
+
+    public function setFlag($flag, $val) {
+        if ($val)
+            $this->flags |= $flag;
+        else
+            $this->flags &= ~$flag;
+    }
+
+    public function setCc() {
+      $this->setFlag(Collaborator::FLAG_ACTIVE, true);
+      $this->setFlag(Collaborator::FLAG_CC, true);
+      $this->save();
+    }
+
+    public function setBcc() {
+      $this->setFlag(Collaborator::FLAG_ACTIVE, true);
+      $this->setFlag(Collaborator::FLAG_CC, false);
+      $this->save();
+    }
+
+    function isCc() {
+        return !!($this->flags & self::FLAG_CC);
+    }
+
+    function getCollabList($collabs) {
+      $collabList = array();
+      foreach ($collabs as $c) {
+        $u = User::lookup($c);
+        if ($u) {
+          $email = $u->getEmail()->address;
+          $collabList[$c] = $email;
+        }
+      }
+      return $collabList;
+    }
+
     static function create($vars=false) {
         $inst = new static($vars);
         $inst->created = SqlFunction::NOW();
@@ -127,15 +184,14 @@ implements EmailContact, ITicketUser {
     }
 
     static function add($info, &$errors) {
-
         if (!$info || !$info['threadId'] || !$info['userId'])
             $errors['err'] = __('Invalid or missing information');
-        elseif ($c = static::lookup(array(
+        elseif ($c = Collaborator::lookup(array(
             'thread_id' => $info['threadId'],
             'user_id' => $info['userId'],
         )))
-            $errors['err'] = sprintf(__('%s is already a collaborator'),
-                    $c->getName());
+          $errors['err'] = sprintf(__('%s is already a collaborator'),
+                      $c->getName());
 
         if ($errors) return false;
 
diff --git a/include/class.email.php b/include/class.email.php
index c1542113e..a91bebde5 100644
--- a/include/class.email.php
+++ b/include/class.email.php
@@ -169,13 +169,13 @@ class Email extends VerySimpleModel {
         return $info;
     }
 
-    function send($to, $subject, $message, $attachments=null, $options=null) {
+    function send($to, $subject, $message, $attachments=null, $options=null, $cc=array()) {
 
         $mailer = new Mailer($this);
         if($attachments)
             $mailer->addAttachments($attachments);
 
-        return $mailer->send($to, $subject, $message, $options);
+        return $mailer->send($to, $subject, $message, $options, $cc);
     }
 
     function sendAutoReply($to, $subject, $message, $attachments=null, $options=array()) {
diff --git a/include/class.mailer.php b/include/class.mailer.php
index a97cf650f..ed1ef55df 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -294,7 +294,7 @@ class Mailer {
             0, 6);
     }
 
-    function send($recipient, $subject, $message, $options=null) {
+    function send($recipient, $subject, $message, $options=null, $collabs=array()) {
         global $ost, $cfg;
 
         //Get the goodies
@@ -508,6 +508,19 @@ class Mailer {
             }
         }
 
+        $cc = array();
+        if($collabs) {
+          if($collabs['cc']) {
+            foreach ($collabs['cc'] as $email) {
+              $mime->addCc($email);
+              $email = preg_replace("/(\r\n|\r|\n)/s",'', trim($email));
+              $cc[] = $email;
+            }
+            $to = $to.', '.implode(', ',$cc);
+          }
+        }
+        $to = ltrim($to, ', ');
+
         //Desired encodings...
         $encodings=array(
                 'head_encoding' => 'quoted-printable',
diff --git a/include/class.task.php b/include/class.task.php
index 4b98ff1d4..7cb0aab79 100644
--- a/include/class.task.php
+++ b/include/class.task.php
@@ -1150,6 +1150,14 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
 
     }
 
+    function addCollaborator($user, $vars, &$errors, $event=true) {
+        if ($c = $this->getThread()->addCollaborator($user, $vars, $errors, $event)) {
+            $this->collaborators = null;
+            $this->recipients = null;
+        }
+        return $c;
+    }
+
     /*
      * Notify collaborators on response or new message
      *
diff --git a/include/class.thread.php b/include/class.thread.php
index af92d3a5f..f101aef0d 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -118,7 +118,13 @@ implements Searchable {
     }
 
     function getActiveCollaborators() {
-        return $this->getCollaborators(array('isactive'=>1));
+        $collaborators = $this->getCollaborators();
+        $active = array();
+        foreach ($collaborators as $c) {
+          if ($c->isactive())
+            $active[] = $c;
+        }
+        return $active;
     }
 
     function getCollaborators($criteria=array()) {
@@ -130,7 +136,8 @@ implements Searchable {
             ->filter(array('thread_id' => $this->getId()));
 
         if (isset($criteria['isactive']))
-            $collaborators->filter(array('isactive' => $criteria['isactive']));
+          $collaborators->filter(array('flags__hasbit'=>Collaborator::FLAG_ACTIVE));
+
 
         // TODO: sort by name of the user
         $collaborators->order_by('user__name');
@@ -177,13 +184,14 @@ implements Searchable {
             $collabs = array();
             foreach ($ids as $k => $cid) {
                 if (($c=Collaborator::lookup($cid))
-                        && $c->getThreadId() == $this->getId()
+                        && ($c->getThreadId() == $this->getId())
                         && $c->delete())
                      $collabs[] = $c;
+
+                 $this->getEvents()->log($this->getObject(), 'collab', array(
+                     'del' => array($c->user_id => array('name' => $c->getName()->getOriginal()))
+                 ));
             }
-            $this->getEvents()->log($this->getObject(), 'collab', array(
-                'del' => array($c->user_id => array('name' => $c->getName()->getOriginal()))
-            ));
         }
 
         //statuses
@@ -196,15 +204,42 @@ implements Searchable {
                 'updated' => SqlFunction::NOW(),
                 'isactive' => 1,
             ));
+
+            foreach ($vars['cid'] as $c) {
+              $collab = Collaborator::lookup($c);
+              if(get_class($collab) == 'Collaborator') {
+                $collab->setFlag(Collaborator::FLAG_ACTIVE, true);
+                $collab->save();
+              }
+            }
         }
 
-        $this->collaborators->filter(array(
+        $inactive = $this->collaborators->filter(array(
             'thread_id' => $this->getId(),
             Q::not(array('id__in' => $cids ?: array(0)))
-        ))->update(array(
-            'updated' => SqlFunction::NOW(),
-            'isactive' => 0,
         ));
+        if($inactive) {
+          foreach ($inactive as $i) {
+            $i->setFlag(Collaborator::FLAG_ACTIVE, false);
+            $i->save();
+          }
+          $inactive->update(array(
+              'updated' => SqlFunction::NOW(),
+              'isactive' => 0,
+          ));
+        }
+
+        if($vars['recipientType']) {
+          $combo = array_combine($vars['uid'], $vars['recipientType']);
+          foreach ($combo as $id => $type) {
+            $collab = Collaborator::lookup($id);
+            if(get_class($collab) == 'Collaborator') {
+              $type == 'Cc' ? $collab->setFlag(Collaborator::FLAG_CC, true) :
+                $collab->setFlag(Collaborator::FLAG_CC, false);
+              $collab->save();
+            }
+          }
+        }
 
         unset($this->ht['active_collaborators']);
         $this->_collaborators = null;
@@ -240,8 +275,17 @@ implements Searchable {
             include_once INCLUDE_DIR . 'class.thread_actions.php';
 
         $entries = $this->getEntries();
-        if ($type && is_array($type))
-            $entries->filter(array('type__in' => $type));
+
+        if ($type && is_array($type)) {
+          $visibility = Q::all(array('type__in' => $type));
+
+          if ($type['poster']) {
+            $visibility->add(array('poster__exact' => $type['poster']));
+            $visibility->ored = true;
+          }
+
+          $entries->filter($visibility);
+        }
 
         if ($options['sort'] && !strcasecmp($options['sort'], 'DESC'))
             $entries->order_by('-id');
@@ -329,6 +373,30 @@ implements Searchable {
 
         $body = $mailinfo['message'];
 
+        // extra handling for determining Cc and Bcc collabs
+        if ($mailinfo['email']) {
+          $staffSenderId = Staff::getIdByEmail($mailinfo['email']);
+
+          if (!$staffSenderId) {
+            $senderId = UserEmailModel::getIdByEmail($mailinfo['email']);
+            if ($senderId) {
+              $mailinfo['userId'] = $senderId;
+
+              if ($object instanceof Ticket && $senderId != $object->user_id && $senderId != $object->staff_id) {
+                $mailinfo['userClass'] = 'C';
+
+                $collaboratorId = Collaborator::getIdByUserId($senderId, $this->getId());
+                $collaborator = Collaborator::lookup($collaboratorId);
+
+                if ($collaborator && ($collaborator->isCc()))
+                  $vars['thread-type'] = 'M';
+                else
+                  $vars['thread-type'] = 'N';
+              }
+            }
+          }
+        }
+
         // Attempt to determine the user posting the entry and the
         // corresponding entry type by the information determined by the
         // mail parser (via the In-Reply-To header)
@@ -403,7 +471,6 @@ implements Searchable {
         switch ($vars['thread-type']) {
         case 'M':
             $vars['message'] = $body;
-
             if ($object instanceof Threadable)
                 return $object->postThreadEntry('M', $vars);
             elseif ($this instanceof ObjectThread)
@@ -412,7 +479,6 @@ implements Searchable {
 
         case 'N':
             $vars['note'] = $body;
-
             if ($object instanceof Threadable)
                 return $object->postThreadEntry('N', $vars);
             elseif ($this instanceof ObjectThread)
@@ -622,6 +688,7 @@ implements TemplateVariable {
     var $_actions;
     var $is_autoreply;
     var $is_bounce;
+    var $_posterType;
 
     static protected $perms = array(
         self::PERM_EDIT => array(
@@ -688,6 +755,13 @@ implements TemplateVariable {
         return $this->poster;
     }
 
+    function getPosterType() {
+      $this->staff_id ?
+        $this->posterType = __('Agent') : $this->posterType = __('User');
+
+      return $this->posterType;
+    }
+
     function getTitle() {
         return $this->title;
     }
@@ -1314,7 +1388,6 @@ implements TemplateVariable {
         if (!$vars['threadId'] || !$vars['type'])
             return false;
 
-
         if (!$vars['body'] instanceof ThreadEntryBody) {
             if ($cfg->isRichTextEnabled())
                 $vars['body'] = new HtmlThreadEntryBody($vars['body']);
@@ -1340,8 +1413,40 @@ implements TemplateVariable {
             'poster' => $poster,
             'source' => $vars['source'],
             'flags' => $vars['flags'] ?: 0,
+            'recipients' => $vars['recipients'],
         ));
 
+        //add recipients to thread entry
+        $recipients = array();
+        $ticket = Thread::objects()->filter(array('id'=>$vars['threadId']))->values_flat('object_id')->first();
+        $ticketUser = Ticket::objects()->filter(array('ticket_id'=>$ticket[0]))->values_flat('user_id')->first();
+
+        //User
+        if ($ticketUser) {
+          $uEmail = UserEmailModel::objects()->filter(array('user_id'=>$ticketUser[0]))->values_flat('address')->first();
+          $u = array();
+          $u[$ticketUser[0]] = $uEmail[0];
+          $recipients['to'] = $u;
+        }
+
+        if (Collaborator::getIdByUserId($vars['userId'], $vars['threadId']))
+          $entry->flags |= ThreadEntry::FLAG_COLLABORATOR;
+
+        //Cc collaborators
+        if($vars['ccs'] && $vars['emailcollab'] == 1) {
+          $cc = Collaborator::getCollabList($vars['ccs']);
+          $recipients['cc'] = $cc;
+        }
+
+        //Bcc Collaborators
+        if($vars['bccs'] && $vars['emailcollab'] == 1) {
+          $bcc = Collaborator::getCollabList($vars['bccs']);
+          $recipients['bcc'] = $bcc;
+        }
+
+        if (($vars['do'] == 'create' || $vars['emailreply'] == 1) && $recipients)
+          $entry->recipients = json_encode($recipients);
+
         if ($entry->format == 'html')
             // The current codebase properly balances html
             $entry->flags |= self::FLAG_BALANCED;
@@ -1898,7 +2003,7 @@ class CollaboratorEvent extends ThreadEvent {
             }
             $desc = sprintf($base, implode(', ', $collabs));
             break;
-        case isset($data['add']):
+        case isset($data['add']) && $mode!=self::MODE_CLIENT:
             $base = __('<b>{somebody}</b> added <strong>%s</strong> as collaborators {timestamp}');
             $collabs = array();
             if ($data['add']) {
@@ -2521,14 +2626,12 @@ implements TemplateVariable {
     }
 
     function addNote($vars, &$errors=array()) {
-
         //Add ticket Id.
         $vars['threadId'] = $this->getId();
         return NoteThreadEntry::add($vars, $errors);
     }
 
     function addMessage($vars, &$errors) {
-
         $vars['threadId'] = $this->getId();
         $vars['staffId'] = 0;
 
diff --git a/include/class.thread_actions.php b/include/class.thread_actions.php
index 1eaab156a..dffc93631 100644
--- a/include/class.thread_actions.php
+++ b/include/class.thread_actions.php
@@ -18,6 +18,65 @@
 **********************************************************************/
 include_once(INCLUDE_DIR.'class.thread.php');
 
+class TEA_ShowEmailRecipients extends ThreadEntryAction {
+    static $id = 'emailrecipients';
+    static $name = /* trans */ 'View Email Recipients';
+    static $icon = 'group';
+
+    function isVisible() {
+        global $thisstaff;
+
+        if ($this->entry->getEmailHeader())
+          return ($thisstaff && $this->entry->getEmailHeader());
+        elseif ($this->entry->recipients)
+          return $this->entry->recipients;
+
+    }
+
+    function getJsStub() {
+        return sprintf("$.dialog('%s');",
+            $this->getAjaxUrl()
+        );
+    }
+
+    function trigger() {
+        switch ($_SERVER['REQUEST_METHOD']) {
+        case 'GET' && $this->entry->recipients:
+            return $this->getRecipients();
+        case 'GET':
+            return $this->trigger__get();
+        }
+    }
+
+    private function trigger__get() {
+        $hdr = Mail_parse::splitHeaders(
+                $this->entry->getEmailHeader(), true);
+
+        $recipients = array();
+        foreach (array('To', 'TO', 'Cc', 'CC', 'Bcc', 'BCC') as $k) {
+            if (isset($hdr[$k]) && $hdr[$k] &&
+                ($addresses=Mail_Parse::parseAddressList($hdr[$k]))) {
+                foreach ($addresses as $addr) {
+                    $email = sprintf('%s@%s', $addr->mailbox, $addr->host);
+                    $name = $addr->personal ?: '';
+                    $recipients[$k][] = sprintf('%s<%s>',
+                            (($name && strcasecmp($name, $email))? "$name ": ''),
+                            $email);
+                }
+            }
+        }
+
+        include STAFFINC_DIR . 'templates/thread-email-recipients.tmpl.php';
+    }
+
+    private function getRecipients() {
+      $recipients = json_decode($this->entry->recipients, true);
+
+      include STAFFINC_DIR . 'templates/thread-email-recipients.tmpl.php';
+    }
+}
+ThreadEntry::registerAction(/* trans */ 'E-Mail', 'TEA_ShowEmailRecipients');
+
 class TEA_ShowEmailHeaders extends ThreadEntryAction {
     static $id = 'view_headers';
     static $name = /* trans */ 'View Email Headers';
@@ -134,6 +193,7 @@ JS
             'staffId' => $old->staff_id,
             'type' => $old->type,
             'threadId' => $old->thread_id,
+            'recipients' => $old->recipients,
 
             // Connect the new entry to be a child of the previous
             'pid' => $old->id,
@@ -313,16 +373,19 @@ class TEA_EditAndResendThreadEntry extends TEA_EditThreadEntry {
                 && ($tpl = $dept->getTemplate())
                 && ($msg=$tpl->getReplyMsgTemplate())) {
 
+            $recipients = json_decode($response->recipients, true);
+
             $msg = $object->replaceVars($msg->asArray(),
                 $variables + array('recipient' => $object->getOwner()));
 
             $attachments = $cfg->emailAttachments()
                 ? $response->getAttachments() : array();
             $email->send($object->getOwner(), $msg['subj'], $msg['body'],
-                $attachments, $options);
+                $attachments, $options, $recipients);
         }
         // TODO: Add an option to the dialog
-        $object->notifyCollaborators($response, array('signature' => $signature));
+        if ($object instanceof Task)
+          $object->notifyCollaborators($response, array('signature' => $signature));
 
         // Log an event that the item was resent
         $object->logEvent('resent', array('entry' => $response->id));
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 4d9251c66..e6c85da6b 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -778,16 +778,35 @@ implements RestrictedAccess, Threadable, Searchable {
     }
 
     //UserList of recipients  (owner + collaborators)
-    function getRecipients() {
-        if (!isset($this->recipients)) {
-            $list = new UserList();
-            $list->add($this->getOwner());
-            if ($collabs = $this->getThread()->getActiveCollaborators()) {
-                foreach ($collabs as $c)
+    function getRecipients($excludeBcc=false) {
+        if ($excludeBcc && isset($this->recipients)) {
+          $list = new UserList();
+
+          if ($collabs = $this->getThread()->getActiveCollaborators()) {
+              $list->add($this->getOwner());
+              foreach ($collabs as $c) {
+                if (get_class($c) == 'Collaborator' && !$c->isCc()) //skip bcc
+                    continue;
+                  else
                     $list->add($c);
-            }
-            $this->recipients = $list;
+                }
+              }
+
+          $this->recipients = $list;
+        }
+        //I think we need to rebuild each time since it
+        //would be incomplete if called after an exclude bcc call
+        else {
+          $list = new UserList();
+          $list->add($this->getOwner());
+          if ($collabs = $this->getThread()->getActiveCollaborators()) {
+              foreach ($collabs as $c) {
+                $list->add($c);
+              }
+          }
+          $this->recipients = $list;
         }
+
         return $this->recipients;
     }
 
@@ -931,6 +950,7 @@ implements RestrictedAccess, Threadable, Searchable {
                 'updated' => SqlFunction::NOW(),
                 'isactive' => 1,
             ));
+            $collab->save();
         }
 
         if ($cids) {
@@ -1386,8 +1406,7 @@ implements RestrictedAccess, Threadable, Searchable {
      * Notify collaborators on response or new message
      *
      */
-
-    function  notifyCollaborators($entry, $vars = array()) {
+    function notifyCollaborators($entry, $vars = array()) {
         global $cfg;
 
         if (!$entry instanceof ThreadEntry
@@ -1399,25 +1418,22 @@ implements RestrictedAccess, Threadable, Searchable {
         ) {
             return;
         }
-        // Who posted the entry?
-        $skip = array();
-        if ($entry instanceof MessageThreadEntry) {
-            $poster = $entry->getUser();
-            // Skip the person who sent in the message
-            $skip[$entry->getUserId()] = 1;
-            // Skip all the other recipients of the message
-            foreach ($entry->getAllEmailRecipients() as $R) {
-                foreach ($recipients as $R2) {
-                    if (0 === strcasecmp($R2->getEmail(), $R->mailbox.'@'.$R->host)) {
-                        $skip[$R2->getUserId()] = true;
-                        break;
-                    }
-                }
-            }
-        } else {
-            $poster = $entry->getStaff();
-            // Skip the ticket owner
-            $skip[$this->getUserId()] = 1;
+
+        $poster = User::lookup($entry->user_id);
+        $posterEmail = $poster->getEmail()->address;
+
+        if($vars['ccs']) {
+          foreach ($vars['ccs'] as $cc) {
+            $collab = Collaborator::getIdByUserId($cc, $this->getThread()->getId());
+            $recipients[] = Collaborator::lookup($collab);
+          }
+        }
+
+        if($vars['bccs']) {
+          foreach ($vars['bccs'] as $bcc) {
+            $collab = Collaborator::getIdByUserId($bcc, $this->getThread()->getId());
+            $recipients[] = Collaborator::lookup($collab);
+          }
         }
 
         $vars = array_merge($vars, array(
@@ -1434,15 +1450,53 @@ implements RestrictedAccess, Threadable, Searchable {
         if ($vars['from_name'])
             $options += array('from_name' => $vars['from_name']);
 
+        $skip = array();
+        if ($entry instanceof MessageThreadEntry) {
+          foreach ($entry->getAllEmailRecipients() as $R) {
+                $skip[] = $R->mailbox.'@'.$R->host;
+            }
+        }
+
+        $collaborators = array();
+        $collabsCc = array();
+        $collabsBcc = array();
         foreach ($recipients as $recipient) {
-            // Skip folks who have already been included on this part of
-            // the conversation
-            if (isset($skip[$recipient->getUserId()]))
-                continue;
-            $notice = $this->replaceVars($msg, array('recipient' => $recipient));
-            $email->send($recipient, $notice['subj'], $notice['body'], $attachments,
-                $options);
+            if(get_class($recipient) == 'Collaborator') {
+              if ($recipient->isCc()) {
+                $collabsCc[] = $recipient->getEmail()->address;
+                $cnotice = $this->replaceVars($msg, array('recipient.name.first' => __('Collaborator'), 'recipient' => $recipient));
+              }
+              else
+                $collabsBcc[] = $recipient;
+            }
+
+            if(get_class($recipient) == 'TicketOwner') {
+              $owner = $recipient;
+            }
+         }
+
+         foreach ($collabsBcc as $recipient) {
+           $notice = $this->replaceVars($msg, array('recipient' => $recipient));
+           if ($posterEmail != $recipient->getEmail()->address)
+             $email->send($recipient, $notice['subj'], $notice['body'], $attachments,
+                 $options);
+         }
+
+        foreach ($collabsCc as $cc) {
+          if (in_array($cc, $skip))
+            continue;
+          elseif ($cc != $posterEmail)
+            $collaborators[] = $cc;
         }
+
+        if ($owner->getEmail()->address != $poster->getEmail()->address && !in_array($owner->getEmail()->address, $skip))
+          $collaborators[] = $owner->getEmail()->address;
+
+        $collaborators['cc'] = $collaborators;
+
+        //collaborator email sent out
+        $email->send('', $cnotice['subj'], $cnotice['body'], $attachments,
+            $options, $collaborators);
     }
 
     function onMessage($message, $autorespond=true, $reopen=true) {
@@ -1534,7 +1588,6 @@ implements RestrictedAccess, Threadable, Searchable {
         global $cfg, $thisstaff;
 
         //TODO: do some shit
-
         if (!$alert // Check if alert is enabled
             || !$cfg->alertONNewActivity()
             || !($dept=$this->getDept())
@@ -1933,6 +1986,8 @@ implements RestrictedAccess, Threadable, Searchable {
     function replaceVars($input, $vars = array()) {
         global $ost;
 
+        $recipients = $this->getRecipients(true);
+
         $vars = array_merge($vars, array('ticket' => $this));
         return $ost->replaceTemplateVariables($input, $vars);
     }
@@ -2276,6 +2331,69 @@ implements RestrictedAccess, Threadable, Searchable {
             $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
 
         $errors = array();
+
+        $hdr = Mail_parse::splitHeaders($vars['header'], true);
+        $existingCollab = Collaborator::getIdByUserId($vars['userId'], $this->getThreadId());
+
+        if (($vars['userId'] != $this->user_id) && (!$existingCollab)) {
+          if ($vars['userId'] == 0) {
+            $emailStream = '<<<EOF' . $vars['header'] . 'EOF';
+            $parsed = EmailDataParser::parse($emailStream);
+            $email = $parsed['email'];
+            if (!$existinguser = User::lookupByEmail($email)) {
+              $name = $parsed['name'];
+              $user = User::fromVars(array('name' => $name, 'email' => $email));
+              $vars['userId'] = $user->getId();
+            }
+          }
+          else
+            $user = User::lookup($vars['userId']);
+
+          $c = $this->getThread()->addCollaborator($user,array('isactive'=>1), $errors);
+
+          foreach (array('To', 'TO', 'Cc', 'CC') as $k) {
+            if ($user && isset($hdr[$k]) && $hdr[$k])
+              $addresses[] = Mail_Parse::parseAddressList($hdr[$k]);
+          }
+          if (count($addresses) > 1) {
+            $isMsg = true;
+            $c->setCc();
+          }
+          else
+            $c->setBcc();
+        }
+        else {
+          $c = Collaborator::lookup($existingCollab);
+          if ($c && !$c->isCc()) {
+            foreach (array('To', 'TO', 'Cc', 'CC') as $k) {
+              if (isset($hdr[$k]) && $hdr[$k])
+                $addresses[] = Mail_Parse::parseAddressList($hdr[$k]);
+            }
+            if (count($addresses) > 1) {
+              $isMsg = true;
+              $c->setCc();
+            }
+          }
+        }
+
+        if ($vars['userId'] == $this->user_id)
+          $isMsg = true;
+
+        //lookup user by userId. if they are bcc in thread, post internal note
+        if($collabs = $this->getRecipients()) {
+          foreach ($collabs as $collab) {
+            if(get_class($collab) == 'Collaborator' && $collab->user_id == $vars['userId'] && !$collab->isCc()) {
+              $user = User::lookup($vars['userId']);
+              $vars['note'] = $vars['message'];
+
+              //post internal note
+              if (!$isMsg) {
+                return $this->postNote($vars,$errors, $user, true);
+              }
+            }
+          }
+        }
+
         if (!($message = $this->getThread()->addMessage($vars, $errors)))
             return null;
 
@@ -2297,14 +2415,22 @@ implements RestrictedAccess, Threadable, Searchable {
                 if (strcasecmp($recipient['source'], 'delivered-to') === 0)
                     continue;
 
-                if (($user=User::fromVars($recipient)))
-                    if ($c=$this->addCollaborator($user, $info, $errors, false))
-                        // FIXME: This feels very unwise — should be a
-                        // string indexed array for future
-                        $collabs[$c->user_id] = array(
-                            'name' => $c->getName()->getOriginal(),
-                            'src' => $recipient['source'],
-                        );
+                if (($cuser=User::fromVars($recipient))) {
+                  if (!$existing = Collaborator::getIdByUserId($cuser->getId(), $this->getThreadId())) {
+                    if ($c=$this->addCollaborator($cuser, $info, $errors, false)) {
+                      $c->setCc();
+
+                      // FIXME: This feels very unwise — should be a
+                      // string indexed array for future
+                      $collabs[$c->user_id] = array(
+                          'name' => $c->getName()->getOriginal(),
+                          'src' => $recipient['source'],
+                      );
+                    }
+                  }
+
+                }
+
             }
             // TODO: Can collaborators add others?
             if ($collabs) {
@@ -2324,8 +2450,10 @@ implements RestrictedAccess, Threadable, Searchable {
 
         $this->onMessage($message, ($autorespond && $alerts), $reopen); //must be called b4 sending alerts to staff.
 
-        if ($autorespond && $alerts && $cfg && $cfg->notifyCollabsONNewMessage())
-            $this->notifyCollaborators($message, array('signature' => ''));
+        if ($autorespond && $alerts && $cfg && $cfg->notifyCollabsONNewMessage()) {
+          //when user replies, this is where collabs notified
+          $this->notifyCollaborators($message, array('signature' => ''));
+        }
 
         if (!($alerts && $autorespond))
             return $message; //Our work is done...
@@ -2389,7 +2517,6 @@ implements RestrictedAccess, Threadable, Searchable {
                 $sentlist[] = $staff->getEmail();
             }
         }
-
         return $message;
     }
 
@@ -2469,6 +2596,30 @@ implements RestrictedAccess, Threadable, Searchable {
     function postReply($vars, &$errors, $alert=true, $claim=true) {
         global $thisstaff, $cfg;
 
+        if ($collabs = $this->getRecipients()) {
+          $collabIds = array();
+          foreach ($collabs as $collab)
+            $collabIds[] = $collab->user_id;
+        }
+
+        $ticket = Ticket::lookup($vars['id']);
+        if (isset($vars['ccs'])) {
+          foreach ($vars['ccs'] as $uid) {
+            $user = User::lookup($uid);
+            if (!in_array($uid, $collabIds))
+              if (($c2=$ticket->getThread()->addCollaborator($user,array('isactive'=>1), $errors)))
+                    $c2->setCc();
+          }
+        }
+        if (isset($vars['bccs'])) {
+          foreach ($vars['bccs'] as $uid) {
+            $user = User::lookup($uid);
+            if (!in_array($uid, $collabIds))
+              if (($c2=$ticket->getThread()->addCollaborator($user,array('isactive'=>1), $errors)))
+                $c2->setBcc();
+          }
+        }
+
         if (!$vars['poster'] && $thisstaff)
             $vars['poster'] = $thisstaff;
 
@@ -2507,7 +2658,9 @@ implements RestrictedAccess, Threadable, Searchable {
         if (!$alert)
             return $response;
 
-        $email = $dept->getEmail();
+        //allow agent to send from different dept email
+        $vars['from_name'] ? $email = Email::lookup($vars['from_name']) : $email = $dept->getEmail();
+
         $options = array('thread'=>$response);
         $signature = $from_name = '';
         if ($thisstaff && $vars['signature']=='mine')
@@ -2529,10 +2682,8 @@ implements RestrictedAccess, Threadable, Searchable {
                 default:
                     $from_name =  $email->getName();
             }
-
             if ($from_name)
                 $options += array('from_name' => $from_name);
-
         }
 
         $variables = array(
@@ -2543,7 +2694,7 @@ implements RestrictedAccess, Threadable, Searchable {
         );
 
         $user = $this->getOwner();
-        if (($email=$dept->getEmail())
+        if (($email=$email)
             && ($tpl = $dept->getTemplate())
             && ($msg=$tpl->getReplyMsgTemplate())
         ) {
@@ -2551,17 +2702,42 @@ implements RestrictedAccess, Threadable, Searchable {
                 $variables + array('recipient' => $user)
             );
             $attachments = $cfg->emailAttachments()?$response->getAttachments():array();
+          }
+
+          if ($vars['emailcollab'] == 1) {
+            //Cc collaborators
+            if($vars['ccs']) {
+              $collabsCc = array();
+              $collabsCc[] = Collaborator::getCollabList($vars['ccs']);
+              $collabsCc['cc'] = $collabsCc;
+              $email->send($user, $msg['subj'], $msg['body'], $attachments,
+                    $options, $collabsCc);
+            }
+            else {
+              $email->send($user, $msg['subj'], $msg['body'], $attachments,
+                  $options);
+            }
+
+            //Bcc Collaborators
+            if($vars['bccs']) {
+              foreach ($vars['bccs'] as $uid) {
+                $recipient = User::lookup($uid);
+                if (($bcctpl = $dept->getTemplate())
+                    && ($bccmsg=$bcctpl->getReplyMsgTemplate())) {
+                  $bccmsg = $this->replaceVars($bccmsg->asArray(), $variables +
+                      array('recipient' => $user, 'recipient.name.first' => $recipient->getName()->getFirst())
+                  );
+
+                  $email->send($recipient, $bccmsg['subj'], $bccmsg['body'], $attachments,
+                      $options);
+                }
+              }
+            }
+          }
+          else
             $email->send($user, $msg['subj'], $msg['body'], $attachments,
                 $options);
-        }
 
-        if ($vars['emailcollab']) {
-            $this->notifyCollaborators($response,
-                array(
-                    'signature' => $signature,
-                    'from_name' => $from_name)
-            );
-        }
         return $response;
     }
 
@@ -2596,12 +2772,12 @@ implements RestrictedAccess, Threadable, Searchable {
     function postNote($vars, &$errors, $poster=false, $alert=true) {
         global $cfg, $thisstaff;
 
-        //Who is posting the note - staff or system?
+        //Who is posting the note - staff or system? or user?
         if ($vars['staffId'] && !$poster)
             $poster = Staff::lookup($vars['staffId']);
 
         $vars['staffId'] = $vars['staffId'] ?: 0;
-        if ($poster && is_object($poster)) {
+        if ($poster && is_object($poster) && !$vars['userId']) {
             $vars['staffId'] = $poster->getId();
             $vars['poster'] = $poster->getName();
         }
@@ -3546,6 +3722,35 @@ implements RestrictedAccess, Threadable, Searchable {
         if (!($ticket=self::create($create_vars, $errors, 'staff', false)))
             return false;
 
+        $collabsCc = array();
+        $collabsBcc = array();
+        if (isset($vars['ccs'])) {
+          foreach ($vars['ccs'] as $uid) {
+            $ccuser = User::lookup($uid);
+
+            if ($ccuser && !$existing = Collaborator::getIdByUserId($ccuser->getId(), $ticket->getThreadId())) {
+                $collabsCc[] = $ccuser->getEmail()->address;
+
+              if (($c2=$ticket->getThread()->addCollaborator($ccuser,array('isactive'=>1), $errors)))
+                    $c2->setCc();
+            }
+          }
+          $collabsCc['cc'] = $collabsCc;
+        }
+
+        if (isset($vars['bccs'])) {
+          foreach ($vars['bccs'] as $uid) {
+            $bccuser = User::lookup($uid);
+
+            if ($bccuser && !$existing = Collaborator::getIdByUserId($bccuser->getId(), $ticket->getThreadId())) {
+              $collabsBcc[] = $bccuser;
+
+              if (($c2=$ticket->getThread()->addCollaborator($bccuser,array('isactive'=>1), $errors)))
+                $c2->setBcc();
+            }
+          }
+        }
+
         $vars['msgId']=$ticket->getLastMsgId();
 
         // Effective role for the department
@@ -3608,8 +3813,37 @@ implements RestrictedAccess, Threadable, Searchable {
             $options = array(
                 'thread' => $message ?: $ticket->getThread(),
             );
-            $email->send($ticket->getOwner(), $msg['subj'], $msg['body'], $attachments,
-                $options);
+
+            //ticket created on user's behalf
+            if($vars['emailcollab'] == 1) {
+
+              $email->send($ticket->getOwner(), $msg['subj'], $msg['body'], $attachments,
+                  $options, $collabsCc);
+
+              if ($collabsBcc) {
+                foreach ($collabsBcc as $recipient) {
+                  if (($tpl=$dept->getTemplate())
+                      && ($bccmsg=$tpl->getNewTicketNoticeMsgTemplate())
+                      && ($email=$dept->getEmail())
+                  )
+                  $bccmsg = $ticket->replaceVars($bccmsg->asArray(),
+                      array(
+                          'message'   => $message,
+                          'signature' => $signature,
+                          'response'  => ($response) ? $response->getBody() : '',
+                          'recipient' => $ticket->getOwner(),
+                          'recipient.name.first' => $recipient->getName()->getFirst(),
+                      )
+                  );
+
+                  $email->send($recipient, $bccmsg['subj'], $bccmsg['body'], $attachments,
+                      $options);
+                }
+              }
+            }
+            else
+              $email->send($ticket->getOwner(), $msg['subj'], $msg['body'], $attachments,
+                  $options);
         }
         return $ticket;
     }
diff --git a/include/class.user.php b/include/class.user.php
index e5f898d7d..4edb3d7a2 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -34,6 +34,15 @@ class UserEmailModel extends VerySimpleModel {
     function __toString() {
         return (string) $this->address;
     }
+
+    static function getIdByEmail($email) {
+        $row = UserEmailModel::objects()
+            ->filter(array('address'=>$email))
+            ->values_flat('user_id')
+            ->first();
+
+        return $row ? $row[0] : 0;
+    }
 }
 
 class UserModel extends VerySimpleModel {
@@ -149,6 +158,13 @@ class UserModel extends VerySimpleModel {
         return true;
     }
 
+    public function setFlag($flag, $val) {
+        if ($val)
+            $this->status |= $flag;
+        else
+            $this->status &= ~$flag;
+    }
+
     protected function hasStatus($flag) {
         return $this->get('status') & $flag !== 0;
     }
diff --git a/include/client/templates/thread-entry.tmpl.php b/include/client/templates/thread-entry.tmpl.php
index c87edfa0b..3ab3127e8 100644
--- a/include/client/templates/thread-entry.tmpl.php
+++ b/include/client/templates/thread-entry.tmpl.php
@@ -1,16 +1,22 @@
 <?php
 global $cfg;
-$entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note');
+$entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note', 'B' => 'bccmessage');
 $user = $entry->getUser() ?: $entry->getStaff();
 $name = $user ? $user->getName() : $entry->poster;
 $avatar = '';
 if ($cfg->isAvatarsEnabled() && $user)
     $avatar = $user->getAvatar();
 ?>
+<?php
+  if ($entryTypes[$entry->type] == 'note') {
+    $entryTypes[$entry->type] = 'bccmessage';
+    $entry->type = 'B';
+  }
+?>
 
 <div class="thread-entry <?php echo $entryTypes[$entry->type]; ?> <?php if ($avatar) echo 'avatar'; ?>">
 <?php if ($avatar) { ?>
-    <span class="<?php echo ($entry->type == 'M') ? 'pull-left' : 'pull-right'; ?> avatar">
+    <span class="<?php echo ($entry->type == 'M' || $entry->type == 'B') ? 'pull-left' : 'pull-right'; ?> avatar">
 <?php echo $avatar; ?>
     </span>
 <?php } ?>
diff --git a/include/client/templates/thread-event.tmpl.php b/include/client/templates/thread-event.tmpl.php
index 42fd8027e..faa922f6d 100644
--- a/include/client/templates/thread-event.tmpl.php
+++ b/include/client/templates/thread-event.tmpl.php
@@ -1,5 +1,5 @@
 <?php
-$desc = $event->getDescription(ThreadEvent::MODE_CLIENT);
+$desc = $event->getDescription(ThreadEvent::MODE_CLIENT); 
 if (!$desc)
     return;
 ?>
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 6840c252b..28c7605bb 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -129,7 +129,7 @@ $tickets->order_by($order.$order_by);
 $tickets->values(
     'ticket_id', 'number', 'created', 'isanswered', 'source', 'status_id',
     'status__state', 'status__name', 'cdata__subject', 'dept_id',
-    'dept__name', 'dept__ispublic', 'user__default_email__address'
+    'dept__name', 'dept__ispublic', 'user__default_email__address', 'user_id'
 );
 
 ?>
@@ -238,6 +238,7 @@ if ($closedTickets) {?>
                 $subject="<b>$subject</b>";
                 $ticketNumber="<b>$ticketNumber</b>";
             }
+            $thisclient->getId() != $T['user_id'] ? $isCollab = true : $isCollab = false;
             ?>
             <tr id="<?php echo $T['ticket_id']; ?>">
                 <td>
@@ -247,7 +248,11 @@ if ($closedTickets) {?>
                 <td><?php echo Format::date($T['created']); ?></td>
                 <td><?php echo $status; ?></td>
                 <td>
+                  <?php if ($isCollab) {?>
+                    <div style="max-height: 1.2em; max-width: 320px;" class="link truncate" href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><i class="icon-group"></i> <?php echo $subject; ?></div>
+                  <?php } else {?>
                     <div style="max-height: 1.2em; max-width: 320px;" class="link truncate" href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></div>
+                    <?php } ?>
                 </td>
                 <td><span class="truncate"><?php echo $dept; ?></span></td>
             </tr>
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index 2aa01d019..b368ae25d 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -38,8 +38,17 @@ if ($thisclient && $thisclient->isGuest()
                 </b>
                 <small>#<?php echo $ticket->getNumber(); ?></small>
 <div class="pull-right">
-    <a class="action-button" href="tickets.php?a=print&id=<?php
-        echo $ticket->getId(); ?>"><i class="icon-print"></i> <?php echo __('Print'); ?></a>
+  <?php
+      if($collabs = $ticket->getRecipients()) {
+        foreach ($collabs as $collab) {
+          if(get_class($collab) == 'Collaborator' && $collab->user_id == $thisclient->getId() && !$collab->isCc()) {
+            $viewThreads = true;
+          }
+        }
+      } ?>
+      <a class="action-button" href="tickets.php?a=print&id=<?php
+          echo $ticket->getId(); ?>"><i class="icon-print"></i> <?php echo __('Print'); ?></a>
+
 <?php if ($ticket->hasClientEditableFields()
         // Only ticket owners can edit the ticket details (and other forms)
         && $thisclient->getId() == $ticket->getUserId()) { ?>
@@ -134,13 +143,15 @@ echo $v;
 </tr>
 </table>
 <br>
+  <?php
+    $email = $thisclient->getUserName();
+    $clientName = TicketUser::lookupByEmail($email)->getName()->name;
 
-<?php
-    $ticket->getThread()->render(array('M', 'R'), array(
-                'mode' => Thread::MODE_CLIENT,
-                'html-id' => 'ticketThread')
-            );
-?>
+    $ticket->getThread()->render(array('M', 'R', 'poster' => $clientName), array(
+                    'mode' => Thread::MODE_CLIENT,
+                    'html-id' => 'ticketThread')
+                );
+  ?>
 
 <div class="clear" style="padding-bottom:10px;"></div>
 <?php if($errors['err']) { ?>
diff --git a/include/i18n/en_US/templates/email/message.alert.yaml b/include/i18n/en_US/templates/email/message.alert.yaml
index e6e83e5bb..13826fba8 100644
--- a/include/i18n/en_US/templates/email/message.alert.yaml
+++ b/include/i18n/en_US/templates/email/message.alert.yaml
@@ -26,7 +26,7 @@ body: |
              <strong>From</strong>:
         </td>
         <td>
-             %{ticket.name} &lt;%{ticket.email}&gt;
+             %{poster.name} &lt;%{ticket.email}&gt;
         </td>
     </tr>
     <tr>
diff --git a/include/staff/templates/collaborators.tmpl.php b/include/staff/templates/collaborators.tmpl.php
index 4393ba1f2..7f80a5804 100644
--- a/include/staff/templates/collaborators.tmpl.php
+++ b/include/staff/templates/collaborators.tmpl.php
@@ -3,46 +3,73 @@
 <?php
 if($info && $info['msg']) {
     echo sprintf('<p id="msg_notice" style="padding-top:2px;">%s</p>', $info['msg']);
-} ?>
+}
+
+if ($thread->object_type == 'T')
+  $type = '\'tickets\'';
+if ($thread->object_type == 'A')
+  $type = '\'tasks\'';
+?>
 <hr/>
 <?php
 if(($users=$thread->getCollaborators())) {?>
 <div id="manage_collaborators">
-<form method="post" class="collaborators" action="#thread/<?php echo $thread->getId(); ?>/collaborators">
+<form method="post" class="collaborators" onsubmit="refreshAndClose(<?php echo $thread->object_id; ?>, <?php echo $type; ?>);" action="#thread/<?php echo $thread->getId(); ?>/collaborators">
     <table border="0" cellspacing="1" cellpadding="1" width="100%">
     <?php
     foreach($users as $user) {
         $checked = $user->isActive() ? 'checked="checked"' : '';
+        $cc = $user->isCc() ? 'selected="selected"' : '';
+        $bcc = !$user->isCc() ? 'selected="selected"' : '';
+
         echo sprintf('<tr>
                         <td>
                             <label class="inline checkbox">
+                            <input type="checkbox" class="hidden" name="uid[]" id="%d" value="%d" checked="checked">
                             <input type="checkbox" name="cid[]" id="c%d" value="%d" %s>
                             </label>
                             <a class="collaborator" href="#thread/%d/collaborators/%d/view">%s%s</a>
-                            <span class="faded"><em>%s</em></span></td>
-                        <td width="10">
-                            <input type="hidden" name="del[]" id="d%d" value="">
-                            <a class="remove" href="#d%d">&times;</a></td>
-                        <td width="30">&nbsp;</td>
-                    </tr>',
-                    $user->getId(),
-                    $user->getId(),
-                    $checked,
-                    $thread->getId(),
-                    $user->getId(),
-                    (($U = $user->getUser()) && ($A = $U->getAvatar()))
-                        ? $U->getAvatar()->getImageTag(24) : '',
-                    Format::htmlchars($user->getName()),
-                    $user->getEmail(),
-                    $user->getId(),
-                    $user->getId());
+                            <div align="left">
+                                <span class="faded"><em>%s</em></span>
+                            </div>
+                        </td>', $user->getId(),
+                        $user->getId(),
+                        $user->getId(),
+                        $user->getId(),
+                        $checked,
+                        $thread->getId(),
+                        $user->getId(),
+                        (($U = $user->getUser()) && ($A = $U->getAvatar()))
+                            ? $U->getAvatar()->getImageTag(24) : '',
+                        Format::htmlchars($user->getName()),
+                        $user->getEmail());
+
+            if ($thread->object_type == 'T') {
+              echo sprintf('<td>
+                <select name="recipientType[]">
+                    <option value="Cc" %s>Cc</option>
+                    <option value="Bcc" %s>Bcc</option>
+                </select>
+              </td>', $cc, $bcc);
+            }
+
+            echo sprintf('<td width="10">
+                <input type="hidden" name="del[]" id="d%d" value="">
+                <a class="remove" href="#d%d">
+                  <i class="icon-trash icon-fixed-width"></i>
+                </a>
+            </td>
+            <td width="30">&nbsp;</td>
+            </tr>',$user->getId(), $user->getId());
     }
     ?>
+    <td>
+      <div><a class="collaborator" id="addcollaborator"
+          href="#thread/<?php echo $thread->getId(); ?>/add-collaborator"
+          ><i class="icon-plus-sign"></i> <?php echo __('Add Collaborator'); ?></a></div>
+    </td>
     </table>
     <hr style="margin-top:1em"/>
-    <div><a class="collaborator"
-        href="#thread/<?php echo $thread->getId(); ?>/add-collaborator"
-        ><i class="icon-plus-sign"></i> <?php echo __('Add New Collaborator'); ?></a></div>
     <div id="savewarning" style="display:none; padding-top:2px;"><p
     id="msg_warning"><?php echo __('You have made changes that you need to save.'); ?></p></div>
     <p class="full-width">
@@ -136,4 +163,8 @@ $(function() {
     });
 
 });
+
+function refreshAndClose(tid, type) {
+  window.location.href = type + '.php?id=' + tid;
+}
 </script>
diff --git a/include/staff/templates/task-view.tmpl.php b/include/staff/templates/task-view.tmpl.php
index b2dff1253..84cbcdea7 100644
--- a/include/staff/templates/task-view.tmpl.php
+++ b/include/staff/templates/task-view.tmpl.php
@@ -619,7 +619,6 @@ else
 <?php
 echo $reply_attachments_form->getMedia();
 ?>
-
 <script type="text/javascript">
 $(function() {
     $(document).off('.tasks-content');
diff --git a/include/staff/templates/thread-email-recipients.tmpl.php b/include/staff/templates/thread-email-recipients.tmpl.php
new file mode 100644
index 000000000..3bae51c02
--- /dev/null
+++ b/include/staff/templates/thread-email-recipients.tmpl.php
@@ -0,0 +1,30 @@
+<?php
+if (!$_REQUEST['mode']) { ?>
+<h3 class="drag-handle"><?php echo __('Email Recipients'); ?></h3>
+<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b>
+<hr/>
+<?php
+} ?>
+<p>
+<table>
+<?php
+$recipients = Format::htmlchars($recipients);
+ foreach ($recipients as $k => $v) {
+    echo sprintf('<tr><td nowrap width="5" valign="top"><b>%s</b>:</td><td>%s</td></tr>',
+            ucfirst($k),            
+            implode('<br>', $v)
+             );
+ }
+ ?>
+</table>
+<?php
+if (!$_REQUEST['mode']) {?>
+<hr>
+<p class="full-width">
+    <span class="buttons pull-right">
+        <input type="button" name="cancel" class="close"
+            value="<?php echo __('Close'); ?>">
+    </span>
+</p>
+<?php
+} ?>
diff --git a/include/staff/templates/thread-entry.tmpl.php b/include/staff/templates/thread-entry.tmpl.php
index a904670f2..d36d2b7c9 100644
--- a/include/staff/templates/thread-entry.tmpl.php
+++ b/include/staff/templates/thread-entry.tmpl.php
@@ -7,12 +7,17 @@ if ($thisstaff && !strcasecmp($thisstaff->datetime_format, 'relative')) {
     };
 }
 
-$entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note');
+$entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note','B' => 'bccmessage');
 $user = $entry->getUser() ?: $entry->getStaff();
 $name = $user ? $user->getName() : $entry->poster;
 $avatar = '';
 if ($user && $cfg->isAvatarsEnabled())
     $avatar = $user->getAvatar();
+
+if ($entry->flags & ThreadEntry::FLAG_COLLABORATOR && $entry->type == 'N') {
+  $entryTypes[$entry->type] = 'bccmessage';
+  $entry->type = 'B';
+}
 ?>
 <div class="thread-entry <?php
     echo $entry->isSystem() ? 'system' : $entryTypes[$entry->type]; ?> <?php if ($avatar) echo 'avatar'; ?>">
@@ -53,9 +58,12 @@ if ($user && $cfg->isAvatarsEnabled())
         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   } ?>
+        if ($entry->flags & ThreadEntry::FLAG_COLLABORATOR && $entry->type == 'B') { ?>
+            <span class="label label-bare"><?php echo __('Bcc Collaborator'); ?></span>
+<?php   }
+        if ($entry->flags & ThreadEntry::FLAG_COLLABORATOR && $entry->type == 'M') { ?>
+            <span class="label label-bare"><?php echo __('Cc Collaborator'); ?></span>
+        <?php   } ?>
         </span>
         </div>
 <?php
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index 78912dfb5..1056cdab8 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -45,7 +45,7 @@ if ($_POST)
     <tbody>
         <tr>
             <th colspan="2">
-                <em><strong><?php echo __('User Information'); ?></strong>: </em>
+                <em><strong><?php echo __('Recipient Information'); ?></strong>: </em>
                 <div class="error"><?php echo $errors['user']; ?></div>
             </th>
         </tr>
@@ -116,6 +116,64 @@ if ($_POST)
         </tr>
         <?php
         } ?>
+          <tr>
+            <td>
+              <table border="0">
+                <tr class="no_border">
+                  <td width="120">
+                      <label><strong><?php echo __('Collaborators'); ?>:</strong></label>
+                  </td>
+                  <td>
+                      <input type='checkbox' value='1' name="emailcollab"
+                      id="emailcollab"
+                          <?php echo ((!$info['emailcollab'] && !$errors) || isset($info['emailcollab']))?'checked="checked"':''; ?>
+
+                          >
+                      <?php
+                     ?>
+                  </td>
+                </tr>
+                <tr class="no_border" id="ccRow">
+                  <td width="160"><?php echo __('Cc'); ?>:</td>
+                  <td>
+                      <select name="ccs[]" id="cc_users_open" multiple="multiple"
+                          data-placeholder="<?php echo __('Select Contacts'); ?>">
+                          <option value=""></option>
+                          <?php
+                          $users = User::objects();
+                          foreach ($users as $u) {
+                            if($user && $u->id != $user->getId()) {
+                          ?>
+                          <option value="<?php echo $u->id; ?>"
+                              ><?php echo $u->getName(); ?>
+                          </option>
+                              <?php } } ?>
+                      </select>
+                      <br/><span class="error"><?php echo $errors['ccs']; ?></span>
+                  </td>
+                </tr>
+                <tr class="no_border" id="bccRow">
+                  <td width="160"><?php echo __('Bcc'); ?>:</td>
+                  <td>
+                      <select name="bccs[]" id="bcc_users_open" multiple="multiple"
+                          data-placeholder="<?php echo __('Select Contacts'); ?>">
+                          <option value=""></option>
+                          <?php
+                          $users = User::objects();
+                          foreach ($users as $u) {
+                            if($user && $u->id != $user->getId()) {
+                          ?>
+                          <option value="<?php echo $u->id; ?>"
+                              ><?php echo $u->getName(); ?>
+                          </option>
+                              <?php } } ?>
+                      </select>
+                      <br/><span class="error"><?php echo $errors['ccs']; ?></span>
+                  </td>
+                </tr>
+              </table>
+          </td>
+        </tr>
     </tbody>
     <tbody>
         <tr>
@@ -449,5 +507,47 @@ $(function() {
     <?php
     } ?>
 });
-</script>
 
+$(function() {
+    $('a#editorg').click( function(e) {
+        e.preventDefault();
+        $('div#org-profile').hide();
+        $('div#org-form').fadeIn();
+        return false;
+     });
+
+    $(document).on('click', 'form.org input.cancel', function (e) {
+        e.preventDefault();
+        $('div#org-form').hide();
+        $('div#org-profile').fadeIn();
+        return false;
+    });
+    $("#cc_users_open").select2({width: '300px'});
+    $("#bcc_users_open").select2({width: '300px'});
+});
+
+$(document).ready(function () {
+    $('#emailcollab').on('change', function(){
+      var value = $("#cc_users_open").val();
+        if ($(this).prop('checked')) {
+            $('#ccRow').show();
+            $('#bccRow').show();
+        }
+        else {
+            $('#ccRow').hide();
+            $('#bccRow').hide();
+        }
+    });
+});
+
+$("form").submit(function(event) {
+  var value = $("#emailcollab").val();
+  if ($("#emailcollab").prop('checked')) {
+    //do nothing
+  }
+  else {
+    $("#cc_users_open").val(null).change();
+    $("#bcc_users_open").val(null).change();
+  }
+});
+</script>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 675e99642..b216b3f8b 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -179,6 +179,19 @@ if($ticket->isOverdue())
                 <?php
                 } ?>
 
+                <li>
+
+                    <?php
+                    $recipients = __(' Manage Collaborators');
+
+                    echo sprintf('<a class="collaborators manage-collaborators"
+                            href="#thread/%d/collaborators"><i class="icon-group"></i>%s</a>',
+                            $ticket->getThreadId(),
+                            $recipients);
+                   ?>
+                </li>
+
+
 <?php           if ($thisstaff->hasPerm(Email::PERM_BANLIST)) {
                      if(!$emailBanned) {?>
                         <li><a class="confirm-action" id="ticket-banemail"
@@ -301,6 +314,21 @@ if($ticket->isOverdue())
 <?php   } ?>
                                 </ul>
                             </div>
+                            <?php
+                            if ($role->hasPerm(TicketModel::PERM_EDIT)) {
+                            $numCollaborators = $ticket->getThread()->getNumCollaborators();
+                             if ($ticket->getThread()->getNumCollaborators())
+                                $recipients = sprintf(__('%d'),
+                                        $numCollaborators);
+                            else
+                              $recipients = 0;
+
+                             echo sprintf('<span><a class="collaborators preview"
+                                    href="#thread/%d/collaborators"><span id="t%d-recipients"><i class="icon-group"></i> (%s)</span></a></span>',
+                                    $ticket->getThreadId(),
+                                    $ticket->getThreadId(),
+                                    $recipients);
+                             }?>
 <?php                   } # end if ($user) ?>
                     </td>
                 </tr>
@@ -554,6 +582,30 @@ if ($errors['err'] && isset($_POST['a'])) {
             <?php
             }?>
            <tbody id="to_sec">
+           <tr>
+               <td width="120">
+                   <label><strong><?php echo __('From'); ?>:</strong></label>
+               </td>
+               <td>
+                   <?php
+                   # XXX: Add user-to-name and user-to-email HTML ID#s
+                   $addresses = Email::getAddresses();
+                   ?>
+                   <select id="from_name" name="from_name">
+                     <?php
+                     $sql=' SELECT email_id, email, name, smtp_host '
+                         .' FROM '.EMAIL_TABLE.' WHERE smtp_active = 1';
+                     if(($res=db_query($sql)) && db_num_rows($res)) {
+                         while (list($id, $email, $name, $host) = db_fetch_row($res)){
+                             $email=$name?"$name &lt;$email&gt;":$email;
+                             ?>
+                             <option value="<?php echo $id; ?>"<?php echo ($dept->getEmail()->email_id==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option>
+                         <?php
+                         }
+                     } ?>
+                   </select>
+               </td>
+           </tr>
             <tr>
                 <td width="120">
                     <label><strong><?php echo __('To'); ?>:</strong></label>
@@ -590,20 +642,62 @@ if ($errors['err'] && isset($_POST['a'])) {
                         style="display:<?php echo $ticket->getThread()->getNumCollaborators() ? 'inline-block': 'none'; ?>;"
                         >
                     <?php
-                    $recipients = __('Add Recipients');
-                    if ($ticket->getThread()->getNumCollaborators())
-                        $recipients = sprintf(__('Recipients (%d of %d)'),
-                                $ticket->getThread()->getNumActiveCollaborators(),
-                                $ticket->getThread()->getNumCollaborators());
-
-                    echo sprintf('<span><a class="collaborators preview"
-                            href="#thread/%d/collaborators"><span id="t%d-recipients">%s</span></a></span>',
-                            $ticket->getThreadId(),
-                            $ticket->getThreadId(),
-                            $recipients);
                    ?>
                 </td>
              </tr>
+             <?php $collaborators = $ticket->getThread()->getCollaborators();
+             $cc_cids = array();
+             $bcc_cids = array();
+             foreach ($collaborators as $c) {
+               if ($c->flags & Collaborator::FLAG_CC && $c->flags & Collaborator::FLAG_ACTIVE)
+                  $cc_cids[] = $c->user_id;
+                elseif (!($c->flags & Collaborator::FLAG_CC) && $c->flags & Collaborator::FLAG_ACTIVE) {
+                  $bcc_cids[] = $c->user_id;
+                }
+             }
+            ?>
+             <tr>
+                 <td width="160"><b><?php echo __('Cc'); ?>:</b></td>
+                 <td>
+                     <select name="ccs[]" id="cc_users" multiple="multiple"
+                         data-placeholder="<?php echo __('Select Contacts'); ?>">
+                         <option value=""></option>
+                         <option value="NEW">&mdash; <?php echo __('Add New');?> &mdash;</option>
+                         <?php
+                         $users = User::objects();
+                         foreach ($users as $u) {
+                           if($u->id != $ticket->user_id && !in_array($u->getId(), $bcc_cids)) {
+                         ?>
+                         <option value="<?php echo $u->id; ?>" <?php
+                            if (in_array($u->getId(), $cc_cids))
+                              echo 'selected="selected"'; ?>><?php echo $u->getName(); ?>
+                         </option>
+                             <?php } } ?>
+                     </select>
+                     <br/><span class="error"><?php echo $errors['ccs']; ?></span>
+                 </td>
+             </tr>
+             <tr>
+               <td width="160"><b><?php echo __('Bcc'); ?>:</b></td>
+               <td>
+                   <select name="bccs[]" id="bcc_users" multiple="multiple"
+                       data-placeholder="<?php echo __('Select Contacts'); ?>">
+                       <option value=""></option>
+                       <option value="NEW">&mdash; <?php echo __('Add New');?> &mdash;</option>
+                       <?php
+                       $users = User::objects();
+                       foreach ($users as $u) {
+                         if($u->id != $ticket->user_id && !in_array($u->getId(), $cc_cids)) {
+                       ?>
+                       <option value="<?php echo $u->id; ?>" <?php
+                           if (in_array($u->getId(), $bcc_cids))
+                           echo 'selected="selected"'; ?>><?php echo $u->getName(); ?>
+                       </option>
+                           <?php } } ?>
+                   </select>
+                   <br/><span class="error"><?php echo $errors['bccs']; ?></span>
+               </td>
+             </tr>
             </tbody>
             <?php
             } ?>
@@ -967,4 +1061,78 @@ $(function() {
     });
 
 });
+
+$(function() {
+    $("#cc_users").select2({width: '350px'});
+    $("#bcc_users").select2({width: '350px'});
+});
+
+$(function() {
+   $('#cc_users').on("select2:select", function(e) {
+     var el = $(this);
+     var tid = <?php echo $ticket->getThreadId(); ?>;
+
+    if(el.val().includes("NEW")) {
+      $("li[title='— Add New —']").remove();
+      var url = 'ajax.php/thread/' + tid + '/add-collaborator' ;
+       $.userLookup(url, function(user) {
+         e.preventDefault();
+          if($('.dialog#confirm-action').length) {
+              $('.dialog#confirm-action #action').val('addcc');
+              $('#confirm-form').append('<input type=hidden name=user_id value='+user.id+' />');
+              $('#overlay').show();
+          }
+       });
+          var arr = el.val();
+          var removeStr = "NEW";
+
+          arr.splice($.inArray(removeStr, arr),1);
+          $(this).val(arr);
+     }
+  });
+
+  $('#bcc_users').on("select2:select", function(e) {
+      var el = $(this);
+      var tid = <?php echo $ticket->getThreadId(); ?>;
+
+      if(el.val().includes("NEW")) {
+        $("li[title='— Add New —']").remove();
+        var url = 'ajax.php/thread/' + tid + '/add-collaborator' ;
+         $.userLookup(url, function(user) {
+            e.preventDefault();
+            if($('.dialog#confirm-action').length) {
+                $('.dialog#confirm-action #action').val('addbcc');
+                $('#confirm-form').append('<input type=hidden name=user_id value='+user.id+' />');
+                $('#overlay').show();
+            }
+         });
+         var arr = el.val();
+         var removeStr = "NEW";
+
+         arr.splice($.inArray(removeStr, arr),1);
+         $(this).val(arr);
+      }
+  });
+
+  $('#cc_users').on("select2:unselecting", function(e) {
+      var confirmation = confirm(__("Are you sure you want to remove the collaborator from receiving this reply?"));
+      if (confirmation == false) {
+        $('#cc_users').on("select2:opening", function(e) {
+          return false;
+        });
+        return false;
+      }
+
+ });
+
+ $('#bcc_users').on("select2:unselecting", function(e) {
+     var confirmation = confirm(__("Are you sure you want to remove the collaborator from receiving this reply?"));
+     if (confirmation == false) {
+         $('#bcc_users').on("select2:opening", function(e) {
+           return false;
+         });
+         return false;
+       }
+ });
+});
 </script>
diff --git a/include/upgrader/streams/core/98ad7d55-b2ce8ba7.patch.sql b/include/upgrader/streams/core/98ad7d55-b2ce8ba7.patch.sql
new file mode 100644
index 000000000..3c10b87fd
--- /dev/null
+++ b/include/upgrader/streams/core/98ad7d55-b2ce8ba7.patch.sql
@@ -0,0 +1,31 @@
+/**
+ * @version v1.11.0
+ * @title Add recipients field/collaborator flags
+ * @signature b2ce8ba794a40ed5380d7cdf30bca233
+ *
+ * This patch adds a new field called recipients to the thread entry table
+ * allowing agents to see a list of recipients for any thread entry where
+ * an email was involved (agent or user generated)
+ *
+ * It also adds a flags field to the thread_collaborator table which
+ * tracks whether a collaborator is a CC or BCC collaborator as well as
+ * storing whether or not the collaborator is active. As a result, we can
+ * remove the isactive field
+ */
+
+ ALTER TABLE `%TABLE_PREFIX%thread_entry`
+  ADD `recipients` text AFTER `ip_address`;
+
+ ALTER TABLE `%TABLE_PREFIX%thread_collaborator`
+  ADD `flags` int(10) unsigned NOT NULL DEFAULT 1 AFTER `id`;
+
+ UPDATE `%TABLE_PREFIX%thread_collaborator`
+  SET `flags` = `isactive` + 2;
+
+  ALTER TABLE `%TABLE_PREFIX%thread_collaborator`
+   DROP COLUMN `isactive`;
+
+ -- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = 'b2ce8ba794a40ed5380d7cdf30bca233'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 13d4c9643..d747a7a08 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1310,6 +1310,10 @@ table.fixed > tr > td + td:not([width]) {
     width: auto;
 }
 
+tr.no_border > td, td.no_border{
+  border-style:hidden;
+}
+
 td.multi-line {
     vertical-align:top;
     padding-top: 0.4em;
@@ -1584,6 +1588,27 @@ img.avatar {
     position: relative;
 }
 
+.thread-entry.bccmessage .header {
+    background:#DDFDAC;
+}
+
+.thread-entry.avatar.bccmessage .header:before {
+    top: 7px;
+    left: -8px;
+    right: initial;
+    border-left: none;
+    border-right: 8px solid #CCC;
+}
+
+.thread-entry.avatar.bccmessage .header:after {
+    top: 7px;
+    left: -8px;
+    right: initial;
+    border-left: none;
+    border-right: 7px solid #DDFDAC;
+    margin-left: 1px;
+}
+
 .thread-entry.message .header {
     background:#C3D9FF;
 }
diff --git a/scp/tasks.php b/scp/tasks.php
index 327de9a26..23f83dba0 100644
--- a/scp/tasks.php
+++ b/scp/tasks.php
@@ -41,7 +41,6 @@ $reply_attachments_form = new SimpleForm(array(
 
 //At this stage we know the access status. we can process the post.
 if($_POST && !$errors):
-
     if ($task) {
         //More coffee please.
         $errors=array();
@@ -110,6 +109,24 @@ if($_POST && !$errors):
         default:
             $errors['err']=__('Unknown action');
         endswitch;
+
+        switch(strtolower($_POST['do'])):
+          case 'addcc':
+              $errors = array();
+              if (!$role->hasPerm(TicketModel::PERM_EDIT)) {
+                  $errors['err']=__('Permission Denied. You are not allowed to add collaborators');
+              } elseif (!$_POST['user_id'] || !($user=User::lookup($_POST['user_id']))) {
+                  $errors['err'] = __('Unknown user selected');
+              } elseif ($c2 = $task->addCollaborator($user, array('isactive'=>1), $errors)) {
+                  $c2->setFlag(Collaborator::FLAG_CC, true);
+                  $c2->save();
+                  $msg = sprintf(__('Collaborator %s added'),
+                      Format::htmlchars($user->getName()));
+              }
+              else
+                $errors['err'] = sprintf('%s %s', __('Unable to add collaborator.'), __('Please try again!'));
+              break;
+      endswitch;
     }
     if(!$errors)
         $thisstaff->resetStats(); //We'll need to reflect any changes just made!
diff --git a/scp/tickets.php b/scp/tickets.php
index e2fe93c79..a79354e61 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -366,6 +366,34 @@ if($_POST && !$errors):
                         $errors['err'] = sprintf('%s %s', __('Unable to change ticket ownership.'), __('Please try again!'));
                     }
                     break;
+                case 'addcc':
+                    if (!$role->hasPerm(TicketModel::PERM_EDIT)) {
+                        $errors['err']=__('Permission Denied. You are not allowed to add collaborators');
+                    } elseif (!$_POST['user_id'] || !($user=User::lookup($_POST['user_id']))) {
+                        $errors['err'] = __('Unknown user selected');
+                    } elseif ($c2 = $ticket->addCollaborator($user, array('isactive'=>1), $errors)) {
+                        $c2->setFlag(Collaborator::FLAG_CC, true);
+                        $c2->save();
+                        $msg = sprintf(__('Collaborator %s added'),
+                            Format::htmlchars($user->getName()));
+                    }
+                    else {
+                      $errors['err'] = sprintf('%s %s', __('Unable to add collaborator.'), __('Please try again!'));
+                    }
+                    break;
+                  case 'addbcc':
+                      if (!$role->hasPerm(TicketModel::PERM_EDIT)) {
+                          $errors['err']=__('Permission Denied. You are not allowed to add collaborators');
+                      } elseif (!$_POST['user_id'] || !($user=User::lookup($_POST['user_id']))) {
+                          $errors['err'] = __('Unknown user selected');
+                      } elseif ($c2 = $ticket->addCollaborator($user, array('isactive'=>1), $errors)) {
+                          $msg = sprintf(__('Collaborator %s added'),
+                              Format::htmlchars($user->getName()));
+                      }
+                      else {
+                        $errors['err'] = sprintf('%s %s', __('Unable to add collaborator.'), __('Please try again!'));
+                      }
+                      break;
                 default:
                     $errors['err']=__('You must select action to perform');
             endswitch;
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 1893c482e..bd9651c5f 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -637,6 +637,7 @@ CREATE TABLE `%TABLE_PREFIX%thread_entry` (
   `body` text NOT NULL,
   `format` varchar(16) NOT NULL default 'html',
   `ip_address` varchar(64) NOT NULL default '',
+  `recipients` text,
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
   PRIMARY KEY  (`id`),
@@ -761,7 +762,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_priority` (
 
 CREATE TABLE `%TABLE_PREFIX%thread_collaborator` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `isactive` tinyint(1) NOT NULL DEFAULT '1',
+  `flags` int(10) unsigned NOT NULL DEFAULT '1',
   `thread_id` int(11) unsigned NOT NULL DEFAULT '0',
   `user_id` int(11) unsigned NOT NULL DEFAULT '0',
   -- M => (message) clients, N => (note) 3rd-Party, R => (reply) external authority
-- 
GitLab