Skip to content
Snippets Groups Projects
class.ticket.php 119 KiB
Newer Older
        $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;
    }
    function setLastMessage($message) {
        $this->last_message = $message;
        $this->setLastMsgId($message->getId());
    }
Jared Hancock's avatar
Jared Hancock committed

    //DeptId can NOT be 0. No orphans please!
Jared Hancock's avatar
Jared Hancock committed
        //Make sure it's a valid department//
        if(!($dept=Dept::lookup($deptId)) || $dept->getId()==$this->getDeptId())
Jared Hancock's avatar
Jared Hancock committed
            return false;

Jared Hancock's avatar
Jared Hancock committed
        $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), dept_id='.db_input($deptId)
            .' WHERE ticket_id='.db_input($this->getId());

        return (db_query($sql) && db_affected_rows());
    }
Jared Hancock's avatar
Jared Hancock committed
    //Set staff ID...assign/unassign/release (id can be 0)
Peter Rotich's avatar
Peter Rotich committed
    function setStaffId($staffId) {

        if(!is_numeric($staffId)) return false;
        $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), staff_id='.db_input($staffId)
            .' WHERE ticket_id='.db_input($this->getId());
Peter Rotich's avatar
Peter Rotich committed
        if (!db_query($sql)  || !db_affected_rows())
            return false;

        $this->staff = null;
        $this->ht['staff_id'] = $staffId;

Peter Rotich's avatar
Peter Rotich committed
        return true;
Jared Hancock's avatar
Jared Hancock committed
    }

    function setSLAId($slaId) {
        if ($slaId == $this->getSLAId()) return true;
        $rv = db_query(
Jared Hancock's avatar
Jared Hancock committed
             'UPDATE '.TICKET_TABLE.' SET sla_id='.db_input($slaId)
            .' WHERE ticket_id='.db_input($this->getId()))
            && db_affected_rows();
        if ($rv) {
            $this->ht['sla_id'] = $slaId;
            $this->sla = null;
        }
        return $rv;
Jared Hancock's avatar
Jared Hancock committed
    }
    /**
     * Selects the appropriate service-level-agreement plan for this ticket.
     * When tickets are transfered between departments, the SLA of the new
Jared Hancock's avatar
Jared Hancock committed
     * department should be applied to the ticket. This would be useful,
Jared Hancock's avatar
Jared Hancock committed
     * for instance, if the ticket is transferred to a different department
     * which has a shorter grace period, the ticket should be considered
     * overdue in the shorter window now that it is owned by the new
     * department.
     *
     * $trump - if received, should trump any other possible SLA source.
     *          This is used in the case of email filters, where the SLA
     *          specified in the filter should trump any other SLA to be
     *          considered.
     */
    function selectSLAId($trump=null) {
        global $cfg;
        # XXX Should the SLA be overridden if it was originally set via an
Jared Hancock's avatar
Jared Hancock committed
        #     email filter? This method doesn't consider such a case
Jared Hancock's avatar
Jared Hancock committed
            $slaId = $trump;
        } elseif ($this->getDept() && $this->getDept()->getSLAId()) {
Jared Hancock's avatar
Jared Hancock committed
            $slaId = $this->getDept()->getSLAId();
        } elseif ($this->getTopic() && $this->getTopic()->getSLAId()) {
Jared Hancock's avatar
Jared Hancock committed
            $slaId = $this->getTopic()->getSLAId();
        } else {
            $slaId = $cfg->getDefaultSLAId();
        }
Jared Hancock's avatar
Jared Hancock committed
        return ($slaId && $this->setSLAId($slaId)) ? $slaId : false;
    }

    //Set team ID...assign/unassign/release (id can be 0)
Peter Rotich's avatar
Peter Rotich committed
    function setTeamId($teamId) {
Peter Rotich's avatar
Peter Rotich committed
        if(!is_numeric($teamId)) return false;
Peter Rotich's avatar
Peter Rotich committed
        $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), team_id='.db_input($teamId)
            .' WHERE ticket_id='.db_input($this->getId());
Peter Rotich's avatar
Peter Rotich committed
        return (db_query($sql)  && db_affected_rows());
