Skip to content
Snippets Groups Projects
class.ticket.php 146 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jared Hancock's avatar
    Jared Hancock committed
    <?php
    /*********************************************************************
        class.ticket.php
    
        The most important class! Don't play with fire please.
    
        Peter Rotich <peter@osticket.com>
    
        Copyright (c)  2006-2013 osTicket
    
    Jared Hancock's avatar
    Jared Hancock committed
        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.thread.php');
    
    Jared Hancock's avatar
    Jared Hancock committed
    include_once(INCLUDE_DIR.'class.staff.php');
    
    include_once(INCLUDE_DIR.'class.client.php');
    
    Jared Hancock's avatar
    Jared Hancock committed
    include_once(INCLUDE_DIR.'class.team.php');
    include_once(INCLUDE_DIR.'class.email.php');
    include_once(INCLUDE_DIR.'class.dept.php');
    include_once(INCLUDE_DIR.'class.topic.php');
    include_once(INCLUDE_DIR.'class.lock.php');
    include_once(INCLUDE_DIR.'class.file.php');
    
    Peter Rotich's avatar
    Peter Rotich committed
    include_once(INCLUDE_DIR.'class.export.php');
    
    Jared Hancock's avatar
    Jared Hancock committed
    include_once(INCLUDE_DIR.'class.attachment.php');
    include_once(INCLUDE_DIR.'class.banlist.php');
    include_once(INCLUDE_DIR.'class.template.php');
    
    include_once(INCLUDE_DIR.'class.variable.php');
    
    Jared Hancock's avatar
    Jared Hancock committed
    include_once(INCLUDE_DIR.'class.priority.php');
    
    Peter Rotich's avatar
    Peter Rotich committed
    include_once(INCLUDE_DIR.'class.sla.php');
    
    include_once(INCLUDE_DIR.'class.canned.php');
    
    Jared Hancock's avatar
    Jared Hancock committed
    require_once(INCLUDE_DIR.'class.dynamic_forms.php');
    
    require_once(INCLUDE_DIR.'class.user.php');
    
    require_once(INCLUDE_DIR.'class.collaborator.php');
    
    Peter Rotich's avatar
    Peter Rotich committed
    require_once(INCLUDE_DIR.'class.task.php');
    
    require_once(INCLUDE_DIR.'class.faq.php');
    
    class Ticket extends VerySimpleModel
    implements RestrictedAccess, Threadable, Searchable {
    
        static $meta = array(
            'table' => TICKET_TABLE,
            'pk' => array('ticket_id'),
    
            'select_related' => array('topic', 'staff', 'user', 'team', 'dept',
                'sla', 'thread', 'user__default_email', 'status'),
    
            'joins' => array(
                'user' => array(
                    'constraint' => array('user_id' => 'User.id')
                ),
                'status' => array(
                    'constraint' => array('status_id' => 'TicketStatus.id')
                ),
                'lock' => array(
    
                    'constraint' => array('lock_id' => 'Lock.lock_id'),
    
                    'null' => true,
                ),
                'dept' => array(
    
                    'constraint' => array('dept_id' => 'Dept.id'),
    
                'sla' => array(
    
                    'constraint' => array('sla_id' => 'Sla.id'),
    
                    'null' => true,
                ),
    
    Jared Hancock's avatar
    Jared Hancock committed
                    'constraint' => array('staff_id' => 'Staff.staff_id'),
    
                'tasks' => array(
                    'reverse' => 'Task.ticket',
                ),
    
                'team' => array(
                    'constraint' => array('team_id' => 'Team.team_id'),
                    'null' => true,
                ),
                'topic' => array(
                    'constraint' => array('topic_id' => 'Topic.topic_id'),
                    'null' => true,
                ),
    
                    'reverse' => 'TicketThread.ticket',
    
                    'list' => false,
                    'null' => true,
                ),
    
                'cdata' => array(
                    'reverse' => 'TicketCData.ticket',
                    'list' => false,
                ),
    
                'entries' => array(
                    'constraint' => array(
                        "'T'" => 'DynamicFormEntry.object_type',
                        'ticket_id' => 'DynamicFormEntry.object_id',
                    ),
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'list' => true,
    
        const PERM_CREATE   = 'ticket.create';
        const PERM_EDIT     = 'ticket.edit';
        const PERM_ASSIGN   = 'ticket.assign';
    
        const PERM_RELEASE  = 'ticket.release';
    
        const PERM_TRANSFER = 'ticket.transfer';
    
        const PERM_REFER    = 'ticket.refer';
    
        const PERM_REPLY    = 'ticket.reply';
        const PERM_CLOSE    = 'ticket.close';
        const PERM_DELETE   = 'ticket.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 open tickets on behalf of users'),
                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 tickets'),
                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 tickets to agents or teams'),
    
                self::PERM_RELEASE => array(
                    'title' =>
                    /* @trans */ 'Release',
                    'desc'  =>
                    /* @trans */ 'Ability to release ticket assignment'),
    
                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 tickets between departments'),
    
                self::PERM_REFER => array(
                    'title' =>
                    /* @trans */ 'Refer',
                    'desc'  =>
                    /* @trans */ 'Ability to manage ticket referrals'),
    
                self::PERM_REPLY => array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'title' =>
    
                    /* @trans */ 'Post Reply',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'desc'  =>
    
                    /* @trans */ 'Ability to post a ticket reply'),
                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 tickets'),
                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 tickets'),
                );
    
    Peter Rotich's avatar
    Peter Rotich committed
        // Ticket Sources
        static protected $sources =  array(
                'Phone' =>
                /* @trans */ 'Phone',
                'Email' =>
                /* @trans */ 'Email',
    
    Peter Rotich's avatar
    Peter Rotich committed
    
                'Web' =>
                /* @trans */ 'Web',
                'API' =>
                /* @trans */ 'API',
    
    Peter Rotich's avatar
    Peter Rotich committed
                'Other' =>
                /* @trans */ 'Other',
                );
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        var $lastMsgId;
    
        var $last_message;
    
        var $owner;     // TicketOwner
        var $_user;      // EndUser
        var $_answers;
        var $collaborators;
        var $active_collaborators;
        var $recipients;
        var $lastrespondent;
    
        function loadDynamicData($force=false) {
            if (!isset($this->_answers) || $force) {
    
                $this->_answers = array();
    
                foreach (DynamicFormEntryAnswer::objects()
                    ->filter(array(
                        'entry__object_id' => $this->getId(),
                        'entry__object_type' => 'T'
                    )) as $answer
                ) {
                    $tag = mb_strtolower($answer->field->name)
                        ?: 'field.' . $answer->field->id;
                        $this->_answers[$tag] = $answer;
    
            return $this->_answers;
    
        function getAnswer($field, $form=null) {
            // TODO: Prefer CDATA ORM relationship if already loaded
            $this->loadDynamicData();
            return $this->_answers[$field];
        }
    
    
        function getId() {
            return $this->ticket_id;
        }
    
    
        function hasState($state) {
    
            return  strcasecmp($this->getState(), $state) == 0;
    
    Jared Hancock's avatar
    Jared Hancock committed
        function isOpen() {
    
            return $this->hasState('open');
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function isReopened() {
    
            return null !== $this->getReopenDate();
    
        function isReopenable() {
    
            return ($this->getStatus()->isReopenable() && $this->getDept()->allowsReopen()
    
    JediKev's avatar
    JediKev committed
            && ($this->getTopic() ? $this->getTopic()->allowsReopen() : true));
    
    Jared Hancock's avatar
    Jared Hancock committed
        function isClosed() {
    
             return $this->hasState('closed');
        }
    
    
        function isCloseable() {
    
            global $cfg;
    
    
            if ($this->isClosed())
                return true;
    
            $warning = null;
    
            if (self::getMissingRequiredFields($this)) {
    
                $warning = sprintf(
                        __( '%1$s is missing data on %2$s one or more required fields %3$s and cannot be closed'),
                        __('This ticket'),
                        '', '');
            } elseif (($num=$this->getNumOpenTasks())) {
                $warning = sprintf(__('%1$s has %2$d open tasks and cannot be closed'),
                        __('This ticket'), $num);
    
            } elseif ($cfg->requireTopicToClose() && !$this->getTopicId()) {
                $warning = sprintf(
                        __( '%1$s is missing a %2$s and cannot be closed'),
                        __('This ticket'), __('Help Topic'), '');
    
        function isArchived() {
             return $this->hasState('archived');
        }
    
        function isDeleted() {
             return $this->hasState('deleted');
    
        function isAssigned($to=null) {
    
            if (!$this->isOpen())
                return false;
    
    
                return ($this->getStaffId() || $this->getTeamId());
    
            switch (true) {
            case $to instanceof Staff:
                return ($to->getId() == $this->getStaffId() ||
                        $to->isTeamMember($this->getTeamId()));
                break;
            case $to instanceof Team:
                return ($to->getId() == $this->getTeamId());
                break;
            }
    
            return false;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function isOverdue() {
    
            return $this->ht['isoverdue'];
    
    Jared Hancock's avatar
    Jared Hancock committed
        function isAnswered() {
    
           return $this->ht['isanswered'];
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function isLocked() {
    
            return null !== $this->getLock();
    
        function getRole($staff) {
            if (!$staff instanceof Staff)
                return null;
    
            return $staff->getRole($this->getDept(), $this->isAssigned($staff));
        }
    
    
        function checkStaffPerm($staff, $perm=null) {
    
            // Must be a valid staff
    
            if ((!$staff instanceof Staff) && !($staff=Staff::lookup($staff)))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    
            // check department access first
            if (!$staff->canAccessDept($this->getDept())
                    // no restrictions
                    && !$staff->isAccessLimited()
                    // check assignment
                    && !$this->isAssigned($staff)
                    // check referral
                    && !$this->thread->isReferred($staff))
    
    Jared Hancock's avatar
    Jared Hancock committed
                return false;
    
    
            // At this point staff has view access unless a specific permission is
            // requested
            if ($perm === null)
    
            // Permission check requested -- get role if any
            if (!($role=$this->getRole($staff)))
    
            // Check permission based on the effective role
            return $role->hasPerm($perm);
    
        function checkUserAccess($user) {
            if (!$user || !($user instanceof EndUser))
    
            // Ticket Owner
    
            if ($user->getId() == $this->getUserId())
    
            // Organization
            if ($user->canSeeOrgTickets()
                && ($U = $this->getUser())
                && ($U->getOrgId() == $user->getOrgId())
            ) {
                // The owner of this ticket is in the same organization as the
                // user in question, and the organization is configured to allow
                // the user in question to see other tickets in the
                // organization.
                return true;
            }
    
    
            // Collaborator?
    
            // 1) If the user was authorized via this ticket.
            if ($user->getTicketId() == $this->getId()
    
                && !strcasecmp($user->getUserType(), 'collaborator')
            ) {
    
                return true;
    
            // 2) Query the database to check for expanded access...
            if (Collaborator::lookup(array(
    
                'user_id' => $user->getId(),
                'thread_id' => $this->getThreadId()))
            ) {
    
                return true;
    
        // Getters
    
        function getOwnerId() {
    
            return $this->user_id;
    
            if (!isset($this->owner)) {
                $this->owner = new TicketOwner(new EndUser($this->user), $this);
            }
    
            return $this->owner;
    
        function getEmail() {
            if ($o = $this->getOwner()) {
    
                return $o->getEmail();
    
            return null;
        }
    
        function getReplyToEmail() {
    
            //TODO: Determine the email to use (once we enable multi-email support)
            return $this->getEmail();
    
        // Deprecated
        function getOldAuthToken() {
    
            # XXX: Support variable email address (for CCs)
    
            return md5($this->getId() . strtolower($this->getEmail()) . SECRET_SALT);
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getName(){
    
            if ($o = $this->getOwner()) {
    
                return $o->getName();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getSubject() {
    
            return (string) $this->getAnswer('subject');
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        /* Help topic title  - NOT object -> $topic */
        function getHelpTopic() {
    
            if ($this->topic)
                return $this->topic->getFullName();
    
            return $this->created;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getOpenDate() {
            return $this->getCreateDate();
        }
    
        function getReopenDate() {
    
            return $this->reopened;
    
            return $this->updated;
    
        function getEffectiveDate() {
    
            return $this->lastupdate;
    
            return $this->duedate;
    
        function getSLADueDate($datetime=null) {
    
            if ($sla = $this->getSLA()) {
    
                $dt = new DateTime($datetime ?: $this->getReopenDate() ?: $this->getCreateDate());
    
                return $dt
                    ->add(new DateInterval('PT' . $sla->getGracePeriod() . 'H'))
                    ->format('Y-m-d H:i:s');
            }
    
        function updateEstDueDate($clearOverdue=true) {
            $DueDate = $this->getEstDueDate();
            $this->est_duedate = $this->getSLADueDate();
            // Clear overdue flag if duedate or SLA changes and the ticket is no longer overdue.
            if ($this->isOverdue()
                && $clearOverdue
                && (!$DueDate // Duedate + SLA cleared
                    || Misc::db2gmtime($DueDate) > Misc::gmtime() //New due date in the future.
            )) {
                 $this->isoverdue = 0;
            }
    
            return $this->save();
    
        }
    
        function getEstDueDate() {
    
            // Real due date or  sla due date (If ANY)
            return $this->getDueDate() ?: $this->getSLADueDate();
    
            return $this->closed;
    
        function getStatusId() {
    
            return $this->status_id;
    
        /**
         * setStatusId
         *
         * Forceably set the ticket status ID to the received status ID. No
         * checks are made. Use ::setStatus() to change the ticket status
         */
        // XXX: Use ::setStatus to change the status. This can be used as a
        //      fallback if the logic in ::setStatus fails.
        function setStatusId($id) {
    
            $this->status_id = $id;
            return $this->save();
    
            return $this->status;
        }
    
        function getState() {
    
            if (!$this->getStatus()) {
    
                return '';
    
            return $this->getStatus()->getState();
    
           return $this->dept_id;
    
            if ($this->dept instanceof Dept)
                return $this->dept->getFullName();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getPriorityId() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            if (($priority = $this->getPriority()))
                return $priority->getId();
    
    
            return $cfg->getDefaultPriorityId();
    
        function getPriority() {
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            if (($a = $this->getAnswer('priority')))
                return $a->getValue();
    
            return null;
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getPhoneNumber() {
    
            return (string)$this->getOwner()->getPhoneNumber();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getSource() {
    
    aydreeihn's avatar
    aydreeihn committed
            $sources = $this->getSources();
            return $sources[$this->source] ?: $this->source;
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getIP() {
    
            return $this->ip_address;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getHashtable() {
            return $this->ht;
        }
    
        function getUpdateInfo() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            global $cfg;
    
    
            return array(
    
    aydreeihn's avatar
    aydreeihn committed
                'source'    => $this->source,
    
                'topicId'   => $this->getTopicId(),
                'slaId'     => $this->getSLAId(),
                'user_id'   => $this->getOwnerId(),
                'duedate'   => $this->getDueDate()
    
    Peter Rotich's avatar
    Peter Rotich committed
                    ? Format::date($this->getDueDate(), true,
                        $cfg->getDateFormat(true))
                    : '',
                'time'      => $this->getDueDate()
                    ? Format::time($this->getDueDate(), true, 'HH:mm')
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            $lock = $this->lock;
            if ($lock && !$lock->isExpired())
                return $lock;
    
        function acquireLock($staffId, $lockTime=null) {
            global $cfg;
    
            if (!isset($lockTime))
                $lockTime = $cfg->getLockTime();
    
            if (!$staffId or !$lockTime) //Lockig disabled?
    
    Jared Hancock's avatar
    Jared Hancock committed
                return null;
    
    
            // Check if the ticket is already locked.
            if (($lock = $this->getLock()) && !$lock->isExpired()) {
                if ($lock->getStaffId() != $staffId) //someone else locked the ticket.
    
    Jared Hancock's avatar
    Jared Hancock committed
                    return null;
    
                //Lock already exits...renew it
                $lock->renew($lockTime); //New clock baby.
    
    Jared Hancock's avatar
    Jared Hancock committed
                return $lock;
            }
    
            // No lock on the ticket or it is expired
            $this->lock = Lock::acquire($staffId, $lockTime); //Create a new lock..
    
            if ($this->lock) {
                $this->save();
    
            // load and return the newly created lock if any!
            return $this->lock;
    
        function releaseLock($staffId=false) {
    
            if (!($lock = $this->getLock()))
    
                return false;
    
            if ($staffId && $lock->staff_id != $staffId)
                return false;
    
    
            if (!$lock->delete())
    
                return false;
    
            $this->lock = null;
            return $this->save();
    
            return $this->dept ?: $cfg->getDefaultDept();
    
        function getUserId() {
            return $this->getOwnerId();
        }
    
        function getUser() {
    
            if (!isset($this->_user) && $this->user) {
                $this->_user = new EndUser($this->user);
            }
            return $this->_user;
    
            return $this->staff_id;
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->staff;
        }
    
    
            return $this->team_id;
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->team;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getAssigneeId() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            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;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getAssignee() {
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            if (!$this->isOpen() || !$this->isAssigned())
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($this->staff)
                return $this->staff;
    
    Peter Rotich's avatar
    Peter Rotich committed
            if ($this->team)
                return $this->team;
    
    Peter Rotich's avatar
    Peter Rotich committed
            return null;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getAssignees() {
    
            $assignees = array();
            if ($staff = $this->getStaff())
    
                $assignees[] = $staff->getName();
    
            if ($team = $this->getTeam())
    
                $assignees[] = $team->getName();
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            return $assignees;
        }
    
        function getAssigned($glue='/') {
            $assignees = $this->getAssignees();
    
            return $assignees ? implode($glue, $assignees) : '';
    
        function getTopicId() {
    
            return $this->topic_id;
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->topic;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getSLAId() {
    
            return $this->sla_id;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getSLA() {
            return $this->sla;
        }
    
        function getLastRespondent() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            if (!isset($this->lastrespondent)) {
    
                if (!$this->thread || !$this->thread->entries)
                    return $this->lastrespondent = false;
    
                $this->lastrespondent = Staff::objects()
    
                    ->filter(array(
    
                    'staff_id' => $this->thread->entries
    
                        ->filter(array(
    
                            'type' => 'R',
                            'staff_id__gt' => 0,
    
                        ->values_flat('staff_id')
                        ->order_by('-id')
    
                        ->limit(1)
    
                    ->first()
                    ?: false;
    
    Peter Rotich's avatar
    Peter Rotich committed
            }
            return $this->lastrespondent;
    
        function getLastUserRespondent() {
            if (!isset($this->$lastuserrespondent)) {
                if (!$this->thread || !$this->thread->entries)
                    return $this->$lastuserrespondent = false;
                $this->$lastuserrespondent = User::objects()
                    ->filter(array(
                    'id' => $this->thread->entries
                        ->filter(array(
                            'user_id__gt' => 0,
                        ))
                        ->values_flat('user_id')
                        ->order_by('-id')
                        ->limit(1)
                    ))
                    ->first()
                    ?: false;
            }
            return $this->$lastuserrespondent;
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getLastMessageDate() {
    
            return $this->thread->lastmessage;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getLastMsgDate() {
            return $this->getLastMessageDate();
        }
    
        function getLastResponseDate() {
    
            return $this->thread->lastresponse;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getLastRespDate() {
            return $this->getLastResponseDate();
        }
    
        function getLastMsgId() {
            return $this->lastMsgId;
        }
    
    
        function getLastMessage() {
    
            if (!isset($this->last_message)) {
    
                if ($this->getLastMsgId())
                    $this->last_message = MessageThreadEntry::lookup(
                        $this->getLastMsgId(), $this->getThreadId());
    
                if (!$this->last_message)
    
                    $this->last_message = $this->getThread()->getLastMessage();
    
            }
            return $this->last_message;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getNumTasks() {
    
            // FIXME: Implement this after merging Tasks
            return count($this->tasks);
    
    Peter Rotich's avatar
    Peter Rotich committed
        }
    
    
        function getNumOpenTasks() {
            return count($this->tasks->filter(array(
                            'flags__hasbit' => TaskModel::ISOPEN)));
        }
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getThreadId() {
    
            if ($this->thread)
                return $this->thread->id;
    
        function getThread() {
            return $this->thread;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getThreadCount() {
    
            return $this->getClientThread()->count();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getNumMessages() {
    
            return $this->getThread()->getNumMessages();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getNumResponses() {
    
            return $this->getThread()->getNumResponses();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getNumNotes() {
    
            return $this->getThread()->getNumNotes();
    
        function getMessages() {
    
            return $this->getThreadEntries(array('M'));
    
        function getResponses() {
    
            return $this->getThreadEntries(array('R'));
    
        function getNotes() {
    
            return $this->getThreadEntries(array('N'));
    
        function getClientThread() {
    
            return $this->getThreadEntries(array('M', 'R'));
    
        function getThreadEntry($id) {
            return $this->getThread()->getEntry($id);
    
        function getThreadEntries($type=false) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            $entries = $this->getThread()->getEntries();
    
    Peter Rotich's avatar
    Peter Rotich committed
                $entries->filter(array('type__in' => $type));
    
            return $entries;
    
        // MailingList of participants  (owner + collaborators)
        function getRecipients($who='all', $whitelist=array(), $active=true) {
            $list = new MailingList();
            switch (strtolower($who)) {
                case 'user':
                    $list->addTo($this->getOwner());
                    break;
                case 'all':
                    $list->addTo($this->getOwner());
                    // Fall-trough
                case 'collabs':
                    if (($collabs = $active ?  $this->getActiveCollaborators() :
                        $this->getCollaborators())) {
                        foreach ($collabs as $c)
                            if (!$whitelist || in_array($c->getUserId(),
                                        $whitelist))
                                $list->addCc($c);
                    }
                    break;
                default:
                    return null;
    
            return $list;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getCollaborators() {
            return $this->getThread()->getCollaborators();
        }
    
        function getNumCollaborators() {
            return $this->getThread()->getNumCollaborators();
        }
    
        function getActiveCollaborators() {
            return $this->getThread()->getActiveCollaborators();
        }
    
        function getNumActiveCollaborators() {
            return $this->getThread()->getNumActiveCollaborators();
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getAssignmentForm($source=null, $options=array()) {
    
            $prompt = $assignee = '';
            // Possible assignees
    
            switch (strtolower($options['target'])) {
                case 'agents':
    
                    $dept = $this->getDept();
    
                    foreach ($dept->getAssignees() as $member)
                        $assignees['s'.$member->getId()] = $member;
    
                    if (!$source && $this->isOpen() && $this->staff)
                        $assignee = sprintf('s%d', $this->staff->getId());
                    $prompt = __('Select an Agent');
                    break;
                case 'teams':
    
                    if (($teams = Team::getActiveTeams()))
    
                        foreach ($teams as $id => $name)
                            $assignees['t'.$id] = $name;
    
                    if (!$source && $this->isOpen() && $this->team)
    
    Peter Rotich's avatar
    Peter Rotich committed
                        $assignee = sprintf('t%d', $this->team->getId());
    
                    $prompt = __('Select a Team');
                    break;
            }
    
            // Default to current assignee if source is not set
    
    Peter Rotich's avatar
    Peter Rotich committed
            if (!$source)
    
                $source = array('assignee' => array($assignee));
    
            $form = AssignmentForm::instantiate($source, $options);
    
                $form->setAssignees($assignees);
    
    
            if (($refer = $form->getField('refer'))) {
                if ($assignee) {
                    $visibility = new VisibilityConstraint(
                            new Q(array()), VisibilityConstraint::HIDDEN);
                    $refer->set('visibility', $visibility);
                } else {
                    $refer->configure('desc', sprintf(__('Maintain referral access to %s'),
                            $this->getAssigned()));
                }
            }
    
    
    
            if ($prompt && ($f=$form->getField('assignee')))
                $f->configure('prompt', $prompt);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $form;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getReferralForm($source=null, $options=array()) {
    
            $form = ReferralForm::instantiate($source, $options);
    
            $dept = $this->getDept();
            // Agents
            $staff = Staff::objects()->filter(array(
             'isactive' => 1,
             ))
             ->filter(Q::not(array('dept_id' => $dept->getId())));
            $staff = Staff::nsort($staff);
            $agents = array();
            foreach ($staff as $s)
              $agents[$s->getId()] = $s;
            $form->setChoices('agent', $agents);
            // Teams
            $form->setChoices('team', Team::getActiveTeams());
            // Depts
            $form->setChoices('dept', Dept::getDepartments());
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            return $form;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getClaimForm($source=null, $options=array()) {
            global $thisstaff;
    
    Peter Rotich's avatar
    Peter Rotich committed
            $id = sprintf('s%d', $thisstaff->getId());
            if(!$source)
                $source = array('assignee' => array($id));
    
    Peter Rotich's avatar
    Peter Rotich committed
            $form = ClaimForm::instantiate($source, $options);
            $form->setAssignees(array($id => $thisstaff->getName()));
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getTransferForm($source=null) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            if (!$source)
    
                $source = array('dept' => array($this->getDeptId()),
                        'refer' => false);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            return TransferForm::instantiate($source);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getField($fid) {
    
            if (is_numeric($fid))
                return $this->getDymanicFieldById($fid);
    
            // Special fields
            switch ($fid) {
            case 'priority':
    
                if (($a = $this->getAnswer('priority')))