Skip to content
Snippets Groups Projects
class.task.php 44.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Peter Rotich's avatar
    Peter Rotich committed
    <?php
    
    /*********************************************************************
        class.task.php
    
        Task
    
        Peter Rotich <peter@osticket.com>
        Copyright (c)  2014 osTicket
        http://www.osticket.com
    
        Released under the GNU General Public License WITHOUT ANY WARRANTY.
        See LICENSE.TXT for details.
    
        vim: expandtab sw=4 ts=4 sts=4:
    **********************************************************************/
    
    include_once INCLUDE_DIR.'class.role.php';
    
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    class TaskModel extends VerySimpleModel {
        static $meta = array(
            'table' => TASK_TABLE,
            'pk' => array('id'),
            'joins' => array(
    
                'dept' => array(
                    'constraint' => array('dept_id' => 'Dept.id'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                ),
    
                'lock' => array(
                    'constraint' => array('lock_id' => 'Lock.lock_id'),
                    'null' => true,
                ),
    
                'staff' => array(
                    'constraint' => array('staff_id' => 'Staff.staff_id'),
                    'null' => true,
                ),
    
    Peter Rotich's avatar
    Peter Rotich committed
                'team' => array(
                    'constraint' => array('team_id' => 'Team.team_id'),
                    'null' => true,
                ),
                'thread' => array(
                    'constraint' => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                        'id'  => 'TaskThread.object_id',
                        "'A'" => 'TaskThread.object_type',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    ),
                    'list' => false,
                    'null' => false,
                ),
                'cdata' => array(
                    'constraint' => array('id' => 'TaskCData.task_id'),
                    'list' => false,
                ),
    
                'entries' => array(
                    'constraint' => array(
                        "'A'" => 'DynamicFormEntry.object_type',
                        'id' => 'DynamicFormEntry.object_id',
                    ),
                    'list' => true,
                ),
    
    
                'ticket' => array(
                    'constraint' => array(
                        'object_type' => "'T'",
                        'object_id' => 'Ticket.ticket_id',
                    ),
                    'null' => true,
                ),
    
    Peter Rotich's avatar
    Peter Rotich committed
        );
    
    
        const PERM_CREATE   = 'task.create';
        const PERM_EDIT     = 'task.edit';
        const PERM_ASSIGN   = 'task.assign';
        const PERM_TRANSFER = 'task.transfer';
    
        const PERM_REPLY    = 'task.reply';
    
        const PERM_CLOSE    = 'task.close';
        const PERM_DELETE   = 'task.delete';
    
        static protected $perms = array(
                self::PERM_CREATE    => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Create',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to create tasks'),
                self::PERM_EDIT      => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Edit',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to edit tasks'),
                self::PERM_ASSIGN    => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Assign',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to assign tasks to agents or teams'),
                self::PERM_TRANSFER  => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Transfer',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to transfer tasks between departments'),
    
                self::PERM_REPLY => array(
                    'title' =>
                    /* @trans */ 'Post Reply',
                    'desc'  =>
                    /* @trans */ 'Ability to post task update'),
    
                self::PERM_CLOSE     => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Close',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to close tasks'),
                self::PERM_DELETE    => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Delete',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to delete tasks'),
                );
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        const ISOPEN    = 0x0001;
        const ISOVERDUE = 0x0002;
    
    
        protected function hasFlag($flag) {
            return ($this->get('flags') & $flag) !== 0;
        }
    
        protected function clearFlag($flag) {
            return $this->set('flags', $this->get('flags') & ~$flag);
        }
    
        protected function setFlag($flag) {
            return $this->set('flags', $this->get('flags') | $flag);
        }
    
        function getId() {
            return $this->id;
        }
    
        function getNumber() {
            return $this->number;
        }
    
        function getStaffId() {
            return $this->staff_id;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getStaff() {
            return $this->staff;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getTeamId() {
            return $this->team_id;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getTeam() {
            return $this->team;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getDeptId() {
            return $this->dept_id;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getDept() {
            return $this->dept;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getCreateDate() {
            return $this->created;
        }
    
        function getDueDate() {
            return $this->duedate;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getCloseDate() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->isClosed() ? $this->closed : '';
    
    Peter Rotich's avatar
    Peter Rotich committed
        function isOpen() {
            return $this->hasFlag(self::ISOPEN);
        }
    
        function isClosed() {
            return !$this->isOpen();
        }
    
    
        function isCloseable() {
    
            if ($this->isClosed())
                return true;
    
            $warning = null;
            if ($this->getMissingRequiredFields()) {
                $warning = sprintf(
                        __( '%1$s is missing data on %2$s one or more required fields %3$s and cannot be closed'),
                        __('This task'),
                        '', '');
            }
    
            return $warning ?: true;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        protected function close() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->clearFlag(self::ISOPEN);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        protected function reopen() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->setFlag(self::ISOPEN);
        }
    
        function isAssigned() {
            return ($this->isOpen() && ($this->getStaffId() || $this->getTeamId()));
        }
    
        function isOverdue() {
            return $this->hasFlag(self::ISOVERDUE);
        }
    
    
        static function getPermissions() {
            return self::$perms;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
    }
    
    
    RolePermission::register(/* @trans */ 'Tasks', TaskModel::getPermissions());
    
    
    
    class Task extends TaskModel implements RestrictedAccess, Threadable {
    
    Peter Rotich's avatar
    Peter Rotich committed
        var $form;
        var $entry;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        var $_thread;
    
        var $_entries;
    
        var $_answers;
    
        var $lastrespondent;
    
        function __onload() {
            $this->loadDynamicData();
        }
    
        function loadDynamicData() {
            if (!isset($this->_answers)) {
                $this->_answers = array();
                foreach (DynamicFormEntryAnswer::objects()
                    ->filter(array(
                        'entry__object_id' => $this->getId(),
                        'entry__object_type' => ObjectModel::OBJECT_TYPE_TASK
                    )) as $answer
                ) {
                    $tag = mb_strtolower($answer->field->name)
                        ?: 'field.' . $answer->field->id;
                        $this->_answers[$tag] = $answer;
                }
            }
            return $this->_answers;
        }
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getStatus() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->isOpen() ? __('Open') : __('Completed');
    
    Peter Rotich's avatar
    Peter Rotich committed
        }
    
        function getTitle() {
            return $this->__cdata('title', ObjectModel::OBJECT_TYPE_TASK);
        }
    
    
        function checkStaffPerm($staff, $perm=null) {
    
            // Must be a valid staff
            if (!$staff instanceof Staff && !($staff=Staff::lookup($staff)))
                return false;
    
            // Check access based on department or assignment
            if (!$staff->canAccessDept($this->getDeptId())
                    && $this->isOpen()
                    && $staff->getId() != $this->getStaffId()
                    && !$staff->isTeamMember($this->getTeamId()))
                return false;
    
            // At this point staff has access unless a specific permission is
            // requested
            if ($perm === null)
                return true;
    
            // Permission check requested -- get role.
            if (!($role=$staff->getRole($this->getDeptId())))
                return false;
    
            // Check permission based on the effective role
            return $role->hasPerm($perm);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getAssignee() {
    
            if (!$this->isOpen() || !$this->isAssigned())
                return false;
    
            if ($this->staff)
                return $this->staff;
    
            if ($this->team)
                return $this->team;
    
            return null;
        }
    
        function getAssigneeId() {
    
            if (!($assignee=$this->getAssignee()))
                return null;
    
            $id = '';
            if ($assignee instanceof Staff)
                $id = 's'.$assignee->getId();
            elseif ($assignee instanceof Team)
                $id = 't'.$assignee->getId();
    
            return $id;
        }
    
    
        function getAssignees() {
    
            $assignees=array();
            if ($this->staff)
                $assignees[] = $this->staff->getName();
    
            //Add team assignment
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($this->team)
    
                $assignees[] = $this->team->getName();
    
            return $assignees;
        }
    
        function getAssigned($glue='/') {
            $assignees = $this->getAssignees();
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            return $assignees ? implode($glue, $assignees):'';
    
    Peter Rotich's avatar
    Peter Rotich committed
        }
    
    
        function getLastRespondent() {
    
            if (!isset($this->lastrespondent)) {
                $this->lastrespondent = Staff::objects()
                    ->filter(array(
                    'staff_id' => static::objects()
                        ->filter(array(
                            'thread__entries__type' => 'R',
                            'thread__entries__staff_id__gt' => 0
                        ))
                        ->values_flat('thread__entries__staff_id')
                        ->order_by('-thread__entries__id')
                        ->limit(1)
                    ))
                    ->first()
                    ?: false;
            }
    
            return $this->lastrespondent;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getDynamicFields($criteria=array()) {
    
    
            $fields = DynamicFormField::objects()->filter(array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                        'id__in' => $this->entries
                        ->filter($criteria)
                    ->values_flat('answers__field_id')));
    
    
            return ($fields && count($fields)) ? $fields : array();
    
    Peter Rotich's avatar
    Peter Rotich committed
        }
    
        function getMissingRequiredFields() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->getDynamicFields(array(
                        'answers__field__flags__hasbit' => DynamicFormField::FLAG_ENABLED,
                        'answers__field__flags__hasbit' => DynamicFormField::FLAG_CLOSE_REQUIRED,
                        'answers__value__isnull' => true,
                        ));
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getParticipants() {
            $participants = array();
            foreach ($this->getThread()->collaborators as $c)
                $participants[] = $c->getName();
    
            return $participants ? implode(', ', $participants) : ' ';
        }
    
    
        function getThreadId() {
            return $this->thread->getId();
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getThread() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->thread;
    
    Peter Rotich's avatar
    Peter Rotich committed
        }
    
        function getThreadEntry($id) {
            return $this->getThread()->getEntry($id);
        }
    
    
        function getThreadEntries($type=false) {
            $thread = $this->getThread()->getEntries();
            if ($type && is_array($type))
                $thread->filter(array('type__in' => $type));
            return $thread;
    
    Peter Rotich's avatar
    Peter Rotich committed
        }
    
    
        function postThreadEntry($type, $vars, $options=array()) {
    
            $errors = array();
    
            $poster = isset($options['poster']) ? $options['poster'] : null;
            $alert = isset($options['alert']) ? $options['alert'] : true;
    
            switch ($type) {
            case 'N':
            default:
    
                return $this->postNote($vars, $errors, $poster, $alert);
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getForm() {
            if (!isset($this->form)) {
                // Look for the entry first
                if ($this->form = DynamicFormEntry::lookup(
                            array('object_type' => ObjectModel::OBJECT_TYPE_TASK))) {
                    return $this->form;
                }
                // Make sure the form is in the database
                elseif (!($this->form = DynamicForm::lookup(
                                array('type' => ObjectModel::OBJECT_TYPE_TASK)))) {
                    $this->__loadDefaultForm();
                    return $this->getForm();
                }
                // Create an entry to be saved later
                $this->form = $this->form->instanciate();
                $this->form->object_type = ObjectModel::OBJECT_TYPE_TASK;
            }
    
            return $this->form;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getAssignmentForm($source=null) {
    
            if (!$source)
                $source = array('assignee' => array($this->getAssigneeId()));
    
            return AssignmentForm::instantiate($source,
                    array('dept' => $this->getDept()));
        }
    
        function getTransferForm($source=null) {
    
            if (!$source)
                $source = array('dept' => array($this->getDeptId()));
    
            return TransferForm::instantiate($source);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function addDynamicData($data) {
    
            $tf = TaskForm::getInstance($this->id, true);
            foreach ($tf->getFields() as $f)
                if (isset($data[$f->get('name')]))
                    $tf->setAnswer($f->get('name'), $data[$f->get('name')]);
    
            $tf->save();
    
            return $tf;
        }
    
        function getDynamicData($create=true) {
            if (!isset($this->_entries)) {
                $this->_entries = DynamicFormEntry::forObject($this->id,
                        ObjectModel::OBJECT_TYPE_TASK)->all();
                if (!$this->_entries && $create) {
                    $f = TaskForm::getInstance($this->id, true);
                    $f->save();
                    $this->_entries[] = $f;
                }
            }
    
            return $this->_entries ?: array();
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function setStatus($status, $comments='', &$errors=array()) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            global $thisstaff;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $ecb = null;
    
    Peter Rotich's avatar
    Peter Rotich committed
            switch($status) {
            case 'open':
                if ($this->isOpen())
                    return false;
    
                $this->reopen();
    
    Peter Rotich's avatar
    Peter Rotich committed
                $this->closed = null;
    
    
                $ecb = function ($t) {
                    $t->logEvent('reopened', false, null, 'closed');
                };
    
    Peter Rotich's avatar
    Peter Rotich committed
                break;
            case 'closed':
                if ($this->isClosed())
                    return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
                // Check if task is closeable
                $closeable = $this->isCloseable();
                if ($closeable !== true)
                    $errors['err'] = $closeable ?: sprintf(__('%s cannot be closed'), __('This task'));
    
                if ($errors)
                    return false;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
                $this->close();
    
    Peter Rotich's avatar
    Peter Rotich committed
                $this->closed = SqlFunction::NOW();
    
                $ecb = function($t) {
                    $t->logEvent('closed');
                };
    
    Peter Rotich's avatar
    Peter Rotich committed
                break;
            default:
                return false;
            }
    
    
            if (!$this->save(true))
                return false;
    
    
            // Log events via callback
            if ($ecb) $ecb($this);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($comments) {
                $errors = array();
                $this->postNote(array(
                            'note' => $comments,
                            'title' => sprintf(
                                __('Status changed to %s'),
                                $this->getStatus())
                            ),
                        $errors,
                        $thisstaff);
            }
    
            return true;
        }
    
    Peter Rotich's avatar
    Peter Rotich committed
    
        function to_json() {
    
            $info = array(
                    'id'  => $this->getId(),
                    'title' => $this->getTitle()
                    );
    
            return JsonDataEncoder::encode($info);
        }
    
        function __cdata($field, $ftype=null) {
    
            foreach ($this->getDynamicData() as $e) {
                // Make sure the form type matches
    
    Peter Rotich's avatar
    Peter Rotich committed
                if (!$e->form
                        || ($ftype && $ftype != $e->form->get('type')))
    
    Peter Rotich's avatar
    Peter Rotich committed
                    continue;
    
                // Get the named field and return the answer
    
    Peter Rotich's avatar
    Peter Rotich committed
                if ($a = $e->getAnswer($field))
                    return $a;
    
    Peter Rotich's avatar
    Peter Rotich committed
            }
    
            return null;
        }
    
        function __toString() {
            return (string) $this->getTitle();
        }
    
        /* util routines */
    
    
        function logEvent($state, $data=null, $user=null, $annul=null) {
            $this->getThread()->getEvents()->log($this, $state, $data, $user, $annul);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function assign(AssignmentForm $form, &$errors, $alert=true) {
    
            global $thisstaff;
    
            $evd = array();
    
    Peter Rotich's avatar
    Peter Rotich committed
            $assignee = $form->getAssignee();
            if ($assignee instanceof Staff) {
                if ($this->getStaffId() == $assignee->getId()) {
                    $errors['assignee'] = sprintf(__('%s already assigned to %s'),
                            __('Task'),
                            __('the agent')
                            );
                } elseif(!$assignee->isAvailable()) {
                    $errors['assignee'] = __('Agent is unavailable for assignment');
                } else {
                    $this->staff_id = $assignee->getId();
    
                    if ($thisstaff && $thisstaff->getId() == $assignee->getId())
                        $evd['claim'] = true;
                    else
    
                        $evd['staff'] = array($assignee->getId(), $assignee->getName());
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
            } elseif ($assignee instanceof Team) {
                if ($this->getTeamId() == $assignee->getId()) {
                    $errors['assignee'] = sprintf(__('%s already assigned to %s'),
                            __('Task'),
                            __('the team')
                            );
                } else {
                    $this->team_id = $assignee->getId();
    
                    $evd = array('team' => $assignee->getId());
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
            } else {
                $errors['assignee'] = __('Unknown assignee');
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($errors || !$this->save(true))
    
                return false;
    
    
            $this->logEvent('assigned', $evd);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $this->onAssignment($assignee,
                    $form->getField('comments')->getClean(),
                    $alert);
    
            return true;
        }
    
    
        function onAssignment($assignee, $comments='', $alert=true) {
            global $thisstaff, $cfg;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            if (!is_object($assignee))
                return false;
    
            $assigner = $thisstaff ?: __('SYSTEM (Auto Assignment)');
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Assignment completed... post internal note.
    
            $note = null;
            if ($comments) {
    
                $title = sprintf(__('Task assigned to %s'),
                        (string) $assignee);
    
                $errors = array();
                $note = $this->postNote(
                        array('note' => $comments, 'title' => $title),
                        $errors,
                        $assigner,
                        false);
    
            // Send alerts out if enabled.
            if (!$alert || !$cfg->alertONTaskAssignment())
    
    Peter Rotich's avatar
    Peter Rotich committed
                return false;
    
            if (!($dept=$this->getDept())
                || !($tpl = $dept->getTemplate())
                || !($email = $dept->getAlertEmail())
            ) {
                return true;
            }
    
            // Recipients
            $recipients = array();
            if ($assignee instanceof Staff) {
                if ($cfg->alertStaffONTaskAssignment())
                    $recipients[] = $assignee;
            } elseif (($assignee instanceof Team) && $assignee->alertsEnabled()) {
                if ($cfg->alertTeamMembersONTaskAssignment() && ($members=$assignee->getMembers()))
                    $recipients = array_merge($recipients, $members);
                elseif ($cfg->alertTeamLeadONTaskAssignment() && ($lead=$assignee->getTeamLead()))
                    $recipients[] = $lead;
            }
    
            if ($recipients
                && ($msg=$tpl->getTaskAssignmentAlertMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(),
                    array('comments' => $comments,
                          'assignee' => $assignee,
                          'assigner' => $assigner
                    )
                );
                // Send the alerts.
                $sentlist = array();
                $options = $note instanceof ThreadEntry
                    ? array(
                        'inreplyto' => $note->getEmailMessageId(),
                        'references' => $note->getEmailReferences(),
                        'thread' => $note)
                    : array();
    
                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);
                    $sentlist[] = $staff->getEmail();
                }
            }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function transfer(TransferForm $form, &$errors, $alert=true) {
    
            global $thisstaff, $cfg;
    
            $cdept = $this->getDept();
    
    Peter Rotich's avatar
    Peter Rotich committed
            $dept = $form->getDept();
            if (!$dept || !($dept instanceof Dept))
                $errors['dept'] = __('Department selection required');
    
            elseif ($dept->getid() == $this->getDeptId())
    
    Peter Rotich's avatar
    Peter Rotich committed
                $errors['dept'] = __('Task already in the department');
    
            else
                $this->dept_id = $dept->getId();
    
            if ($errors || !$this->save())
                return false;
    
    
            // Log transfer event
            $this->logEvent('transferred');
    
            // Post internal note if any
    
    Peter Rotich's avatar
    Peter Rotich committed
            $note = $form->getField('comments')->getClean();
    
            if ($note) {
                $title = sprintf(__('%1$s transferred from %2$s to %3$s'),
                        __('Task'),
                       $cdept->getName(),
                        $dept->getName());
    
                $_errors = array();
                $note = $this->postNote(
                        array('note' => $note, 'title' => $title),
                        $_errors, $thisstaff, false);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            // Send alerts if requested && enabled.
    
            if (!$alert || !$cfg->alertONTaskTransfer())
    
    Peter Rotich's avatar
    Peter Rotich committed
                return true;
    
    
            if (($email = $dept->getAlertEmail())
                 && ($tpl = $dept->getTemplate())
                 && ($msg=$tpl->getTaskTransferAlertMsgTemplate())) {
    
                $msg = $this->replaceVars($msg->asArray(),
                    array('comments' => $note, 'staff' => $thisstaff));
                // Recipients
                $recipients = array();
                // Assigned staff or team... if any
                if ($this->isAssigned() && $cfg->alertAssignedONTaskTransfer()) {
                    if($this->getStaffId())
                        $recipients[] = $this->getStaff();
                    elseif ($this->getTeamId()
                        && ($team=$this->getTeam())
                        && ($members=$team->getMembers())
                    ) {
                        $recipients = array_merge($recipients, $members);
                    }
                } elseif ($cfg->alertDeptMembersONTaskTransfer() && !$this->isAssigned()) {
                    // Only alerts dept members if the task is NOT assigned.
    
                    if ($members = $dept->getMembersForAlerts()->all())
    
                        $recipients = array_merge($recipients, $members);
                }
    
                // Always alert dept manager??
                if ($cfg->alertDeptManagerONTaskTransfer()
                    && ($manager=$dept->getManager())) {
                    $recipients[] = $manager;
                }
    
                $sentlist = $options = array();
                if ($note instanceof ThreadEntry) {
                    $options += array(
                        'inreplyto'=>$note->getEmailMessageId(),
                        'references'=>$note->getEmailReferences(),
                        '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);
                    $sentlist[] = $staff->getEmail();
                }
            }
    
    Peter Rotich's avatar
    Peter Rotich committed
        function postNote($vars, &$errors, $poster='', $alert=true) {
            global $cfg, $thisstaff;
    
            $vars['staffId'] = 0;
            $vars['poster'] = 'SYSTEM';
            if ($poster && is_object($poster)) {
                $vars['staffId'] = $poster->getId();
                $vars['poster'] = $poster->getName();
            } elseif ($poster) { //string
                $vars['poster'] = $poster;
            }
    
            if (!($note=$this->getThread()->addNote($vars, $errors)))
                return null;
    
    
            $assignee = $this->getStaff();
    
    
            if (isset($vars['task:status']))
                $this->setStatus($vars['task:status']);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            $this->onActivity(array(
                'activity' => $note->getActivity(),
                'threadentry' => $note,
                'assignee' => $assignee
            ), $alert);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $note;
        }
    
    
        /* public */
        function postReply($vars, &$errors, $alert = true) {
            global $thisstaff, $cfg;
    
    
            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)))
                return null;
    
            $assignee = $this->getStaff();
    
    
            if (isset($vars['task:status']))
                $this->setStatus($vars['task:status']);
    
    
            /*
            // TODO: add auto claim setting for tasks.
            // Claim on response bypasses the department assignment restrictions
            if ($thisstaff
                && $this->isOpen()
                && !$this->getStaffId()
                && $cfg->autoClaimTasks)
            ) {
                $this->staff_id = $thisstaff->getId();
            }
            */
    
            $this->lastrespondent = $response->staff;
            $this->save();
    
            // Send activity alert to agents
            $activity = $vars['activity'] ?: $response->getActivity();
            $this->onActivity( array(
                        'activity' => $activity,
                        'threadentry' => $response,
                        'assignee' => $assignee,
                        ));
            // Send alert to collaborators
            if ($alert && $vars['emailcollab']) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                $signature = '';
    
                $this->notifyCollaborators($response,
                    array('signature' => $signature)
                );
            }
    
            return $response;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function pdfExport($options=array()) {
            global $thisstaff;
    
            require_once(INCLUDE_DIR.'class.pdf.php');
            if (!isset($options['psize'])) {
                if ($_SESSION['PAPER_SIZE'])
                    $psize = $_SESSION['PAPER_SIZE'];
                elseif (!$thisstaff || !($psize = $thisstaff->getDefaultPaperSize()))
                    $psize = 'Letter';
    
                $options['psize'] = $psize;
            }
    
            $pdf = new Task2PDF($this, $options);
            $name = 'Task-'.$this->getNumber().'.pdf';
            Http::download($name, 'application/pdf', $pdf->Output($name, 'S'));
            //Remember what the user selected - for autoselect on the next print.
            $_SESSION['PAPER_SIZE'] = $options['psize'];
            exit;
        }
    
    
        /* util routines */
        function replaceVars($input, $vars = array()) {
            global $ost;
    
            return $ost->replaceTemplateVariables($input,
                    array_merge($vars, array('task' => $this)));
        }
    
        function asVar() {
           return $this->getNumber();
        }
    
        function getVar($tag) {
            global $cfg;
    
            if ($tag && is_callable(array($this, 'get'.ucfirst($tag))))
                return call_user_func(array($this, 'get'.ucfirst($tag)));
    
            switch(mb_strtolower($tag)) {
            case 'phone':
            case 'phone_number':
                return $this->getPhoneNumber();
                break;
            case 'staff_link':
                return sprintf('%s/scp/tasks.php?id=%d', $cfg->getBaseUrl(), $this->getId());
                break;
            case 'create_date':
                return new FormattedDate($this->getCreateDate());
                break;
             case 'due_date':
                if ($due = $this->getEstDueDate())
                    return new FormattedDate($due);
                break;
            case 'close_date':
                if ($this->isClosed())
                    return new FormattedDate($this->getCloseDate());
                break;
            case 'last_update':
                return new FormattedDate($this->last_update);
            default:
                if (isset($this->_answers[$tag]))
                    // The answer object is retrieved here which will
                    // automatically invoke the toString() method when the
                    // answer is coerced into text
                    return $this->_answers[$tag];
            }
            return false;
        }
    
        static function getVarScope() {
            $base = array(
                'assigned' => __('Assigned agent and/or team'),
                'close_date' => array(
                    'class' => 'FormattedDate', 'desc' => __('Date Closed'),
                ),
                'create_date' => array(
                    'class' => 'FormattedDate', 'desc' => __('Date created'),
                ),
                'dept' => array(
                    'class' => 'Dept', 'desc' => __('Department'),
                ),
                'due_date' => array(
                    'class' => 'FormattedDate', 'desc' => __('Due Date'),
                ),
                'number' => __('Task number'),
                'recipients' => array(
                    'class' => 'UserList', 'desc' => __('List of all recipient names'),
                ),
                'status' => __('Status'),
                'staff' => array(
                    'class' => 'Staff', 'desc' => __('Assigned/closing agent'),
                ),
                'subject' => 'Subject',
                'team' => array(
                    'class' => 'Team', 'desc' => __('Assigned/closing team'),
                ),
                'thread' => array(
                    'class' => 'TaskThread', 'desc' => __('Task Thread'),
                ),
                'last_update' => array(
                    'class' => 'FormattedDate', 'desc' => __('Time of last update'),
                ),
            );
    
            $extra = VariableReplacer::compileFormScope(TaskForm::getInstance());
            return $base + $extra;
        }
    
        function onActivity($vars, $alert=true) {
            global $cfg, $thisstaff;
    
            if (!$alert // Check if alert is enabled
                || !$cfg->alertONTaskActivity()
                || !($dept=$this->getDept())
                || !($email=$cfg->getAlertEmail())
                || !($tpl = $dept->getTemplate())
                || !($msg=$tpl->getTaskActivityAlertMsgTemplate())
            ) {
                return;
            }
    
            // Alert recipients
            $recipients = array();
            //Last respondent.
            if ($cfg->alertLastRespondentONTaskActivity())
                $recipients[] = $this->getLastRespondent();