Jared Hancock's avatar
Jared Hancock committed
    }

    //Status helper.

    function setStatus($status, $comments='', &$errors=array(), $set_closing_agent=true) {
        global $thisstaff;
        if ($thisstaff && !($role=$thisstaff->getRole($this->getDeptId())))
        if ($status && is_numeric($status))
            $status = TicketStatus::lookup($status);
        if (!$status || !$status instanceof TicketStatus)
            return false;

        // Double check permissions (when changing status)
        if ($role && $this->getStatusId()) {
            switch ($status->getState()) {
            case 'closed':
                if (!($role->hasPerm(TicketModel::PERM_CLOSE)))
                    return false;
                break;
            case 'deleted':
                // XXX: intercept deleted status and do hard delete
                if ($role->hasPerm(TicketModel::PERM_DELETE))
                    return $this->delete($comments);
                // Agent doesn't have permission to delete  tickets
        $hadStatus = $this->getStatusId();
        if ($this->getStatusId() == $status->getId())
            return true;

        $sql = 'UPDATE '.TICKET_TABLE.' SET updated=NOW() '.
               ' ,status_id='.db_input($status->getId());

        //TODO: move this up.
        $ecb = null;
        switch($status->getState()) {
Jared Hancock's avatar
Jared Hancock committed
            case 'closed':
                if ($this->getMissingRequiredFields()) {
                    $errors['err'] = sprintf(__(
                        'This ticket is missing data on %s one or more required fields %s and cannot be closed'),
                    '', '');
                    return false;
                }
                $sql.=', closed=NOW(), lastupdate=NOW(), duedate=NULL ';
                if ($thisstaff && $set_closing_agent)
                    $sql.=', staff_id='.db_input($thisstaff->getId());

                $ecb = function($t) {
                    $t->reload();
                    $t->logEvent('closed');
                    $t->deleteDrafts();
                };
                break;
            case 'open':
                // TODO: check current status if it allows for reopening
                if ($this->isClosed()) {
                    $sql .= ',closed=NULL, lastupdate=NOW(), reopened=NOW() ';
                    $ecb = function ($t) {
                        $t->logEvent('reopened', false, 'closed');

                // If the ticket is not open then clear answered flag
                if (!$this->isOpen())
                    $sql .= ', isanswered = 0 ';
Jared Hancock's avatar
Jared Hancock committed
                break;
        $sql.=' WHERE ticket_id='.db_input($this->getId());

        if (!db_query($sql) || !db_affected_rows())
            return false;

        // Log status change b4 reload — if currently has a status. (On new
        // ticket, the ticket is opened and thereafter the status is set to
        // the requested status).
        if ($current_status = $this->getStatus()) {
            $note = sprintf(__('Status changed from %1$s to %2$s by %3$s'),
                    $this->getStatus(),
                    $status,
                    $thisstaff ?: 'SYSTEM');

            $alert = false;
            if ($comments) {
                $note .= sprintf('<hr>%s', $comments);
                // Send out alerts if comments are included
                $alert = true;
                $this->logNote(__('Status Changed'), $note, $thisstaff, $alert);
        // Log events via callback
        if ($ecb)
            $ecb($this);
        elseif ($hadStatus)
            // Don't log the initial status change
            $this->logEvent('edited', array('status' => $status->getId()));

        return true;
Jared Hancock's avatar
Jared Hancock committed
    }

    function setState($state, $alerts=false) {

        switch(strtolower($state)) {
            case 'open':
                return $this->setStatus('open');
                break;
            case 'closed':
                return $this->setStatus('closed');
                break;
            case 'answered':
                return $this->setAnsweredState(1);
                break;
            case 'unanswered':
                return $this->setAnsweredState(0);
                break;
            case 'overdue':
                return $this->markOverdue();
                break;
Peter Rotich's avatar
Peter Rotich committed
            case 'notdue':
                return $this->clearOverdue();
                break;
            case 'unassined':
                return $this->unassign();
Jared Hancock's avatar
Jared Hancock committed
        }

        return false;
    }




    function setAnsweredState($isanswered) {

        $sql='UPDATE '.TICKET_TABLE.' SET isanswered='.db_input($isanswered)
            .' WHERE ticket_id='.db_input($this->getId());

        return (db_query($sql) && db_affected_rows());
    }

    function reopen() {
        global $cfg;
        if (!$this->isClosed())
            return false;

        // Set status to open based on current closed status settings
        // If the closed status doesn't have configured "reopen" status then use the
        // the default ticket status.
        if (!($status=$this->getStatus()->getReopenStatus()))
            $status = $cfg->getDefaultTicketStatusId();

        return $status ? $this->setStatus($status, 'Reopened') : false;
Jared Hancock's avatar
Jared Hancock committed
    }

    function onNewTicket($message, $autorespond=true, $alertstaff=true) {
        global $cfg;

        //Log stuff here...
Jared Hancock's avatar
Jared Hancock committed
        if(!$autorespond && !$alertstaff) return true; //No alerts to send.

        /* ------ SEND OUT NEW TICKET AUTORESP && ALERTS ----------*/
Jared Hancock's avatar
Jared Hancock committed
        $this->reload(); //get the new goodies.
        if(!$cfg
                || !($dept=$this->getDept())
                || !($tpl = $dept->getTemplate())
                || !($email=$dept->getAutoRespEmail())) {
                return false;  //bail out...missing stuff.
        }
        $options = array();
        if ($message instanceof ThreadEntry) {
            $options += array(
                'inreplyto'=>$message->getEmailMessageId(),
                'references'=>$message->getEmailReferences(),
                'thread'=>$message
            );
        }
        else {
            $options += array(
                'thread' => $this->getThread(),
            );
        }
Jared Hancock's avatar
Jared Hancock committed
        //Send auto response - if enabled.
        if($autorespond
                && $cfg->autoRespONNewTicket()
Jared Hancock's avatar
Jared Hancock committed
                &&  ($msg=$tpl->getAutoRespMsgTemplate())) {
            $msg = $this->replaceVars($msg->asArray(),
                    array('message' => $message,
                          'recipient' => $this->getOwner(),
                          'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')
                    );

            $email->sendAutoReply($this->getOwner(), $msg['subj'], $msg['body'],
Jared Hancock's avatar
Jared Hancock committed
        //Send alert to out sleepy & idle staff.
                && ($email=$dept->getAlertEmail())
Jared Hancock's avatar
Jared Hancock committed
                && ($msg=$tpl->getNewTicketAlertMsgTemplate())) {
            $msg = $this->replaceVars($msg->asArray(), array('message' => $message));
Jared Hancock's avatar
Jared Hancock committed
            $recipients=$sentlist=array();
            //Exclude the auto responding email just incase it's from staff member.
            if ($message instanceof ThreadEntry && $message->isAutoReply())
                $sentlist[] = $this->getEmail();

Jared Hancock's avatar
Jared Hancock committed
            //Alert admin??
            if($cfg->alertAdminONNewTicket()) {
                $alert = $this->replaceVars($msg, array('recipient' => 'Admin'));
                $email->sendAlert($cfg->getAdminEmail(), $alert['subj'], $alert['body'], null, $options);
Jared Hancock's avatar
Jared Hancock committed
                $sentlist[]=$cfg->getAdminEmail();
            }
Jared Hancock's avatar
Jared Hancock committed
            //Only alerts dept members if the ticket is NOT assigned.
            if($cfg->alertDeptMembersONNewTicket() && !$this->isAssigned()) {
                if(($members=$dept->getMembersForAlerts()))
Jared Hancock's avatar
Jared Hancock committed
                    $recipients=array_merge($recipients, $members);
            }
Jared Hancock's avatar
Jared Hancock committed
            if($cfg->alertDeptManagerONNewTicket() && $dept && ($manager=$dept->getManager()))
                $recipients[]= $manager;
            // Account manager
            if ($cfg->alertAcctManagerONNewMessage()
                    && ($org = $this->getOwner()->getOrganization())
                    && ($acct_manager = $org->getAccountManager())) {
                if ($acct_manager instanceof Team)
                    $recipients = array_merge($recipients, $acct_manager->getMembers());
                else
                    $recipients[] = $acct_manager;
            }

            foreach( $recipients as $k=>$staff) {
                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                $alert = $this->replaceVars($msg, array('recipient' => $staff));
                $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
Peter Rotich's avatar
Peter Rotich committed
                $sentlist[] = $staff->getEmail();
Jared Hancock's avatar
Jared Hancock committed
        return true;
    }

    function onOpenLimit($sendNotice=true) {

        //Log the limit notice as a warning for admin.
        $msg=sprintf(_S('Maximum open tickets (%1$d) reached for %2$s'),
            $cfg->getMaxOpenTickets(), $this->getEmail());
        $ost->logWarning(sprintf(_S('Maximum Open Tickets Limit (%s)'),$this->getEmail()),
            $msg);
        if(!$sendNotice || !$cfg->sendOverLimitNotice())
            return true;

        //Send notice to user.
        if(($dept = $this->getDept())
            && ($tpl=$dept->getTemplate())
            && ($msg=$tpl->getOverlimitMsgTemplate())
            && ($email=$dept->getAutoRespEmail())) {
            $msg = $this->replaceVars($msg->asArray(),
                        array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():''));
            $email->sendAutoReply($this->getOwner(), $msg['subj'], $msg['body']);
        $user = $this->getOwner();
        //Alert admin...this might be spammy (no option to disable)...but it is helpful..I think.
        $alert=sprintf(__('Maximum open tickets reached for %s.'), $this->getEmail())."\n"
              .sprintf(__('Open tickets: %d'), $user->getNumOpenTickets())."\n"
              .sprintf(__('Max allowed: %d'), $cfg->getMaxOpenTickets())
              ."\n\n".__("Notice sent to the user.");
        $ost->alertAdmin(__('Overlimit Notice'), $alert);
Peter Rotich's avatar
Peter Rotich committed
    function onResponse($response, $options=array()) {
        db_query('UPDATE '.TICKET_TABLE.' SET isanswered=1, lastresponse=NOW(), updated=NOW() WHERE ticket_id='.db_input($this->getId()));
        $this->reload();
Peter Rotich's avatar
Peter Rotich committed
        $vars = array_merge($options,
                array(
                    'activity' => _S('New Response'),
                    'threadentry' => $response));

        $this->onActivity($vars);
    /*
     * Notify collaborators on response or new message
     *
     */

    function  notifyCollaborators($entry, $vars = array()) {
        if (!$entry instanceof ThreadEntry
                || !($recipients=$this->getRecipients())
                || !($dept=$this->getDept())
                || !($tpl=$dept->getTemplate())
                || !($msg=$tpl->getActivityNoticeMsgTemplate())
                || !($email=$dept->getEmail()))
            return;

        //Who posted the entry?
        if ($entry instanceof Message) {
            $poster = $entry->getUser();
            // Skip the person who sent in the message
            $skip[$entry->getUserId()] = 1;
            // Skip all the other recipients of the message
            foreach ($entry->getAllEmailRecipients() as $R) {
                foreach ($recipients as $R2) {
                    if (0 === strcasecmp($R2->getEmail(), $R->mailbox.'@'.$R->host)) {
            $poster = $entry->getStaff();
            // Skip the ticket owner
            $skip[$this->getUserId()] = 1;
        $vars = array_merge($vars, array(
                    'message' => (string) $entry,
                    'poster' => $poster ?: _S('A collaborator'),
        $msg = $this->replaceVars($msg->asArray(), $vars);
        $attachments = $cfg->emailAttachments()?$entry->getAttachments():array();
        $options = array('inreplyto' => $entry->getEmailMessageId(),
                         'thread' => $entry);
        foreach ($recipients as $recipient) {
            // Skip folks who have already been included on this part of
            // the conversation
            if (isset($skip[$recipient->getUserId()]))
                continue;
            $notice = $this->replaceVars($msg, array('recipient' => $recipient));
            $email->send($recipient, $notice['subj'], $notice['body'], $attachments,
    function onMessage($message, $autorespond=true) {
Jared Hancock's avatar
Jared Hancock committed
        global $cfg;

        db_query('UPDATE '.TICKET_TABLE.' SET isanswered=0,lastupdate=NOW(),lastmessage=NOW() WHERE ticket_id='.db_input($this->getId()));
        // Auto-assign to closing staff or last respondent
        // If the ticket is closed and auto-claim is not enabled then put the
        // ticket back to unassigned pool.
        if ($this->isClosed() && !$cfg->autoClaimTickets()) {
            $this->setStaffId(0);
        } elseif(!($staff=$this->getStaff()) || !$staff->isAvailable()) {
            // Ticket has no assigned staff -  if auto-claim is enabled then
            // try assigning it to the last respondent (if available)
            // otherwise leave the ticket unassigned.
            if ($cfg->autoClaimTickets() //Auto claim is enabled.
                    && ($lastrep=$this->getLastRespondent())
                    && $lastrep->isAvailable()) {
Jared Hancock's avatar
Jared Hancock committed
                $this->setStaffId($lastrep->getId()); //direct assignment;
            } else {
                $this->setStaffId(0); //unassign - last respondent is not available.
            }
        }

        // Reopen if closed AND reopenable
        // We're also checking autorespond flag because we don't want to
        // reopen closed tickets on auto-reply from end user. This is not to
        // confused with autorespond on new message setting
        if ($autorespond && $this->isClosed() && $this->isReopenable())
        // Figure out the user
        if ($this->getOwnerId() == $message->getUserId())
            $user = new TicketOwner(
                    User::lookup($message->getUserId()), $this);
        else
            $user = Collaborator::lookup(array(
                    'user_id' => $message->getUserId(),
                    'thread_id' => $this->getThreadId()));

        /**********   double check auto-response  ************/
        if (!$user)
            $autorespond=false;
        elseif ($autorespond && (Email::getIdByEmail($user->getEmail())))
Jared Hancock's avatar
Jared Hancock committed
            $autorespond=false;
        elseif ($autorespond && ($dept=$this->getDept()))
Jared Hancock's avatar
Jared Hancock committed
            $autorespond=$dept->autoRespONNewMessage();


        if(!$autorespond
                || !$cfg->autoRespONNewMessage()
                || !$message) return;  //no autoresp or alerts.

Jared Hancock's avatar
Jared Hancock committed
        $this->reload();
        $dept = $this->getDept();
        $email = $dept->getAutoRespEmail();
Jared Hancock's avatar
Jared Hancock committed
        //If enabled...send confirmation to user. ( New Message AutoResponse)
        if($email
                && ($tpl=$dept->getTemplate())
                && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) {
            $msg = $this->replaceVars($msg->asArray(),
                            array(
                                'recipient' => $user,
                                'signature' => ($dept && $dept->isPublic())?$dept->getSignature():''));
                'inreplyto'=>$message->getEmailMessageId(),
                'thread'=>$message);
            $email->sendAutoReply($user, $msg['subj'], $msg['body'],
Peter Rotich's avatar
Peter Rotich committed
    function onActivity($vars, $alert=true) {
        global $cfg, $thisstaff;

        //TODO: do some shit

        if (!$alert // Check if alert is enabled
                || !$cfg->alertONNewActivity()
                || !($dept=$this->getDept())
                || !($email=$cfg->getAlertEmail())
                || !($tpl = $dept->getTemplate())
                || !($msg=$tpl->getNoteAlertMsgTemplate()))
            return;

        // Alert recipients
        $recipients=array();

        //Last respondent.
        if ($cfg->alertLastRespondentONNewActivity())
            $recipients[] = $this->getLastRespondent();

        // Assigned staff / team
        if ($cfg->alertAssignedONNewActivity()) {

            if (isset($vars['assignee'])
                    && $vars['assignee'] instanceof Staff)
                 $recipients[] = $vars['assignee'];
            elseif ($this->isOpen() && ($assignee = $this->getStaff()))
                $recipients[] = $assignee;

            if ($team = $this->getTeam())
                $recipients = array_merge($recipients, $team->getMembers());
        }

        // Dept manager
        if ($cfg->alertDeptManagerONNewActivity() && $dept && $dept->getManagerId())
            $recipients[] = $dept->getManager();

        $options = array();
        $staffId = $thisstaff ? $thisstaff->getId() : 0;
        if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) {
            $options = array(
                'inreplyto' => $vars['threadentry']->getEmailMessageId(),
                'references' => $vars['threadentry']->getEmailReferences(),
                'thread' => $vars['threadentry']);

            // Activity details
            if (!$vars['comments'])
                $vars['comments'] = $vars['threadentry'];

            // Staff doing the activity
            $staffId = $vars['threadentry']->getStaffId() ?: $staffId;
        }

        $msg = $this->replaceVars($msg->asArray(),
                array(
                    'note' => $vars['threadentry'], // For compatibility
Peter Rotich's avatar
Peter Rotich committed
                    'activity' => $vars['activity'],
                    'comments' => $vars['comments']));

        $isClosed = $this->isClosed();
        $sentlist=array();
        foreach ($recipients as $k=>$staff) {
            if (!is_object($staff)
                    // Don't bother vacationing staff.
                    || !$staff->isAvailable()
                    // No need to alert the poster!
                    || $staffId == $staff->getId()
                    // No duplicates.
                    || isset($sentlist[$staff->getEmail()])
                    // Make sure staff has access to ticket
                    || ($isClosed && !$this->checkStaffPerm($staff))
Peter Rotich's avatar
Peter Rotich committed
                    )
                continue;
            $alert = $this->replaceVars($msg, array('recipient' => $staff));
            $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options);
            $sentlist[$staff->getEmail()] = 1;
        }

    }

    function onAssign($assignee, $comments, $alert=true) {
Jared Hancock's avatar
Jared Hancock committed
        global $cfg, $thisstaff;
Jared Hancock's avatar
Jared Hancock committed

        if($this->isClosed()) $this->reopen(); //Assigned tickets must be open - otherwise why assign?

        //Assignee must be an object of type Staff or Team
        if(!$assignee || !is_object($assignee)) return false;

Jared Hancock's avatar
Jared Hancock committed
        $this->reload();

        $user_comments = (bool) $comments;
        $comments = $comments ?: _S('Ticket assignment');
        $assigner = $thisstaff ?: _S('SYSTEM (Auto Assignment)');
        //Log an internal note - no alerts on the internal note.
        if ($user_comments) {
            $note = $this->logNote(
                sprintf(_S('Ticket Assigned to %s'), $assignee->getName()),
                $comments, $assigner, false);
        }
Jared Hancock's avatar
Jared Hancock committed

        //See if we need to send alerts
        if(!$alert || !$cfg->alertONAssignment()) return true; //No alerts!

        $dept = $this->getDept();
        if(!$dept
                || !($tpl = $dept->getTemplate())
                || !($email = $dept->getAlertEmail()))

        //recipients
        $recipients=array();
        if ($assignee instanceof Staff) {
            if ($cfg->alertStaffONAssignment())
                $recipients[] = $assignee;
        } elseif (($assignee instanceof Team) && $assignee->alertsEnabled()) {
            if ($cfg->alertTeamMembersONAssignment() && ($members=$assignee->getMembers()))
                $recipients = array_merge($recipients, $members);
            elseif ($cfg->alertTeamLeadONAssignment() && ($lead=$assignee->getTeamLead()))
                $recipients[] = $lead;
        }
Jared Hancock's avatar
Jared Hancock committed

        //Get the message template
        if ($recipients
                && ($msg=$tpl->getAssignedAlertMsgTemplate())) {
            $msg = $this->replaceVars($msg->asArray(),
                        array('comments' => $comments,
                              'assignee' => $assignee,
                              'assigner' => $assigner
Jared Hancock's avatar
Jared Hancock committed
            //Send the alerts.
            $sentlist=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);
Peter Rotich's avatar
Peter Rotich committed
                $sentlist[] = $staff->getEmail();
Jared Hancock's avatar
Jared Hancock committed
   function onOverdue($whine=true, $comments="") {
Jared Hancock's avatar
Jared Hancock committed
        global $cfg;

        if($whine && ($sla=$this->getSLA()) && !$sla->alertOnOverdue())
            $whine = false;
Jared Hancock's avatar
Jared Hancock committed

        //check if we need to send alerts.
        if(!$whine
                || !$cfg->alertONOverdueTicket()
                || !($dept = $this->getDept()))
Jared Hancock's avatar
Jared Hancock committed
            return true;

        //Get the message template
        if(($tpl = $dept->getTemplate())
                && ($msg=$tpl->getOverdueAlertMsgTemplate())
                && ($email = $dept->getAlertEmail())) {
            $msg = $this->replaceVars($msg->asArray(),
                array('comments' => $comments));
Jared Hancock's avatar
Jared Hancock committed

            //recipients
            $recipients=array();
            //Assigned staff or team... if any
            if($this->isAssigned() && $cfg->alertAssignedONOverdueTicket()) {
Jared Hancock's avatar
Jared Hancock committed
                if($this->getStaffId())
                    $recipients[]=$this->getStaff();
                elseif($this->getTeamId() && ($team=$this->getTeam()) && ($members=$team->getMembers()))
                    $recipients=array_merge($recipients, $members);
            } elseif($cfg->alertDeptMembersONOverdueTicket() && !$this->isAssigned()) {
                //Only alerts dept members if the ticket is NOT assigned.
                if ($members = $dept->getMembersForAlerts())
                    $recipients = array_merge($recipients, $members);
Jared Hancock's avatar
Jared Hancock committed
            }
            //Always alert dept manager??
            if($cfg->alertDeptManagerONOverdueTicket() && $dept && ($manager=$dept->getManager()))
                $recipients[]= $manager;

            $sentlist=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);
Peter Rotich's avatar
Peter Rotich committed
                $sentlist[] = $staff->getEmail();
    // TemplateVariable interface
    function asVar() {
       return $this->getNumber();
    function getVar($tag) {
Jared Hancock's avatar
Jared Hancock committed
        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_number':
                return $this->getPhoneNumber();
                break;
            case 'auth_token':
                return $this->getOldAuthToken();
                break;
            case 'client_link':
                return sprintf('%s/view.php?t=%s',
                        $cfg->getBaseUrl(), $this->getNumber());
                break;
            case 'staff_link':
                return sprintf('%s/scp/tickets.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;
M. Hagen's avatar
M. Hagen committed
            case 'close_date':
                if ($this->isClosed())
                    return new FormattedDate($this->getCloseDate());
                break;
                return new FormattedDate($this->last_update);
            case 'user':
                return $this->getOwner();
Jared Hancock's avatar
Jared Hancock committed
            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];
    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'),
                'class' => 'Dept', 'desc' => __('Department'),
            'due_date' => array(
                'class' => 'FormattedDate', 'desc' => __('Due Date'),
            'email' => __('Default email address of ticket owner'),
            'name' => array(
                'class' => 'PersonsName', 'desc' => __('Name of ticket owner'),
            ),
            'number' => __('Ticket number'),
            'phone' => __('Phone number of ticket owner'),
            'priority' => array(
                'class' => 'Priority', 'desc' => __('Priority'),
            ),
            'recipients' => array(
                'class' => 'UserList', 'desc' => __('List of all recipient names'),
            'source' => __('Source'),
            'status' => array(
                'class' => 'TicketStatus', 'desc' => __('Status'),
            ),
            'staff' => array(
                'class' => 'Staff', 'desc' => __('Assigned/closing agent'),
            ),
            'subject' => 'Subject',
            'team' => array(
                'class' => 'Team', 'desc' => __('Assigned/closing team'),
            ),
            'thread' => array(
                'class' => 'TicketThread', 'desc' => __('Ticket Thread'),
                'class' => 'Topic', 'desc' => __('Help topic'),
            ),
            // XXX: Isn't lastreponse and lastmessage more useful
            'last_update' => array(
                'class' => 'FormattedDate', 'desc' => __('Time of last update'),
            ),
            'user' => array(
                'class' => 'User', 'desc' => __('Ticket owner'),
            ),
        );

        $extra = VariableReplacer::compileFormScope(TicketForm::getInstance());
        return $base + $extra;
    }

    //Replace base variables.
    function replaceVars($input, $vars = array()) {
        global $ost;

        $vars = array_merge($vars, array('ticket' => $this));

        return $ost->replaceTemplateVariables($input, $vars);
Jared Hancock's avatar
Jared Hancock committed
    }

    function markUnAnswered() {
        return (!$this->isAnswered() || $this->setAnsweredState(0));
    }

    function markAnswered() {
        return ($this->isAnswered() || $this->setAnsweredState(1));
    }

    function markOverdue($whine=true) {
Jared Hancock's avatar
Jared Hancock committed
        global $cfg;
Jared Hancock's avatar
Jared Hancock committed
        if($this->isOverdue())
            return true;

        $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=1, updated=NOW() '
Jared Hancock's avatar
Jared Hancock committed
            .' WHERE ticket_id='.db_input($this->getId());

        if(!db_query($sql) || !db_affected_rows())
            return false;

        $this->logEvent('overdue');
Jared Hancock's avatar
Jared Hancock committed
        $this->onOverdue($whine);
Jared Hancock's avatar
Jared Hancock committed
        return true;
    }

Peter Rotich's avatar
Peter Rotich committed
    function clearOverdue() {

Peter Rotich's avatar
Peter Rotich committed
            return true;

        //NOTE: Previously logged overdue event is NOT annuled.

Peter Rotich's avatar
Peter Rotich committed
        $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=0, updated=NOW() ';
Peter Rotich's avatar
Peter Rotich committed
        //clear due date if it's in the past
        if($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime())
Peter Rotich's avatar
Peter Rotich committed
            $sql.=', duedate=NULL';

        //Clear SLA if est. due date is in the past
        if($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime())
Peter Rotich's avatar
Peter Rotich committed
        $sql.=' WHERE ticket_id='.db_input($this->getId());

        return (db_query($sql) && db_affected_rows());
    }

    //Dept Tranfer...with alert.. done by staff
Jared Hancock's avatar
Jared Hancock committed
    function transfer($deptId, $comments, $alert = true) {
Jared Hancock's avatar
Jared Hancock committed
        global $cfg, $thisstaff;
        if (!$this->checkStaffPerm($thisstaff, TicketModel::PERM_TRANSFER))
Jared Hancock's avatar
Jared Hancock committed
            return false;

        $currentDept = $this->getDeptName(); //Current department

        if(!$deptId || !$this->setDeptId($deptId))
            return false;
        if($this->isClosed()) $this->reopen();

        $this->reload();
Peter Rotich's avatar
Peter Rotich committed
        $dept = $this->getDept();
Jared Hancock's avatar
Jared Hancock committed
        if(!$this->getSLAId() || $this->getSLA()->isTransient())
Peter Rotich's avatar
Peter Rotich committed
        // Make sure the new department allows assignment to the
        // currently assigned agent (if any)
        if ($this->isAssigned()
                && ($staff=$this->getStaff())
                && $dept->assignMembersOnly()
                && !$dept->isMember($staff)) {
            $this->setStaffId(0);
        }

        /*** log the transfer comments as internal note - with alerts disabled - ***/
        $title=sprintf(_S('Ticket transferred from %1$s to %2$s'),
            $currentDept, $this->getDeptName());

        if ($comments) {
            $note = $this->logNote($title, $comments, $thisstaff, false);
        }
        $comments = $comments ?: $title;
        $this->logEvent('transferred');
        //Send out alerts if enabled AND requested
Peter Rotich's avatar
Peter Rotich committed
        if (!$alert || !$cfg->alertONTransfer())
            return true; //no alerts!!
         if (($email = $dept->getAlertEmail())
                     && ($tpl = $dept->getTemplate())
                     && ($msg=$tpl->getTransferAlertMsgTemplate())) {
            $msg = $this->replaceVars($msg->asArray(),
                array('comments' => $comments, 'staff' => $thisstaff));