Skip to content
Snippets Groups Projects
class.ticket.php 125 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_TRANSFER = 'ticket.transfer';
        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_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_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();
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function isClosed() {
    
             return $this->hasState('closed');
        }
    
    
        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 ticket'),
                        '', '');
            } elseif (($num=$this->getNumOpenTasks())) {
                $warning = sprintf(__('%1$s has %2$d open tasks and cannot be closed'),
                        __('This ticket'), $num);
            }
    
            return $warning ?: true;
        }
    
    
        function isArchived() {
             return $this->hasState('archived');
        }
    
        function isDeleted() {
             return $this->hasState('deleted');
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function isAssigned() {
    
            return $this->isOpen() && ($this->getStaffId() || $this->getTeamId());
    
    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 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 access based on department or assignment
    
            if (($staff->showAssignedOnly()
                || !$staff->canAccessDept($this->getDeptId()))
                // only open tickets can be considered assigned
                && $this->isOpen()
                && $staff->getId() != $this->getStaffId()
                && !$staff->isTeamMember($this->getTeamId())
            ) {
    
    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 (!($role=$staff->getRole($this->getDeptId())))
    
            // 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() {
    
            if ($sla = $this->getSLA()) {
                $dt = new DateTime($this->getCreateDate());
    
                return $dt
                    ->add(new DateInterval('PT' . $sla->getGracePeriod() . 'H'))
                    ->format('Y-m-d H:i:s');
            }
    
        function updateEstDueDate() {
    
            $this->est_duedate = $this->getEstDueDate();
            $this->save();
    
        }
    
        function getEstDueDate() {
    
            // Real due date
            if ($duedate = $this->getDueDate()) {
    
                return $duedate;
    
            }
            // return sla due date (If ANY)
    
            return $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() {
    
            if (($a = $this->getAnswer('priority'))
    
                && ($b = $a->getValue())
            ) {
    
            return $cfg->getDefaultPriorityId();
    
        function getPriority() {
    
            if (($a = $this->getAnswer('priority')) && ($b = $a->getValue()))
    
                return $b->getDesc();
            return '';
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getPhoneNumber() {
    
            return (string)$this->getOwner()->getPhoneNumber();
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getSource() {
    
            return $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(
                'source'    => $this->getSource(),
                '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;
    
    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;
    
        //UserList of recipients  (owner + collaborators)
        function getRecipients() {
            if (!isset($this->recipients)) {
                $list = new UserList();
                $list->add($this->getOwner());
    
                if ($collabs = $this->getThread()->getActiveCollaborators()) {
    
                    foreach ($collabs as $c)
                        $list->add($c);
                }
                $this->recipients = $list;
            }
            return $this->recipients;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getAssignmentForm($source=null, $options=array()) {
    
            $prompt = $assignee = '';
            // Possible assignees
            $assignees = array();
            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);
    
            if ($assignees)
                $form->setAssignees($assignees);
    
            if ($prompt && ($f=$form->getField('assignee')))
                $f->configure('prompt', $prompt);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    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()));
    
            return TransferForm::instantiate($source);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getDynamicFields($criteria=array()) {
    
            $fields = DynamicFormField::objects()->filter(array(
                        'id__in' => $this->entries
                        ->filter($criteria)
                    ->values_flat('answers__field_id')));
    
            return ($fields && count($fields)) ? $fields : array();
        }
    
    
        function hasClientEditableFields() {
            $forms = DynamicFormEntry::forTicket($this->getId());
            foreach ($forms as $form) {
                foreach ($form->getFields() as $field) {
                    if ($field->isEditableToUsers())
                        return true;
    
        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,
                        ));
    
        function getMissingRequiredField() {
            $fields = $this->getMissingRequiredFields();
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $fields ? $fields[0] : null;
    
        function addCollaborator($user, $vars, &$errors, $event=true) {
    
            if (!$user || $user->getId() == $this->getOwnerId())
    
            if ($c = $this->getThread()->addCollaborator($user, $vars, $errors, $event)) {
                $this->collaborators = null;
                $this->recipients = null;
            }
    
            return $c;
        }
    
        //XXX: Ugly for now
        function updateCollaborators($vars, &$errors) {
    
    
            //Deletes
            if($vars['del'] && ($ids=array_filter($vars['del']))) {
    
                $collabs = array();
                foreach ($ids as $k => $cid) {
                    if (($c=Collaborator::lookup($cid))
                            && $c->getTicketId() == $this->getId()
    
                         $collabs[] = (string) $c;
    
                $this->logEvent('collab', array('del' => $collabs));
    
            }
    
            //statuses
            $cids = null;
            if($vars['cid'] && ($cids=array_filter($vars['cid']))) {
    
                $this->getThread()->collaborators->filter(array(
                    'thread_id' => $this->getThreadId(),
                    'id__in' => $cids
                ))->update(array(
                    'updated' => SqlFunction::NOW(),
                    'isactive' => 1,
                ));
    
            if ($cids) {
                $this->getThread()->collaborators->filter(array(
                    'thread_id' => $this->getThreadId(),
                    Q::not(array('id__in' => $cids))
                ))->update(array(
                    'updated' => SqlFunction::NOW(),
                    'isactive' => 0,
                ));
            }
    
            unset($this->active_collaborators);
    
            $this->collaborators = null;
    
            return true;
        }
    
    
        function getAuthToken($user, $algo=1) {
    
            //Format: // <user type><algo id used>x<pack of uid & tid><hash of the algo>
            $authtoken = sprintf('%s%dx%s',
                    ($user->getId() == $this->getOwnerId() ? 'o' : 'c'),
                    $algo,
                    Base32::encode(pack('VV',$user->getId(), $this->getId())));
    
            switch($algo) {
                case 1:
                    $authtoken .= substr(base64_encode(
                                md5($user->getId().$this->getCreateDate().$this->getId().SECRET_SALT, true)), 8);
                    break;
                default:
                    return null;
    
            return $authtoken;
        }
    
        function sendAccessLink($user) {
            global $ost;
    
            if (!($email = $ost->getConfig()->getDefaultEmail())
                || !($content = Page::lookupByType('access-link')))
                return;
    
            $vars = array(
                'url' => $ost->getConfig()->getBaseUrl(),
                'ticket' => $this,
                'user' => $user,
                'recipient' => $user,
            );
    
            $lang = $user->getLanguage(UserAccount::LANG_MAILOUTS);
            $msg = $ost->replaceTemplateVariables(array(
                'subj' => $content->getLocalName($lang),
                'body' => $content->getLocalBody($lang),
            ), $vars);
    
            $email->send($user, Format::striptags($msg['subj']),
                $msg['body']);
    
    Jared Hancock's avatar
    Jared Hancock committed
        /* -------------------- Setters --------------------- */
        function setLastMsgId($msgid) {
            return $this->lastMsgId=$msgid;
        }