Skip to content
Snippets Groups Projects
class.ticket.php 144 KiB
Newer Older
  • Learn to ignore specific revisions
  •                 return new FormattedDate($due);
                break;
            case 'close_date':
                if ($this->isClosed())
                    return new FormattedDate($this->getCloseDate());
                break;
            case 'last_update':
    
                return new FormattedDate($this->lastupdate);
    
            case 'user':
                return $this->getOwner();
            default:
    
                if ($a = $this->getAnswer($tag))
    
                    // The answer object is retrieved here which will
                    // automatically invoke the toString() method when the
                    // answer is coerced into text
    
        static function getVarScope() {
            $base = array(
    
                'assigned' => __('Assigned Agent / Team'),
    
                'close_date' => array(
    
                    'class' => 'FormattedDate', 'desc' => __('Date Closed'),
    
                ),
                'create_date' => array(
    
                    'class' => 'FormattedDate', 'desc' => __('Date Created'),
    
                    'class' => 'Dept', 'desc' => __('Department'),
    
                'due_date' => array(
    
                    'class' => 'FormattedDate', 'desc' => __('Due Date'),
    
                'email' => __('Default email address of ticket owner'),
    
                'name' => array(
                    'class' => 'PersonsName', 'desc' => __('Name of ticket owner'),
                ),
    
                'number' => __('Ticket Number'),
    
                'phone' => __('Phone number of ticket owner'),
    
                'priority' => array(
    
                    'class' => 'Priority', 'desc' => __('Priority'),
    
                ),
                'recipients' => array(
    
                    'class' => 'UserList', 'desc' => __('List of all recipient names'),
    
                'source' => __('Source'),
    
                'status' => array(
    
                    'class' => 'TicketStatus', 'desc' => __('Status'),
    
                ),
                'staff' => array(
                    'class' => 'Staff', 'desc' => __('Assigned/closing agent'),
                ),
                'subject' => 'Subject',
                'team' => array(
                    'class' => 'Team', 'desc' => __('Assigned/closing team'),
                ),
                'thread' => array(
    
                    'class' => 'TicketThread', 'desc' => __('Ticket Thread'),
    
                    'class' => 'Topic', 'desc' => __('Help Topic'),
    
                ),
                // XXX: Isn't lastreponse and lastmessage more useful
                'last_update' => array(
                    'class' => 'FormattedDate', 'desc' => __('Time of last update'),
    
                    'class' => 'User', 'desc' => __('Ticket Owner'),
    
            $extra = VariableReplacer::compileFormScope(TicketForm::getInstance());
            return $base + $extra;
    
        // Searchable interface
        static function getSearchableFields() {
            $base = array(
                'number' => new TextboxField(array(
                    'label' => __('Ticket Number')
                )),
    
                'created' => new DatetimeField(array(
                    'label' => __('Create Date'),
    
                    'configuration' => array('fromdb' => true),
    
                'duedate' => new DatetimeField(array(
    
                    'label' => __('Due Date'),
    
                    'configuration' => array('fromdb' => true),
    
                'est_duedate' => new DatetimeField(array(
                    'label' => __('SLA Due Date'),
                    'configuration' => array('fromdb' => true),
                )),
    
                'reopened' => new DatetimeField(array(
                    'label' => __('Reopen Date'),
    
                    'configuration' => array('fromdb' => true),
    
                )),
                'closed' => new DatetimeField(array(
                    'label' => __('Close Date'),
    
                    'configuration' => array('fromdb' => true),
    
                )),
                'lastupdate' => new DatetimeField(array(
                    'label' => __('Last Update'),
    
                    'configuration' => array('fromdb' => true),
    
                )),
                'assignee' => new AssigneeChoiceField(array(
                    'label' => __('Assignee'),
                )),
    
                'staff_id' => new AgentSelectionField(array(
                    'label' => __('Assigned Staff'),
                )),
                'team_id' => new TeamSelectionField(array(
                    'label' => __('Assigned Team'),
                )),
                'dept_id' => new DepartmentChoiceField(array(
                    'label' => __('Department'),
                )),
                'topic_id' => new HelpTopicChoiceField(array(
                    'label' => __('Help Topic'),
                )),
                'source' => new TicketSourceChoiceField(array(
                    'label' => __('Ticket Source'),
                )),
                'isoverdue' => new BooleanField(array(
                    'label' => __('Overdue'),
    
                    'descsearchmethods' => array(
                        'set' => '%s',
                        'nset' => 'Not %s'
                        ),
    
                )),
                'isanswered' => new BooleanField(array(
                    'label' => __('Answered'),
    
                    'descsearchmethods' => array(
                        'set' => '%s',
                        'nset' => 'Not %s'
                        ),
                )),
                'isassigned' => new AssignedField(array(
                            'label' => __('Assigned'),
    
                )),
                'ip_address' => new TextboxField(array(
                    'label' => __('IP Address'),
                    'configuration' => array('validator' => 'ip'),
                )),
    
            );
            $tform = TicketForm::getInstance();
            foreach ($tform->getFields() as $F) {
                $fname = $F->get('name') ?: ('field_'.$F->get('id'));
                if (!$F->hasData() || $F->isPresentationOnly())
                    continue;
                if (!$F->isStorable())
                    $base[$fname] = $F;
                else
                    $base["cdata__{$fname}"] = $F;
            }
            return $base;
        }
    
    
        static function supportsCustomData() {
            return true;
        }
    
    
        //Replace base variables.
        function replaceVars($input, $vars = array()) {
            global $ost;
    
    
            $recipients = $this->getRecipients(true);
    
    
            $vars = array_merge($vars, array('ticket' => $this));
            return $ost->replaceTemplateVariables($input, $vars);
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function markUnAnswered() {
            return (!$this->isAnswered() || $this->setAnsweredState(0));
        }
    
        function markAnswered() {
            return ($this->isAnswered() || $this->setAnsweredState(1));
        }
    
        function markOverdue($whine=true) {
            global $cfg;
    
            if ($this->isOverdue())
    
    Jared Hancock's avatar
    Jared Hancock committed
                return true;
    
    
            $this->isoverdue = 1;
            if (!$this->save())
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    
            $this->logEvent('overdue');
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->onOverdue($whine);
    
    Jared Hancock's avatar
    Jared Hancock committed
            return true;
        }
    
    
        function clearOverdue($save=true) {
    
            if (!$this->isOverdue())
    
    Peter Rotich's avatar
    Peter Rotich committed
                return true;
    
    
            //NOTE: Previously logged overdue event is NOT annuled.
    
    
            $this->isoverdue = 0;
    
            // clear due date if it's in the past
            if ($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime())
                $this->duedate = null;
    
            // Clear SLA if est. due date is in the past
            if ($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime())
                $this->sla = null;
    
            return $save ? $this->save() : true;
    
        //Dept Transfer...with alert.. done by staff
    
    Peter Rotich's avatar
    Peter Rotich committed
        function transfer(TransferForm $form, &$errors, $alert=true) {
            global $thisstaff, $cfg;
    
    Peter Rotich's avatar
    Peter Rotich committed
            // Check if staff can do the transfer
            if (!$this->checkStaffPerm($thisstaff, Ticket::PERM_TRANSFER))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            $cdept = $this->getDept(); // Current department
            $dept = $form->getDept(); // Target department
            if (!$dept || !($dept instanceof Dept))
                $errors['dept'] = __('Department selection required');
            elseif ($dept->getid() == $this->getDeptId())
                $errors['dept'] = sprintf(
                        __('%s already in the department'), __('Ticket'));
            else {
                $this->dept_id = $dept->getId();
    
                // Make sure the new department allows assignment to the
                // currently assigned agent (if any)
                if ($this->isAssigned()
                    && ($staff=$this->getStaff())
                    && $dept->assignMembersOnly()
                    && !$dept->isMember($staff)
                ) {
                    $this->staff_id = 0;
                }
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($errors || !$this->save(true))
    
                return false;
    
            if ($this->isClosed())
                $this->reopen();
    
            if (!$this->getSLAId() || $this->getSLA()->isTransient())
    
    Peter Rotich's avatar
    Peter Rotich committed
                if (($slaId=$this->getDept()->getSLAId()))
                    $this->selectSLAId($slaId);
    
    Peter Rotich's avatar
    Peter Rotich committed
            // Log transfer event
            $this->logEvent('transferred');
    
    Peter Rotich's avatar
    Peter Rotich committed
            // Post internal note if any
    
    Peter Rotich's avatar
    Peter Rotich committed
            $note = null;
            $comments = $form->getField('comments')->getClean();
            if ($comments) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                $title = sprintf(__('%1$s transferred from %2$s to %3$s'),
                        __('Ticket'),
                       $cdept->getName(),
                        $dept->getName());
    
    Peter Rotich's avatar
    Peter Rotich committed
                $_errors = array();
                $note = $this->postNote(
    
    Peter Rotich's avatar
    Peter Rotich committed
                        array('note' => $comments, 'title' => $title),
    
    Peter Rotich's avatar
    Peter Rotich committed
                        $_errors, $thisstaff, false);
    
            if ($form->refer() && $cdept)
                $this->thread->refer($cdept);
    
    
            //Send out alerts if enabled AND requested
    
    Peter Rotich's avatar
    Peter Rotich committed
            if (!$alert || !$cfg->alertONTransfer())
    
                return true; //no alerts!!
    
             if (($email = $dept->getAlertEmail())
    
                 && ($tpl = $dept->getTemplate())
                 && ($msg=$tpl->getTransferAlertMsgTemplate())
             ) {
    
                $msg = $this->replaceVars($msg->asArray(),
    
    Peter Rotich's avatar
    Peter Rotich committed
                    array('comments' => $note, 'staff' => $thisstaff));
    
                // Recipients
                $recipients = array();
                // Assigned staff or team... if any
    
    Jared Hancock's avatar
    Jared Hancock committed
                if($this->isAssigned() && $cfg->alertAssignedONTransfer()) {
                    if($this->getStaffId())
    
                        $recipients[] = $this->getStaff();
                    elseif ($this->getTeamId()
                        && ($team=$this->getTeam())
                        && ($members=$team->getMembers())
                    ) {
    
                        $recipients = array_merge($recipients, $members);
    
                    }
                }
                elseif ($cfg->alertDeptMembersONTransfer() && !$this->isAssigned()) {
                    // Only alerts dept members if the ticket is NOT assigned.
    
                    foreach ($dept->getMembersForAlerts() as $M)
                        $recipients[] = $M;
    
                // Always alert dept manager??
                if ($cfg->alertDeptManagerONTransfer()
                    && $dept
                    && ($manager=$dept->getManager())
                ) {
                    $recipients[] = $manager;
                }
    
    Peter Rotich's avatar
    Peter Rotich committed
                $sentlist = $options = array();
    
                if ($note) {
    
                    $options += array('thread'=>$note);
    
                foreach ($recipients as $k=>$staff) {
                    if (!is_object($staff)
                        || !$staff->isAvailable()
                        || in_array($staff->getEmail(), $sentlist)
                    ) {
                        continue;
                    }
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
    
                    $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
    Peter Rotich's avatar
    Peter Rotich committed
        function claim(ClaimForm $form, &$errors) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            global $thisstaff;
    
            $dept = $this->getDept();
    
    Peter Rotich's avatar
    Peter Rotich committed
            $assignee = $form->getAssignee();
            if (!($assignee instanceof Staff)
                    || !$thisstaff
                    || $thisstaff->getId() != $assignee->getId()) {
                $errors['err'] = __('Unknown assignee');
            } elseif (!$assignee->isAvailable()) {
                $errors['err'] = __('Agent is unavailable for assignment');
            } elseif ($dept->assignMembersOnly() && !$dept->isMember($assignee)) {
                $errors['err'] = __('Permission denied');
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($errors)
    
    Peter Rotich's avatar
    Peter Rotich committed
                return false;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->assignToStaff($assignee, $form->getComments(), false);
    
    Jared Hancock's avatar
    Jared Hancock committed
        function assignToStaff($staff, $note, $alert=true) {
    
    
            if(!is_object($staff) && !($staff = Staff::lookup($staff)))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
            if (!$staff->isAvailable() || !$this->setStaffId($staff->getId()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    
            $this->onAssign($staff, $note, $alert);
    
    
            global $thisstaff;
            $data = array();
    
            if ($thisstaff && $staff->getId() == $thisstaff->getId())
    
                $data['claim'] = true;
            else
                $data['staff'] = $staff->getId();
    
            $this->logEvent('assigned', $data);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            return true;
        }
    
        function assignToTeam($team, $note, $alert=true) {
    
    
            if(!is_object($team) && !($team = Team::lookup($team)))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    
            if (!$team->isActive() || !$this->setTeamId($team->getId()))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
            //Clear - staff if it's a closed ticket
            //  staff_id is overloaded -> assigned to & closed by.
    
            if ($this->isClosed())
    
    Jared Hancock's avatar
    Jared Hancock committed
                $this->setStaffId(0);
    
    
            $this->onAssign($team, $note, $alert);
    
            $this->logEvent('assigned', array('team' => $team->getId()));
    
    Peter Rotich's avatar
    Peter Rotich committed
        function assign(AssignmentForm $form, &$errors, $alert=true) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $thisstaff;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $evd = array();
    
    Peter Rotich's avatar
    Peter Rotich committed
            $assignee = $form->getAssignee();
            if ($assignee instanceof Staff) {
    
                $dept = $this->getDept();
    
    Peter Rotich's avatar
    Peter Rotich committed
                if ($this->getStaffId() == $assignee->getId()) {
                    $errors['assignee'] = sprintf(__('%s already assigned to %s'),
                            __('Ticket'),
                            __('the agent')
                            );
                } elseif(!$assignee->isAvailable()) {
                    $errors['assignee'] = __('Agent is unavailable for assignment');
    
                } elseif ($dept->assignMembersOnly() && !$dept->isMember($assignee)) {
                    $errors['err'] = __('Permission denied');
    
    Peter Rotich's avatar
    Peter Rotich committed
                } else {
    
                    $refer = $this->staff ?: null;
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $this->staff_id = $assignee->getId();
    
                    if ($thisstaff && $thisstaff->getId() == $assignee->getId()) {
                        $alert = false;
    
    Peter Rotich's avatar
    Peter Rotich committed
                        $evd['claim'] = true;
    
                        $evd['staff'] = array($assignee->getId(), (string) $assignee->getName()->getOriginal());
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
            } elseif ($assignee instanceof Team) {
                if ($this->getTeamId() == $assignee->getId()) {
                    $errors['assignee'] = sprintf(__('%s already assigned to %s'),
                            __('Ticket'),
                            __('the team')
                            );
                } else {
    
                    $refer = $this->team ?: null;
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $this->team_id = $assignee->getId();
                    $evd = array('team' => $assignee->getId());
                }
            } else {
                $errors['assignee'] = __('Unknown assignee');
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($errors || !$this->save(true))
                return false;
    
            $this->logEvent('assigned', $evd);
    
    
            $this->onAssign($assignee, $form->getComments(), $alert);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            if ($refer && $form->refer())
                $this->thread->refer($refer);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            return true;
    
        // Unassign primary assignee
    
    Jared Hancock's avatar
    Jared Hancock committed
        function unassign() {
    
            // We can't release what is not assigned buddy!
            if (!$this->isAssigned())
    
    Jared Hancock's avatar
    Jared Hancock committed
                return true;
    
    
            // We can only unassigned OPEN tickets.
            if ($this->isClosed())
    
    Peter Rotich's avatar
    Peter Rotich committed
                return false;
    
            // Unassign staff (if any)
            if ($this->getStaffId() && !$this->setStaffId(0))
    
    Peter Rotich's avatar
    Peter Rotich committed
                return false;
    
    
            // Unassign team (if any)
            if ($this->getTeamId() && !$this->setTeamId(0))
    
    Peter Rotich's avatar
    Peter Rotich committed
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            return true;
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        function release() {
            return $this->unassign();
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function refer(ReferralForm $form, &$errors, $alert=true) {
            global $thisstaff;
    
            $evd = array();
    
            $referee = $form->getReferee();
    
    Peter Rotich's avatar
    Peter Rotich committed
            switch (true) {
    
            case $referee instanceof Staff:
    
    Peter Rotich's avatar
    Peter Rotich committed
                $dept = $this->getDept();
    
                if ($this->getStaffId() == $referee->getId()) {
                    $errors['agent'] = sprintf(__('%s is assigned to %s'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                            __('Ticket'),
                            __('the agent')
                            );
    
                } elseif(!$referee->isAvailable()) {
                    $errors['agent'] = sprintf(__('Agent is unavailable for %s'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                            __('referral'));
                } else {
    
                    $evd['staff'] = array($referee->getId(), (string) $referee->getName()->getOriginal());
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
                break;
    
            case $referee instanceof Team:
                if ($this->getTeamId() == $referee->getId()) {
                    $errors['team'] = sprintf(__('%s is assigned to %s'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                            __('Ticket'),
                            __('the team')
                            );
                } else {
                    //TODO::
    
                    $evd = array('team' => $referee->getId());
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
                break;
    
            case $referee instanceof Dept:
    
                if ($this->getDeptId() == $referee->getId()) {
    
                    $errors['dept'] = sprintf(__('%s is already in %s'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                            __('Ticket'),
                            __('the department')
                            );
                } else {
                    //TODO::
    
                    $evd = array('dept' => $referee->getId());
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
                break;
            default:
                $errors['target'] = __('Unknown referral');
            }
    
    
            if (!$errors && !$this->thread->refer($referee))
                $errors['err'] = __('Unable to refer ticket');
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            if ($errors)
                return false;
    
            $this->logEvent('referred', $evd);
    
            return true;
        }
    
    
        function systemReferral($emails) {
    
            if (!$this->thread)
                return;
    
            foreach ($emails as $id) {
                if ($id != $this->email_id
                        && ($email=Email::lookup($id))
                        && $this->getDeptId() != $email->getDeptId()
                        && ($dept=Dept::lookup($email->getDeptId()))
                        && $this->thread->refer($dept)
                        )
                    $this->logEvent('referred',
                                array('dept' => $dept->getId()));
            }
    
        }
    
    
        //Change ownership
        function changeOwner($user) {
            global $thisstaff;
    
            if (!$user
    
                || ($user->getId() == $this->getOwnerId())
                || !($this->checkStaffPerm($thisstaff,
    
                    Ticket::PERM_EDIT))
    
            $this->user_id = $user->getId();
            if (!$this->save())
    
            unset($this->user);
    
            $this->collaborators = null;
            $this->recipients = null;
    
    
            // Remove the new owner from list of collaborators
    
            $c = Collaborator::lookup(array(
    
                'user_id' => $user->getId(),
                'thread_id' => $this->getThreadId()
            ));
            if ($c)
                $c->delete();
    
            $this->logEvent('edited', array('owner' => $user->getId()));
    
        // Insert message from client
    
        function postMessage($vars, $origin='', $alerts=true) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            global $cfg;
    
            if ($origin)
                $vars['origin'] = $origin;
    
            if (isset($vars['ip']))
    
                $vars['ip_address'] = $vars['ip'];
    
            elseif (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
    
                $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
    
    
            $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(), $errors);
    
    Peter Rotich's avatar
    Peter Rotich committed
              $addresses = array();
    
              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)))
    
            $this->setLastMessage($message);
    
            // Add email recipients as collaborators...
    
                && (strtolower($origin) != 'email' || ($cfg && $cfg->addCollabsViaEmail()))
                //Only add if we have a matched local address
                && $vars['to-email-id']
            ) {
    
                //New collaborators added by other collaborators are disable --
                // requires staff approval.
                $info = array(
    
                    'isactive' => ($message->getUserId() == $this->getUserId())? 1: 0);
    
                $collabs = array();
                foreach ($vars['recipients'] as $recipient) {
    
                    // Skip virtual delivered-to addresses
                    if (strcasecmp($recipient['source'], 'delivered-to') === 0)
                        continue;
    
    
                    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?
    
                    $this->logEvent('collab', array('add' => $collabs), $message->user);
    
            // Do not auto-respond to bounces and other auto-replies
    
            $autorespond = isset($vars['mailflags'])
    
                    ? !$vars['mailflags']['bounce'] && !$vars['mailflags']['auto-reply']
    
            $reopen = $autorespond; // Do not reopen bounces
    
            if ($autorespond && $message->isBounceOrAutoReply())
    
                $autorespond = $reopen= false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            elseif ($autorespond && isset($vars['autorespond']))
                $autorespond = $vars['autorespond'];
    
            $this->onMessage($message, ($autorespond && $alerts), $reopen); //must be called b4 sending alerts to staff.
    
            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...
    
    
            $dept = $this->getDept();
    
            $variables = array(
    
                'message' => $message,
                'poster' => ($vars['poster'] ? $vars['poster'] : $this->getName())
            );
    
            $options = array('thread'=>$message);
    
            // If enabled...send alert to staff (New Message Alert)
            if ($cfg->alertONNewMessage()
                && ($email = $dept->getAlertEmail())
                && ($tpl = $dept->getTemplate())
                && ($msg = $tpl->getNewMessageAlertMsgTemplate())
            ) {
    
                $msg = $this->replaceVars($msg->asArray(), $variables);
    
                // Build list of recipients and fire the alerts.
                $recipients = array();
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Last respondent.
    
                if ($cfg->alertLastRespondentONNewMessage() && ($lr = $this->getLastRespondent()))
                    $recipients[] = $lr;
    
    Jared Hancock's avatar
    Jared Hancock committed
                //Assigned staff if any...could be the last respondent
    
                if ($cfg->alertAssignedONNewMessage() && $this->isAssigned()) {
    
                    if ($staff = $this->getStaff())
                        $recipients[] = $staff;
                    elseif ($team = $this->getTeam())
                        $recipients = array_merge($recipients, $team->getMembers());
                }
    
                // Dept manager
                if ($cfg->alertDeptManagerONNewMessage()
                    && $dept
                    && ($manager = $dept->getManager())
                ) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $recipients[]=$manager;
    
                // Account manager
                if ($cfg->alertAcctManagerONNewMessage()
                        && ($org = $this->getOwner()->getOrganization())
    
                        && ($acct_manager = $org->getAccountManager())) {
    
                    if ($acct_manager instanceof Team)
                        $recipients = array_merge($recipients, $acct_manager->getMembers());
                    else
                        $recipients[] = $acct_manager;
                }
    
    
                $sentlist = array(); //I know it sucks...but..it works.
                foreach ($recipients as $k=>$staff) {
                    if (!$staff || !$staff->getEmail()
                        || !$staff->isAvailable()
                        || in_array($staff->getEmail(), $sentlist)
                    ) {
                        continue;
                    }
    
                    $alert = $this->replaceVars($msg, array('recipient' => $staff));
    
                    $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $sentlist[] = $staff->getEmail();
    
        function postCannedReply($canned, $message, $alert=true) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            global $ost, $cfg;
    
            if ((!is_object($canned) && !($canned=Canned::lookup($canned)))
                || !$canned->isEnabled()
            ) {
    
                return false;
    
            $files = array();
    
            foreach ($canned->attachments->getAll() as $file) {
    
                $files[] = $file->file_id;
    
                $_SESSION[':cannedFiles'][$file->file_id] = 1;
            }
    
            if ($cfg->isRichTextEnabled())
    
    Peter Rotich's avatar
    Peter Rotich committed
                $response = new HtmlThreadEntryBody(
    
                    $this->replaceVars($canned->getHtml()));
    
    Peter Rotich's avatar
    Peter Rotich committed
            else
    
    Peter Rotich's avatar
    Peter Rotich committed
                $response = new TextThreadEntryBody(
    
                    $this->replaceVars($canned->getPlainText()));
    
            $info = array('msgId' => $message instanceof ThreadEntry ? $message->getId() : 0,
    
                          'poster' => __('SYSTEM (Canned Reply)'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                          'response' => $response,
    
                          'cannedattachments' => $files
            );
    
    Peter Rotich's avatar
    Peter Rotich committed
            $errors = array();
    
            if (!($response=$this->postReply($info, $errors, false, false)))
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            $this->markUnAnswered();
    
    
            if (!$alert)
                return $response;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            $dept = $this->getDept();
    
    
            if (($email=$dept->getEmail())
                && ($tpl = $dept->getTemplate())
                && ($msg=$tpl->getAutoReplyMsgTemplate())
            ) {
                if ($dept && $dept->isPublic())
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $signature=$dept->getSignature();
                else
                    $signature='';
    
    
                $msg = $this->replaceVars($msg->asArray(),
    
                    array(
                        'response' => $response,
                        'signature' => $signature,
                        'recipient' => $this->getOwner(),
                    )
                );
                $attachments = ($cfg->emailAttachments() && $files)
                    ? $response->getAttachments() : array();
    
    
                $options = array('thread' => $response);
                if (($message instanceof ThreadEntry)
                        && $message->getUserId() == $this->getUserId()
                        && ($mid=$message->getEmailMessageId())) {
                    $options += array(
                            'inreplyto' => $mid,
                            'references' => $message->getEmailReferences()
                            );
                }
    
                $email->sendAutoReply($this->getOwner(), $msg['subj'], $msg['body'], $attachments,
    
        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(), $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(), $errors)))
    
            if (!$vars['poster'] && $thisstaff)
    
                $vars['poster'] = $thisstaff;
    
            if (!$vars['staffId'] && $thisstaff)
    
                $vars['staffId'] = $thisstaff->getId();
    
            if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
                $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
    
    
            if (!($response = $this->getThread()->addResponse($vars, $errors)))
    
            $dept = $this->getDept();
    
    Peter Rotich's avatar
    Peter Rotich committed
            $assignee = $this->getStaff();
    
            // Set status - if checked.
            if ($vars['reply_status_id']
    
                && $vars['reply_status_id'] != $this->getStatusId()
            ) {
    
                $this->setStatus($vars['reply_status_id']);
    
    Peter Rotich's avatar
    Peter Rotich committed
            // Claim on response bypasses the department assignment restrictions
    
            $claim = ($claim
                    && $cfg->autoClaimTickets()
                    && !$dept->disableAutoClaim());
            if ($claim && $thisstaff && $this->isOpen() && !$this->getStaffId()) {
    
                $this->setStaffId($thisstaff->getId()); //direct assignment;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            $this->lastrespondent = $response->staff;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            $this->onResponse($response, array('assignee' => $assignee)); //do house cleaning..
    
            /* email the user??  - if disabled - then bail out */
    
            if (!$alert)
                return $response;
    
            //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')
    
                $signature=$thisstaff->getSignature();
    
            elseif ($vars['signature']=='dept' && $dept->isPublic())
    
                $signature=$dept->getSignature();
    
    
            if ($thisstaff && ($type=$thisstaff->getReplyFromNameType())) {
                switch ($type) {
                    case 'mine':
                        if (!$cfg->hideStaffName())
                            $from_name = (string) $thisstaff->getName();
                        break;
                    case 'dept':
                        if ($dept->isPublic())
                            $from_name = $dept->getName();
                        break;
                    case 'email':
                    default:
                        $from_name =  $email->getName();
                }
                if ($from_name)
                    $options += array('from_name' => $from_name);
            }
    
            $variables = array(
    
                'response' => $response,
                'signature' => $signature,
                'staff' => $thisstaff,
                'poster' => $thisstaff
            );
    
            $user = $this->getOwner();
    
            if (($email=$email)
    
                && ($tpl = $dept->getTemplate())
    
    Peter Rotich's avatar
    Peter Rotich committed
                && ($msg=$tpl->getReplyMsgTemplate())) {
    
    
                $msg = $this->replaceVars($msg->asArray(),
    
                    $variables + array('recipient' => $user)
    
    Peter Rotich's avatar
    Peter Rotich committed
                $attachments = $cfg->emailAttachments() ? $response->getAttachments() : array();
    
                //Cc collaborators
    
    Peter Rotich's avatar
    Peter Rotich committed
                $collabsCc = array();
                if ($vars['ccs'] && $vars['emailcollab']) {
                    $collabsCc[] = Collaborator::getCollabList($vars['ccs']);
                    $collabsCc['cc'] = $collabsCc;
    
    Peter Rotich's avatar
    Peter Rotich committed
                $email->send($user, $msg['subj'], $msg['body'], $attachments,
                        $options, $collabsCc);
    
    
                //Bcc Collaborators
    
    Peter Rotich's avatar
    Peter Rotich committed
                if ($vars['bccs']
                        && $vars['emailcollab']
                        && ($bcctpl = $dept->getTemplate())
    
                        && ($bccmsg=$bcctpl->getReplyMsgTemplate())) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                    foreach ($vars['bccs'] as $uid) {
                        if (!($recipient = User::lookup($uid)))
                            continue;
    
    Peter Rotich's avatar
    Peter Rotich committed
                        $msg = $this->replaceVars($bccmsg->asArray(), $variables + array(
                                    'recipient' => $user,
                                    'recipient.name.first' =>
                                    $recipient->getName()->getFirst()));
    
                        $email->send($recipient, $msg['subj'], $msg['body'], $attachments, $options);
    
    Peter Rotich's avatar
    Peter Rotich committed
            }