diff --git a/include/class.orm.php b/include/class.orm.php index 43df8e29f3ad0acf11fc684c89245f5fd6223d7f..6876be7370dc6a583104663e2bf94cc9157fd6ce 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -43,9 +43,16 @@ class ModelMeta implements ArrayAccess { function __construct($model) { $this->model = $model; - $meta = $model::$meta + self::$base; + $parent = get_parent_class($model); - // TODO: Merge ModelMeta from parent model (if inherited) + // Merge ModelMeta from parent model (if inherited) + if (is_subclass_of($parent, 'VerySimpleModel')) { + $parent::_inspect(); + $meta = $parent::$meta->extend($model::$meta); + } + else { + $meta = $model::$meta + self::$base; + } if (!$meta['table']) throw new OrmConfigurationException( @@ -67,11 +74,19 @@ class ModelMeta implements ArrayAccess { $meta['joins'] = array(); foreach ($meta['joins'] as $field => &$j) { $this->processJoin($j); + if ($j['local']) + $meta['foreign_keys'][$j['local']] = $field; } unset($j); $this->base = $meta; } + function extend($meta) { + if ($meta instanceof self) + $meta = $meta->base; + return $meta + $this->base + self::$base; + } + function processJoin(&$j) { $constraint = array(); if (isset($j['reverse'])) { @@ -266,11 +281,15 @@ class VerySimpleModel { || isset(static::$meta['joins'][$field]); } function __unset($field) { - unset($this->ht[$field]); + if ($this->__isset($field)) + unset($this->ht[$field]); + else + unset($this->{$field}); } function set($field, $value) { // Update of foreign-key by assignment to model instance + $related = false; if (isset(static::$meta['joins'][$field])) { // XXX: This is likely not necessary if (!isset(static::$meta['joins'][$field]['fkey'])) @@ -282,9 +301,9 @@ class VerySimpleModel { return; } if ($value === null) { + $this->ht[$field] = $value; if (in_array($j['local'], static::$meta['pk'])) { // Reverse relationship — don't null out local PK - $this->ht[$field] = $value; return; } // Pass. Set local field to NULL in logic below @@ -307,12 +326,23 @@ class VerySimpleModel { // Capture the foreign key id value $field = $j['local']; } + // elseif $field is in a relationship, adjust the relationship + elseif (isset(static::$meta['foreign_keys'][$field])) { + // meta->foreign_keys->{$field} points to the property of the + // foreign object. For instance 'object_id' points to 'object' + $related = static::$meta['foreign_keys'][$field]; + } $old = isset($this->ht[$field]) ? $this->ht[$field] : null; if ($old != $value) { // isset should not be used here, because `null` should not be // replaced in the dirty array if (!array_key_exists($field, $this->dirty)) $this->dirty[$field] = $old; + if ($related) + // $related points to a foreign object propery. If setting a + // new object_id value, the relationship to object should be + // cleared and rebuilt + unset($this->ht[$related]); } $this->ht[$field] = $value; } @@ -376,6 +406,9 @@ class VerySimpleModel { * no such instance exists. */ static function lookup($criteria) { + // Autoinsepct model + static::_inspect(); + // Model::lookup(1), where >1< is the pk value if (!is_array($criteria)) { $criteria = array(); diff --git a/include/class.staff.php b/include/class.staff.php index 457ab1787462e340b4a9610efb8ff72397732d5e..d23a3704a07a9349e29e8f836f954ec56dce2b4b 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -40,8 +40,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { 'constraint' => array('group_id' => 'Group.id'), ), 'teams' => array( - 'null' => true, - 'list' => true, 'reverse' => 'TeamMember.staff', ), ), diff --git a/include/class.task.php b/include/class.task.php index 36c858e6bcba083bfd148989a040f9910e3341ff..6365d6c57a0cb450ccc668f875445164991ba89b 100644 --- a/include/class.task.php +++ b/include/class.task.php @@ -49,6 +49,13 @@ class TaskModel extends VerySimpleModel { 'constraint' => array('id' => 'TaskCData.task_id'), 'list' => false, ), + 'ticket' => array( + 'constraint' => array( + 'object_type' => "'T'", + 'object_id' => 'Ticket.ticket_id', + ), + 'null' => true, + ), ), ); diff --git a/include/class.ticket.php b/include/class.ticket.php index cc9cbd703ab439d8224634e28ca8a80f7b724bce..57bfe72fe76c3cca15e7e7813929c390d4b724d5 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -53,14 +53,20 @@ class TicketModel extends VerySimpleModel { 'dept' => array( 'constraint' => array('dept_id' => 'Dept.id'), ), + 'events' => array( + 'reverse' => 'TicketEvent.ticket', + ), 'sla' => array( - 'constraint' => array('sla_id' => 'SlaModel.id'), + 'constraint' => array('sla_id' => 'Sla.id'), 'null' => true, ), 'staff' => array( 'constraint' => array('staff_id' => 'Staff.staff_id'), 'null' => true, ), + 'tasks' => array( + 'reverse' => 'Task.ticket', + ), 'team' => array( 'constraint' => array('team_id' => 'Team.team_id'), 'null' => true, @@ -107,7 +113,6 @@ class TicketModel extends VerySimpleModel { 'desc' => /* @trans */ 'Ability to assign tickets to agents or teams'), self::PERM_TRANSFER => array( - 'title' => /* @trans */ 'Transfer', 'desc' => @@ -210,88 +215,30 @@ class TicketCData extends VerySimpleModel { } TicketCData::$meta['table'] = TABLE_PREFIX . 'ticket__cdata'; +class Ticket extends TicketModel +implements RestrictedAccess, Threadable { -class Ticket -implements RestrictedAccess, Threadable, TemplateVariable { - - var $id; - var $number; - - var $ht; + static $meta = array( + 'select_related' => array('topic', 'staff', 'user', 'team', 'dept', 'sla', 'thread'), + ); var $lastMsgId; - var $status; - var $dept; //Dept obj - var $sla; // SLA obj - var $staff; //Staff obj - var $client; //Client Obj - var $team; //Team obj - var $topic; //Topic obj - var $tlock; //TicketLock obj - - var $thread; //Thread obj. - - function Ticket($id) { - $this->id = 0; - $this->load($id); - } - - function load($id=0) { - - if (!$id && !($id=$this->getId())) - return false; - - $sql='SELECT ticket.*, thread.id as thread_id, ticket.lock_id, dept.name as dept_name ' - .' ,count(distinct attach.id) as attachments' - .' ,count(distinct task.id) as tasks' - .' FROM '.TICKET_TABLE.' ticket ' - .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.id) ' - .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.flags & 1 = 1) ' - .' LEFT JOIN '.LOCK_TABLE.' tlock - ON ( ticket.lock_id=tlock.lock_id AND tlock.expire>NOW()) ' - .' LEFT JOIN '.TASK_TABLE.' task - ON ( task.object_id = ticket.ticket_id AND task.object_type="T" ) ' - .' LEFT JOIN '.THREAD_TABLE.' thread - ON ( thread.object_id = ticket.ticket_id AND thread.object_type="T" ) ' - .' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry - ON ( entry.thread_id = thread.id ) ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' attach - ON ( attach.object_id = entry.id AND attach.`type` = "H") ' - .' WHERE ticket.ticket_id='.db_input($id) - .' GROUP BY ticket.ticket_id'; - - //echo $sql; - if (!($res=db_query($sql)) || !db_num_rows($res)) - return false; - - - $this->ht = db_fetch_array($res); - - $this->id = $this->ht['ticket_id']; - $this->number = $this->ht['number']; - $this->_answers = array(); + var $owner; // TicketOwner + var $_user; // EndUser + var $_answers; + var $collaborators; + var $active_collaborators; + var $recipients; + var $lastrespondent; + function __onload() { $this->loadDynamicData(); - - //Reset the sub classes (initiated ondemand)...good for reloads. - $this->status= null; - $this->staff = null; - $this->client = null; - $this->team = null; - $this->dept = null; - $this->sla = null; - $this->tlock = null; - $this->stats = null; - $this->topic = null; - $this->thread = null; - $this->collaborators = null; - - return true; } function loadDynamicData() { - if (!$this->_answers) { + if (!isset($this->_answers)) { + $this->_answers = array(); foreach (DynamicFormEntry::forTicket($this->getId(), true) as $form) { foreach ($form->getAnswers() as $answer) { $tag = mb_strtolower($answer->field->name) @@ -303,12 +250,8 @@ implements RestrictedAccess, Threadable, TemplateVariable { return $this->_answers; } - function reload() { - return $this->load(); - } - function hasState($state) { - return (strcasecmp($this->getState(), $state)==0); + return strcasecmp($this->getState(), $state) == 0; } function isOpen() { @@ -316,7 +259,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function isReopened() { - return ($this->getReopenDate()); + return null !== $this->getReopenDate(); } function isReopenable() { @@ -336,15 +279,15 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function isAssigned() { - return ($this->isOpen() && ($this->getStaffId() || $this->getTeamId())); + return $this->isOpen() && ($this->getStaffId() || $this->getTeamId()); } function isOverdue() { - return ($this->ht['isoverdue']); + return $this->ht['isoverdue']; } function isAnswered() { - return ($this->ht['isanswered']); + return $this->ht['isanswered']; } function isLocked() { @@ -352,19 +295,20 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function checkStaffPerm($staff, $perm=null) { - // Must be a valid staff if (!$staff instanceof Staff && !($staff=Staff::lookup($staff))) return false; // Check access based on department or assignment - if (!(!$staff->showAssignedOnly() - && $staff->canAccessDept($this->getDeptId())) - // only open tickets can be considered assigned - && $this->isOpen() - && $staff->getId() != $this->getStaffId() - && !$staff->isTeamMember($this->getTeamId())) + if (($staff->showAssignedOnly() + || !$staff->canAccessDept($this->getDeptId())) + // only open tickets can be considered assigned + && $this->isOpen() + && $staff->getId() != $this->getStaffId() + && !$staff->isTeamMember($this->getTeamId()) + ) { return false; + } // At this point staff has view access unless a specific permission is // requested @@ -380,55 +324,50 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function checkUserAccess($user) { - if (!$user || !($user instanceof EndUser)) return false; - //Ticket Owner + // Ticket Owner if ($user->getId() == $this->getUserId()) return true; - //Collaborator? + // Collaborator? // 1) If the user was authorized via this ticket. if ($user->getTicketId() == $this->getId() - && !strcasecmp($user->getUserType(), 'collaborator')) + && !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()))) + 'user_id' => $user->getId(), + 'thread_id' => $this->getThreadId())) + ) { return true; - + } return false; } - //Getters - function getId() { - return $this->id; - } - + // Getters function getNumber() { return $this->number; } function getOwnerId() { - return $this->ht['user_id']; + return $this->user_id; } function getOwner() { - - if (!isset($this->owner) - && ($u=User::lookup($this->getOwnerId()))) - $this->owner = new TicketOwner(new EndUser($u), $this); - + if (!isset($this->owner)) { + $this->owner = new TicketOwner(new EndUser($this->user), $this); + } return $this->owner; } - function getEmail(){ - if ($o = $this->getOwner()) + function getEmail() { + if ($o = $this->getOwner()) { return $o->getEmail(); - + } return null; } @@ -444,8 +383,9 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getName(){ - if ($o = $this->getOwner()) + if ($o = $this->getOwner()) { return $o->getName(); + } return null; } @@ -455,15 +395,12 @@ implements RestrictedAccess, Threadable, TemplateVariable { /* Help topic title - NOT object -> $topic */ function getHelpTopic() { - - if(!$this->ht['helptopic'] && ($topic=$this->getTopic())) - $this->ht['helptopic'] = $topic->getFullName(); - - return $this->ht['helptopic']; + if ($this->topic) + return $this->topic->getFullName(); } function getCreateDate() { - return $this->ht['created']; + return $this->created; } function getOpenDate() { @@ -471,19 +408,19 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getReopenDate() { - return $this->ht['reopened']; + return $this->reopened; } function getUpdateDate() { - return $this->ht['updated']; + return $this->updated; } function getEffectiveDate() { - return $this->ht['lastupdate']; + return $this->lastupdate; } function getDueDate() { - return $this->ht['duedate']; + return $this->duedate; } function getSLADueDate() { @@ -497,30 +434,25 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function updateEstDueDate() { - $estimatedDueDate = $this->getEstDueDate(); - if ($estimatedDueDate != $this->ht['est_duedate']) { - $sql = 'UPDATE '.TICKET_TABLE.' SET `est_duedate`='.db_input($estimatedDueDate) - .' WHERE `ticket_id`='.db_input($this->getId()); - db_query($sql); - } + $this->est_duedate = $this->getEstDueDate(); + $this->save(); } function getEstDueDate() { - - //Real due date - if(($duedate=$this->getDueDate())) + // Real due date + if ($duedate = $this->getDueDate()) { return $duedate; - - //return sla due date (If ANY) + } + // return sla due date (If ANY) return $this->getSLADueDate(); } function getCloseDate() { - return $this->ht['closed']; + return $this->closed; } function getStatusId() { - return $this->ht['status_id']; + return $this->status_id; } /** @@ -532,47 +464,38 @@ implements RestrictedAccess, Threadable, TemplateVariable { // XXX: Use ::setStatus to change the status. This can be used as a // fallback if the logic in ::setStatus fails. function setStatusId($id) { - $sql = 'UPDATE '.TICKET_TABLE.' SET updated=NOW() '. - ' ,status_id='.db_input($id) . - ' WHERE ticket_id='.db_input($this->getId()); - - return (db_query($sql) && db_affected_rows()); + $this->status_id = $id; + return $this->save(); } function getStatus() { - - if (!$this->status && $this->getStatusId()) - $this->status = TicketStatus::lookup($this->getStatusId()); - return $this->status; } function getState() { - - if (!$this->getStatus()) + if (!$this->getStatus()) { return ''; - + } return $this->getStatus()->getState(); } function getDeptId() { - return $this->ht['dept_id']; + return $this->dept_id; } function getDeptName() { - - if(!$this->ht['dept_name'] && ($dept = $this->getDept())) - $this->ht['dept_name'] = $dept->getFullName(); - - return $this->ht['dept_name']; + if ($this->dept instanceof Dept) + return $this->dept->getFullName(); } function getPriorityId() { global $cfg; if (($a = $this->_answers['priority']) - && ($b = $a->getValue())) + && ($b = $a->getValue()) + ) { return $b->getId(); + } return $cfg->getDefaultPriorityId(); } @@ -587,11 +510,11 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getSource() { - return $this->ht['source']; + return $this->source; } function getIP() { - return $this->ht['ip_address']; + return $this->ip_address; } function getHashtable() { @@ -599,36 +522,30 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getUpdateInfo() { - global $cfg; - - $info=array('source' => $this->getSource(), - 'topicId' => $this->getTopicId(), - 'slaId' => $this->getSLAId(), - 'user_id' => $this->getOwnerId(), - 'duedate' => $this->getDueDate() - ? Format::date($this->getDueDate()) - :'', - 'time' => $this->getDueDate()?(Format::date($this->getDueDate(), true, 'HH:mm')):'', - ); - - return $info; + return array( + 'source' => $this->getSource(), + 'topicId' => $this->getTopicId(), + 'slaId' => $this->getSLAId(), + 'user_id' => $this->getOwnerId(), + 'duedate' => $this->getDueDate() + ? Format::date($this->getDueDate()) + : '', + 'time' => $this->getDueDate()?(Format::date($this->getDueDate(), true, 'HH:mm')):'', + ); } function getLock() { - if (!isset($this->tlock) && $this->ht['lock_id']) - $this->tlock = Lock::lookup($this->ht['lock_id']); - - return $this->tlock; + return $this->lock; } function acquireLock($staffId, $lockTime) { - if(!$staffId or !$lockTime) //Lockig disabled? + if (!$staffId or !$lockTime) //Lockig disabled? return null; - //Check if the ticket is already locked. - if(($lock=$this->getLock()) && !$lock->isExpired()) { - if($lock->getStaffId()!=$staffId) //someone else locked the ticket. + // Check if the ticket is already locked. + if (($lock = $this->getLock()) && !$lock->isExpired()) { + if ($lock->getStaffId() != $staffId) //someone else locked the ticket. return null; //Lock already exits...renew it @@ -636,18 +553,15 @@ implements RestrictedAccess, Threadable, TemplateVariable { return $lock; } - //No lock on the ticket or it is expired - $this->tlock = Lock::acquire($staffId, $lockTime); //Create a new lock.. + // No lock on the ticket or it is expired + $this->lock = Lock::acquire($staffId, $lockTime); //Create a new lock.. - if ($this->tlock) { - $sql = 'UPDATE '.TICKET_TABLE.' SET `lock_id` = ' - .db_input($this->tlock->getId()) - .' WHERE `ticket_id` = '. db_input($this->getId()); - db_query($sql); + if ($this->lock) { + $this->save(); } - //load and return the newly created lock if any! - return $this->tlock; + // load and return the newly created lock if any! + return $this->lock; } function releaseLock($staffId=false) { @@ -660,19 +574,14 @@ implements RestrictedAccess, Threadable, TemplateVariable { if (!$lock->delete()) return false; - $sql = 'UPDATE '.TICKET_TABLE.' SET `lock_id` = 0 WHERE `ticket_id` = ' - . db_input($this->getId()); - return ($res = db_query($sql)) && db_affected_rows($res); + $this->lock = null; + return $this->save(); } function getDept() { global $cfg; - if(!$this->dept) - if(!($this->dept = Dept::lookup($this->getDeptId()))) - $this->dept = $cfg->getDefaultDept(); - - return $this->dept; + return $this->dept ?: $cfg->getDefaultDept(); } function getUserId() { @@ -680,43 +589,34 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getUser() { - - if(!isset($this->user) && $this->getOwner()) - $this->user = new EndUser($this->getOwner()); - - return $this->user; + if (!isset($this->_user) && $this->user) { + $this->_user = new EndUser($this->user); + } + return $this->_user; } function getStaffId() { - return $this->ht['staff_id']; + return $this->staff_id; } function getStaff() { - - if(!$this->staff && $this->getStaffId()) - $this->staff= Staff::lookup($this->getStaffId()); - return $this->staff; } function getTeamId() { - return $this->ht['team_id']; + return $this->team_id; } function getTeam() { - - if(!$this->team && $this->getTeamId()) - $this->team = Team::lookup($this->getTeamId()); - return $this->team; } function getAssignee() { - if($staff=$this->getStaff()) + if ($staff=$this->getStaff()) return $staff->getName(); - if($team=$this->getTeam()) + if ($team=$this->getTeam()) return $team->getName(); return ''; @@ -724,11 +624,11 @@ implements RestrictedAccess, Threadable, TemplateVariable { function getAssignees() { - $assignees=array(); - if($staff=$this->getStaff()) + $assignees = array(); + if ($staff = $this->getStaff()) $assignees[] = $staff->getName(); - if($team=$this->getTeam()) + if ($team = $this->getTeam()) $assignees[] = $team->getName(); return $assignees; @@ -736,60 +636,47 @@ implements RestrictedAccess, Threadable, TemplateVariable { function getAssigned($glue='/') { $assignees = $this->getAssignees(); - return $assignees?implode($glue, $assignees):''; + return $assignees ? implode($glue, $assignees) : ''; } function getTopicId() { - return $this->ht['topic_id']; + return $this->topic_id; } function getTopic() { - - if(!$this->topic && $this->getTopicId()) - $this->topic = Topic::lookup($this->getTopicId()); - return $this->topic; } function getSLAId() { - return $this->ht['sla_id']; + return $this->sla_id; } function getSLA() { - - if(!$this->sla && $this->getSLAId()) - $this->sla = SLA::lookup($this->getSLAId()); - return $this->sla; } function getLastRespondent() { if (!isset($this->lastrespondent)) { - - $sql ='SELECT resp.staff_id ' - .' FROM '.THREAD_ENTRY_TABLE.' resp ' - .' LEFT JOIN '.THREAD_TABLE.' t ON( t.id=resp.thread_id) ' - .' LEFT JOIN '.STAFF_TABLE. ' s ON(s.staff_id=resp.staff_id) ' - .' WHERE t.object_id='.db_input($this->getId()) - .' AND t.object_type="T" AND resp.staff_id>0 AND resp.`type`="R" ' - .' ORDER BY resp.created DESC LIMIT 1'; - - if(!($res=db_query($sql)) || !db_num_rows($res)) - return null; - - list($id)=db_fetch_row($res); - - $this->lastrespondent = Staff::lookup($id); + $this->lastresponent = Staff::objects() + ->filter(array( + 'staff_id' => static::objects() + ->filter(array( + 'thread__entry__type' => 'R', + 'thread__entry__staff_id__gt' => 0 + )) + ->values_flat('thread__entry__staff_id') + ->order_by('-thread__entry__id') + ->first() + )) + ->first(); } - return $this->lastrespondent; - } function getLastMessageDate() { - return $this->ht['lastmessage']; + return $this->lastmessage; } function getLastMsgDate() { @@ -797,20 +684,18 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getLastResponseDate() { - return $this->ht['lastresponse']; + return $this->lastresponse; } 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( @@ -819,23 +704,20 @@ implements RestrictedAccess, Threadable, TemplateVariable { if (!$this->last_message) $this->last_message = $this->getThread()->getLastMessage(); } - return $this->last_message; } function getNumTasks() { - return $this->ht['tasks']; + // FIXME: Implement this after merging Tasks + return count($this->tasks); } function getThreadId() { - return $this->ht['thread_id']; + if ($this->thread) + return $this->thread->id; } function getThread() { - - if (!$this->thread && $this->getThreadId()) - $this->thread = TicketThread::lookup($this->getThreadId()); - return $this->thread; } @@ -885,7 +767,6 @@ implements RestrictedAccess, Threadable, TemplateVariable { //UserList of recipients (owner + collaborators) function getRecipients() { - if (!isset($this->recipients)) { $list = new UserList(); $list->add($this->getOwner()); @@ -895,7 +776,6 @@ implements RestrictedAccess, Threadable, TemplateVariable { } $this->recipients = $list; } - return $this->recipients; } @@ -931,7 +811,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { function addCollaborator($user, $vars, &$errors) { - if (!$user || $user->getId()==$this->getOwnerId()) + if (!$user || $user->getId() == $this->getOwnerId()) return null; $vars = array_merge(array( @@ -989,7 +869,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { )); } - unset($this->ht['active_collaborators']); + unset($this->active_collaborators); $this->collaborators = null; return true; @@ -1051,46 +931,29 @@ implements RestrictedAccess, Threadable, TemplateVariable { //DeptId can NOT be 0. No orphans please! function setDeptId($deptId) { - - //Make sure it's a valid department// - if(!($dept=Dept::lookup($deptId)) || $dept->getId()==$this->getDeptId()) + // Make sure it's a valid department + if ($deptId == $this->getDeptId() || !($dept=Dept::lookup($deptId))) { return false; - - - $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()); + } + $this->dept = $dept; + return $this->save(); } - //Set staff ID...assign/unassign/release (id can be 0) + // Set staff ID...assign/unassign/release (id can be 0) 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()); - - if (!db_query($sql) || !db_affected_rows()) + if (!is_numeric($staffId)) return false; - $this->staff = null; - $this->ht['staff_id'] = $staffId; - - return true; + $this->staff = Staff::lookup($staffId); + return $this->save(); } function setSLAId($slaId) { - if ($slaId == $this->getSLAId()) return true; - $rv = db_query( - '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; + if ($slaId == $this->getSLAId()) + return true; + + $this->sla = Sla::lookup($slaId); + return $this->save(); } /** * Selects the appropriate service-level-agreement plan for this ticket. @@ -1125,13 +988,11 @@ implements RestrictedAccess, Threadable, TemplateVariable { //Set team ID...assign/unassign/release (id can be 0) function setTeamId($teamId) { + if (!is_numeric($teamId)) + return false; - if(!is_numeric($teamId)) return false; - - $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), team_id='.db_input($teamId) - .' WHERE ticket_id='.db_input($this->getId()); - - return (db_query($sql) && db_affected_rows()); + $this->team = Team::lookup($teamId); + return $this->save(); } //Status helper. @@ -1139,7 +1000,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { function setStatus($status, $comments='', &$errors=array(), $set_closing_agent=true) { global $thisstaff; - if ($thisstaff && !($role=$thisstaff->getRole($this->getDeptId()))) + if ($thisstaff && !($role = $thisstaff->getRole($this->getDeptId()))) return false; if ($status && is_numeric($status)) @@ -1169,8 +1030,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { if ($this->getStatusId() == $status->getId()) return true; - $sql = 'UPDATE '.TICKET_TABLE.' SET updated=NOW() '. - ' ,status_id='.db_input($status->getId()); + $this->status = $status; //TODO: move this up. $ecb = null; @@ -1182,13 +1042,13 @@ implements RestrictedAccess, Threadable, TemplateVariable { '', ''); return false; } - $sql.=', closed=NOW(), lastupdate=NOW(), duedate=NULL '; + $this->closed = $this->lastupdate = SqlFunction::NOW(); + $this->duedate = null; if ($thisstaff && $set_closing_agent) - $sql.=', staff_id='.db_input($thisstaff->getId()); + $this->staff = $thisstaff; $this->clearOverdue(); $ecb = function($t) { - $t->reload(); $t->logEvent('closed'); $t->deleteDrafts(); }; @@ -1196,7 +1056,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { case 'open': // TODO: check current status if it allows for reopening if ($this->isClosed()) { - $sql .= ',closed=NULL, lastupdate=NOW(), reopened=NOW() '; + $this->closed = $this->lastupdate = $this->reopened = SqlFunction::NOW(); $ecb = function ($t) { $t->logEvent('reopened', false, 'closed'); }; @@ -1204,16 +1064,14 @@ implements RestrictedAccess, Threadable, TemplateVariable { // If the ticket is not open then clear answered flag if (!$this->isOpen()) - $sql .= ', isanswered = 0 '; + $this->isanswered = 0; break; default: return false; } - $sql.=' WHERE ticket_id='.db_input($this->getId()); - - if (!db_query($sql) || !db_affected_rows()) + if (!$this->save()) return false; // Log status change b4 reload — if currently has a status. (On new @@ -1244,30 +1102,23 @@ implements RestrictedAccess, Threadable, TemplateVariable { } 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; - case 'notdue': - return $this->clearOverdue(); - break; - case 'unassined': - return $this->unassign(); - } - + switch (strtolower($state)) { + case 'open': + return $this->setStatus('open'); + case 'closed': + return $this->setStatus('closed'); + case 'answered': + return $this->setAnsweredState(1); + case 'unanswered': + return $this->setAnsweredState(0); + case 'overdue': + return $this->markOverdue(); + case 'notdue': + return $this->clearOverdue(); + case 'unassined': + return $this->unassign(); + } + // FIXME: Throw and excception and add test cases return false; } @@ -1275,11 +1126,8 @@ implements RestrictedAccess, Threadable, TemplateVariable { 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()); + $this->isanswered = $isanswered; + return $this->save(); } function reopen() { @@ -1302,16 +1150,17 @@ implements RestrictedAccess, Threadable, TemplateVariable { //Log stuff here... - if(!$autorespond && !$alertstaff) return true; //No alerts to send. + if (!$autorespond && !$alertstaff) + return true; //No alerts to send. /* ------ SEND OUT NEW TICKET AUTORESP && ALERTS ----------*/ - $this->reload(); //get the new goodies. if(!$cfg - || !($dept=$this->getDept()) - || !($tpl = $dept->getTemplate()) - || !($email=$dept->getAutoRespEmail())) { - return false; //bail out...missing stuff. + || !($dept=$this->getDept()) + || !($tpl = $dept->getTemplate()) + || !($email=$dept->getAutoRespEmail()) + ) { + return false; //bail out...missing stuff. } $options = array(); @@ -1329,68 +1178,73 @@ implements RestrictedAccess, Threadable, TemplateVariable { } //Send auto response - if enabled. - if($autorespond - && $cfg->autoRespONNewTicket() - && $dept->autoRespONNewTicket() - && ($msg=$tpl->getAutoRespMsgTemplate())) { - - $msg = $this->replaceVars($msg->asArray(), - array('message' => $message, - 'recipient' => $this->getOwner(), - 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'') - ); - + if ($autorespond + && $cfg->autoRespONNewTicket() + && $dept->autoRespONNewTicket() + && ($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'], null, $options); } - //Send alert to out sleepy & idle staff. + // Send alert to out sleepy & idle staff. if ($alertstaff - && $cfg->alertONNewTicket() - && ($email=$dept->getAlertEmail()) - && ($msg=$tpl->getNewTicketAlertMsgTemplate())) { - + && $cfg->alertONNewTicket() + && ($email=$dept->getAlertEmail()) + && ($msg=$tpl->getNewTicketAlertMsgTemplate()) + ) { $msg = $this->replaceVars($msg->asArray(), array('message' => $message)); - - $recipients=$sentlist=array(); - //Exclude the auto responding email just incase it's from staff member. + $recipients = $sentlist = array(); + // Exclude the auto responding email just incase it's from staff member. if ($message instanceof ThreadEntry && $message->isAutoReply()) $sentlist[] = $this->getEmail(); - //Alert admin?? - if($cfg->alertAdminONNewTicket()) { + // Alert admin?? + if ($cfg->alertAdminONNewTicket()) { $alert = $this->replaceVars($msg, array('recipient' => 'Admin')); $email->sendAlert($cfg->getAdminEmail(), $alert['subj'], $alert['body'], null, $options); $sentlist[]=$cfg->getAdminEmail(); } - //Only alerts dept members if the ticket is NOT assigned. - if($cfg->alertDeptMembersONNewTicket() && !$this->isAssigned()) { - if(($members=$dept->getMembersForAlerts())) - $recipients=array_merge($recipients, $members); + // Only alerts dept members if the ticket is NOT assigned. + if ($cfg->alertDeptMembersONNewTicket() && !$this->isAssigned()) { + if ($members = $dept->getMembersForAlerts()) + $recipients = array_merge($recipients, $members); } - if($cfg->alertDeptManagerONNewTicket() && $dept && ($manager=$dept->getManager())) - $recipients[]= $manager; + if ($cfg->alertDeptManagerONNewTicket() && $dept && ($manager=$dept->getManager())) + $recipients[] = $manager; // Account manager if ($cfg->alertAcctManagerONNewMessage() - && ($org = $this->getOwner()->getOrganization()) - && ($acct_manager = $org->getAccountManager())) { + && ($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; + foreach ($recipients as $k=>$staff) { + if (!is_object($staff) + || !$staff->isAvailable() + || in_array($staff->getEmail(), $sentlist) + ) { + continue; + } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } - return true; } @@ -1403,24 +1257,26 @@ implements RestrictedAccess, Threadable, TemplateVariable { $ost->logWarning(sprintf(_S('Maximum Open Tickets Limit (%s)'),$this->getEmail()), $msg); - if(!$sendNotice || !$cfg->sendOverLimitNotice()) + if (!$sendNotice || !$cfg->sendOverLimitNotice()) return true; //Send notice to user. - if(($dept = $this->getDept()) + 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=$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 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()) @@ -1432,13 +1288,16 @@ implements RestrictedAccess, Threadable, TemplateVariable { } 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(); - $vars = array_merge($options, - array( - 'activity' => _S('New Response'), - 'threadentry' => $response)); + $this->isanswered = 1; + $this->lastresponse = SqlFunction::NOW(); + $this->save(); + $vars = array_merge($options, + array( + 'activity' => _S('New Response'), + 'threadentry' => $response + ) + ); $this->onActivity($vars); } @@ -1451,14 +1310,15 @@ implements RestrictedAccess, Threadable, TemplateVariable { global $cfg; if (!$entry instanceof ThreadEntry - || !($recipients=$this->getRecipients()) - || !($dept=$this->getDept()) - || !($tpl=$dept->getTemplate()) - || !($msg=$tpl->getActivityNoticeMsgTemplate()) - || !($email=$dept->getEmail())) + || !($recipients=$this->getRecipients()) + || !($dept=$this->getDept()) + || !($tpl=$dept->getTemplate()) + || !($msg=$tpl->getActivityNoticeMsgTemplate()) + || !($email=$dept->getEmail()) + ) { return; - - //Who posted the entry? + } + // Who posted the entry? $skip = array(); if ($entry instanceof Message) { $poster = $entry->getUser(); @@ -1480,10 +1340,10 @@ implements RestrictedAccess, Threadable, TemplateVariable { } $vars = array_merge($vars, array( - 'message' => (string) $entry, - 'poster' => $poster ?: _S('A collaborator'), - ) - ); + 'message' => (string) $entry, + 'poster' => $poster ?: _S('A collaborator'), + ) + ); $msg = $this->replaceVars($msg->asArray(), $vars); @@ -1499,14 +1359,14 @@ implements RestrictedAccess, Threadable, TemplateVariable { $email->send($recipient, $notice['subj'], $notice['body'], $attachments, $options); } - - return; } function onMessage($message, $autorespond=true) { global $cfg; - db_query('UPDATE '.TICKET_TABLE.' SET isanswered=0,lastupdate=NOW(),lastmessage=NOW() WHERE ticket_id='.db_input($this->getId())); + $this->isanswered = 0; + $this->lastupdate = SqlFunction::NOW(); + $this->save(); // Auto-assign to closing staff or last respondent // If the ticket is closed and auto-claim is not enabled then put the @@ -1550,28 +1410,31 @@ implements RestrictedAccess, Threadable, TemplateVariable { elseif ($autorespond && ($dept=$this->getDept())) $autorespond=$dept->autoRespONNewMessage(); + if (!$autorespond + || !$cfg->autoRespONNewMessage() + || !$message + ) { + return; //no autoresp or alerts. + } - if(!$autorespond - || !$cfg->autoRespONNewMessage() - || !$message) return; //no autoresp or alerts. - - $this->reload(); $dept = $this->getDept(); $email = $dept->getAutoRespEmail(); - //If enabled...send confirmation to user. ( New Message AutoResponse) - if($email - && ($tpl=$dept->getTemplate()) - && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) { - + // 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():'')); - + array( + 'recipient' => $user, + 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'' + ) + ); $options = array( - 'inreplyto'=>$message->getEmailMessageId(), - 'thread'=>$message); + 'inreplyto' => $message->getEmailMessageId(), + 'thread' => $message + ); $email->sendAutoReply($user, $msg['subj'], $msg['body'], null, $options); } @@ -1583,15 +1446,17 @@ implements RestrictedAccess, Threadable, TemplateVariable { //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())) + || !$cfg->alertONNewActivity() + || !($dept=$this->getDept()) + || !($email=$cfg->getAlertEmail()) + || !($tpl = $dept->getTemplate()) + || !($msg=$tpl->getNoteAlertMsgTemplate()) + ) { return; + } // Alert recipients - $recipients=array(); + $recipients = array(); //Last respondent. if ($cfg->alertLastRespondentONNewActivity()) @@ -1599,7 +1464,6 @@ implements RestrictedAccess, Threadable, TemplateVariable { // Assigned staff / team if ($cfg->alertAssignedONNewActivity()) { - if (isset($vars['assignee']) && $vars['assignee'] instanceof Staff) $recipients[] = $vars['assignee']; @@ -1640,32 +1504,32 @@ implements RestrictedAccess, Threadable, TemplateVariable { $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)) - ) + // 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)) + ) { 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) { global $cfg, $thisstaff; - if($this->isClosed()) $this->reopen(); //Assigned tickets must be open - otherwise why assign? + 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; - - $this->reload(); + // Assignee must be an object of type Staff or Team + if (!$assignee || !is_object($assignee)) + return false; $user_comments = (bool) $comments; $comments = $comments ?: _S('Ticket assignment'); @@ -1678,17 +1542,20 @@ implements RestrictedAccess, Threadable, TemplateVariable { $comments, $assigner, false); } - //See if we need to send alerts - if(!$alert || !$cfg->alertONAssignment()) return true; //No alerts! + // 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())) + if (!$dept + || !($tpl = $dept->getTemplate()) + || !($email = $dept->getAlertEmail()) + ) { return true; + } - //recipients - $recipients=array(); + // Recipients + $recipients = array(); if ($assignee instanceof Staff) { if ($cfg->alertStaffONAssignment()) $recipients[] = $assignee; @@ -1699,82 +1566,97 @@ implements RestrictedAccess, Threadable, TemplateVariable { $recipients[] = $lead; } - //Get the message template + // Get the message template if ($recipients - && ($msg=$tpl->getAssignedAlertMsgTemplate())) { - + && ($msg=$tpl->getAssignedAlertMsgTemplate()) + ) { $msg = $this->replaceVars($msg->asArray(), - array('comments' => $comments, - 'assignee' => $assignee, - 'assigner' => $assigner - )); - - //Send the alerts. - $sentlist=array(); + array('comments' => $comments, + 'assignee' => $assignee, + 'assigner' => $assigner + ) + ); + // Send the alerts. + $sentlist = array(); $options = array( 'inreplyto'=>$note->getEmailMessageId(), 'references'=>$note->getEmailReferences(), 'thread'=>$note); - foreach( $recipients as $k=>$staff) { - if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; + foreach ($recipients as $k=>$staff) { + if (!is_object($staff) + || !$staff->isAvailable() + || in_array($staff->getEmail(), $sentlist) + ) { + continue; + } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } - return true; } function onOverdue($whine=true, $comments="") { global $cfg; - if($whine && ($sla=$this->getSLA()) && !$sla->alertOnOverdue()) + if ($whine && ($sla = $this->getSLA()) && !$sla->alertOnOverdue()) $whine = false; - //check if we need to send alerts. - if(!$whine - || !$cfg->alertONOverdueTicket() - || !($dept = $this->getDept())) + // Check if we need to send alerts. + if (!$whine + || !$cfg->alertONOverdueTicket() + || !($dept = $this->getDept()) + ) { return true; - - //Get the message template - if(($tpl = $dept->getTemplate()) - && ($msg=$tpl->getOverdueAlertMsgTemplate()) - && ($email = $dept->getAlertEmail())) { - + } + // Get the message template + if (($tpl = $dept->getTemplate()) + && ($msg=$tpl->getOverdueAlertMsgTemplate()) + && ($email = $dept->getAlertEmail()) + ) { $msg = $this->replaceVars($msg->asArray(), - array('comments' => $comments)); - - //recipients - $recipients=array(); - //Assigned staff or team... if any - if($this->isAssigned() && $cfg->alertAssignedONOverdueTicket()) { - if($this->getStaffId()) + array('comments' => $comments) + ); + // Recipients + $recipients = array(); + // Assigned staff or team... if any + if ($this->isAssigned() && $cfg->alertAssignedONOverdueTicket()) { + if ($this->getStaffId()) { $recipients[]=$this->getStaff(); - elseif($this->getTeamId() && ($team=$this->getTeam()) && ($members=$team->getMembers())) + } + 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. + } + } + elseif ($cfg->alertDeptMembersONOverdueTicket() && !$this->isAssigned()) { + // Only alerts dept members if the ticket is NOT assigned. if ($members = $dept->getMembersForAlerts()) $recipients = array_merge($recipients, $members); } - //Always alert dept manager?? - if($cfg->alertDeptManagerONOverdueTicket() && $dept && ($manager=$dept->getManager())) + // 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; + } + $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); $sentlist[] = $staff->getEmail(); } - } - return true; - } // TemplateVariable interface @@ -1785,47 +1667,46 @@ implements RestrictedAccess, Threadable, TemplateVariable { function getVar($tag) { global $cfg; - if($tag && is_callable(array($this, 'get'.ucfirst($tag)))) + if ($tag && is_callable(array($this, 'get'.ucfirst($tag)))) return call_user_func(array($this, 'get'.ucfirst($tag))); switch(mb_strtolower($tag)) { - case 'phone': - case 'phone_number': - return $this->getPhoneNumber(); - break; - case '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; - case 'close_date': - if ($this->isClosed()) - return new FormattedDate($this->getCloseDate()); - break; - case 'last_update': - return new FormattedDate($this->last_update); - case 'user': - return $this->getOwner(); - 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]; + case 'phone': + 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; + case 'close_date': + if ($this->isClosed()) + return new FormattedDate($this->getCloseDate()); + break; + case 'last_update': + return new FormattedDate($this->last_update); + case 'user': + return $this->getOwner(); + default: + if (isset($this->_answers[$tag])) + // The answer object is retrieved here which will + // automatically invoke the toString() method when the + // answer is coerced into text + return $this->_answers[$tag]; } - return false; } @@ -1891,7 +1772,6 @@ implements RestrictedAccess, Threadable, TemplateVariable { global $ost; $vars = array_merge($vars, array('ticket' => $this)); - return $ost->replaceTemplateVariables($input, $vars); } @@ -1904,16 +1784,13 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function markOverdue($whine=true) { - global $cfg; - if($this->isOverdue()) + if ($this->isOverdue()) return true; - $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=1, updated=NOW() ' - .' WHERE ticket_id='.db_input($this->getId()); - - if(!db_query($sql) || !db_affected_rows()) + $this->isoverdue = 1; + if (!$this->save()) return false; $this->logEvent('overdue'); @@ -1923,30 +1800,26 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function clearOverdue() { - - if(!$this->isOverdue()) + if (!$this->isOverdue()) return true; //NOTE: Previously logged overdue event is NOT annuled. - $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=0, updated=NOW() '; + $this->isoverdue = 0; - //clear due date if it's in the past - if($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime()) - $sql.=', duedate=NULL'; + // clear due date if it's in the past + if ($this->getDueDate() && Misc::db2gmtime($this->getDueDate()) <= Misc::gmtime()) + $this->duedate = null; - //Clear SLA if est. due date is in the past - if($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime()) - $sql.=', sla_id=0 '; - - $sql.=' WHERE ticket_id='.db_input($this->getId()); + // Clear SLA if est. due date is in the past + if ($this->getSLADueDate() && Misc::db2gmtime($this->getSLADueDate()) <= Misc::gmtime()) + $this->sla = null; - return (db_query($sql) && db_affected_rows()); + return $this->save(); } //Dept Tranfer...with alert.. done by staff - function transfer($deptId, $comments, $alert = true) { - + function transfer($deptId, $comments, $alert=true) { global $cfg, $thisstaff; if (!$this->checkStaffPerm($thisstaff, TicketModel::PERM_TRANSFER)) @@ -1954,25 +1827,26 @@ implements RestrictedAccess, Threadable, TemplateVariable { $currentDept = $this->getDeptName(); //Current department - if(!$deptId || !$this->setDeptId($deptId)) + if (!$deptId || !$this->setDeptId($deptId)) return false; // Reopen ticket if closed - if($this->isClosed()) $this->reopen(); + if ($this->isClosed()) + $this->reopen(); - $this->reload(); $dept = $this->getDept(); // Set SLA of the new department - if(!$this->getSLAId() || $this->getSLA()->isTransient()) + if (!$this->getSLAId() || $this->getSLA()->isTransient()) $this->selectSLAId(); // 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)) { + && ($staff=$this->getStaff()) + && $dept->assignMembersOnly() + && !$dept->isMember($staff) + ) { $this->setStaffId(0); } @@ -1992,44 +1866,56 @@ implements RestrictedAccess, Threadable, TemplateVariable { return true; //no alerts!! if (($email = $dept->getAlertEmail()) - && ($tpl = $dept->getTemplate()) - && ($msg=$tpl->getTransferAlertMsgTemplate())) { - + && ($tpl = $dept->getTemplate()) + && ($msg=$tpl->getTransferAlertMsgTemplate()) + ) { $msg = $this->replaceVars($msg->asArray(), array('comments' => $comments, 'staff' => $thisstaff)); - //recipients - $recipients=array(); - //Assigned staff or team... if any + // Recipients + $recipients = array(); + // Assigned staff or team... if any if($this->isAssigned() && $cfg->alertAssignedONTransfer()) { if($this->getStaffId()) - $recipients[]=$this->getStaff(); - elseif($this->getTeamId() && ($team=$this->getTeam()) && ($members=$team->getMembers())) + $recipients[] = $this->getStaff(); + elseif ($this->getTeamId() + && ($team=$this->getTeam()) + && ($members=$team->getMembers()) + ) { $recipients = array_merge($recipients, $members); - } elseif($cfg->alertDeptMembersONTransfer() && !$this->isAssigned()) { - //Only alerts dept members if the ticket is NOT assigned. - if(($members=$dept->getMembersForAlerts())) + } + } + elseif ($cfg->alertDeptMembersONTransfer() && !$this->isAssigned()) { + // Only alerts dept members if the ticket is NOT assigned. + if ($members = $dept->getMembersForAlerts()) $recipients = array_merge($recipients, $members); } - //Always alert dept manager?? - if($cfg->alertDeptManagerONTransfer() && $dept && ($manager=$dept->getManager())) - $recipients[]= $manager; - - $sentlist = $options = array(); + // Always alert dept manager?? + if ($cfg->alertDeptManagerONTransfer() + && $dept + && ($manager=$dept->getManager()) + ) { + $recipients[] = $manager; + } + $sentlist = array(); if ($note) { $options += array( 'inreplyto'=>$note->getEmailMessageId(), 'references'=>$note->getEmailReferences(), 'thread'=>$note); } - foreach( $recipients as $k=>$staff) { - if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; + foreach ($recipients as $k=>$staff) { + if (!is_object($staff) + || !$staff->isAvailable() + || in_array($staff->getEmail(), $sentlist) + ) { + continue; + } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } - return true; } @@ -2048,7 +1934,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { function assignToStaff($staff, $note, $alert=true) { - if(!is_object($staff) && !($staff=Staff::lookup($staff))) + if(!is_object($staff) && !($staff = Staff::lookup($staff))) return false; if (!$staff->isAvailable() || !$this->setStaffId($staff->getId())) @@ -2069,7 +1955,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { function assignToTeam($team, $note, $alert=true) { - if(!is_object($team) && !($team=Team::lookup($team))) + if(!is_object($team) && !($team = Team::lookup($team))) return false; if (!$team->isActive() || !$this->setTeamId($team->getId())) @@ -2077,7 +1963,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { //Clear - staff if it's a closed ticket // staff_id is overloaded -> assigned to & closed by. - if($this->isClosed()) + if ($this->isClosed()) $this->setStaffId(0); $this->onAssign($team, $note, $alert); @@ -2090,39 +1976,39 @@ implements RestrictedAccess, Threadable, TemplateVariable { function assign($assignId, $note, $alert=true) { global $thisstaff; - $rv=0; - $id=preg_replace("/[^0-9]/", "", $assignId); - if($assignId[0]=='t') { - $rv=$this->assignToTeam($id, $note, $alert); - } elseif($assignId[0]=='s' || is_numeric($assignId)) { - $alert=($alert && $thisstaff && $thisstaff->getId()==$id)?false:$alert; //No alerts on self assigned tickets!!! - //We don't care if a team is already assigned to the ticket - staff assignment takes precedence - $rv=$this->assignToStaff($id, $note, $alert); + $rv = 0; + $id = preg_replace("/[^0-9]/", "", $assignId); + if ($assignId[0] == 't') { + $rv = $this->assignToTeam($id, $note, $alert); + } + elseif ($assignId[0] == 's' || is_numeric($assignId)) { + $alert = ($alert && $thisstaff && $thisstaff->getId() == $id) + ? false : $alert; //No alerts on self assigned tickets!!! + // We don't care if a team is already assigned to the ticket - + // staff assignment takes precedence + $rv = $this->assignToStaff($id, $note, $alert); } - return $rv; } - //unassign primary assignee + // Unassign primary assignee function unassign() { - - if(!$this->isAssigned()) //We can't release what is not assigned buddy! + // We can't release what is not assigned buddy! + if (!$this->isAssigned()) return true; - //We can only unassigned OPEN tickets. - if($this->isClosed()) + // We can only unassigned OPEN tickets. + if ($this->isClosed()) return false; - //Unassign staff (if any) - if($this->getStaffId() && !$this->setStaffId(0)) + // Unassign staff (if any) + if ($this->getStaffId() && !$this->setStaffId(0)) return false; - //unassign team (if any) - if($this->getTeamId() && !$this->setTeamId(0)) + // Unassign team (if any) + if ($this->getTeamId() && !$this->setTeamId(0)) return false; - $this->reload(); - return true; } @@ -2135,20 +2021,18 @@ implements RestrictedAccess, Threadable, TemplateVariable { global $thisstaff; if (!$user - || ($user->getId() == $this->getOwnerId()) - || !($this->checkStaffPerm($thisstaff, - TicketModel::PERM_EDIT))) + || ($user->getId() == $this->getOwnerId()) + || !($this->checkStaffPerm($thisstaff, + TicketModel::PERM_EDIT)) + ) { return false; + } - $sql ='UPDATE '.TICKET_TABLE.' SET updated = NOW() ' - .', user_id = '.db_input($user->getId()) - .' WHERE ticket_id = '.db_input($this->getId()); - - if (!db_query($sql) || !db_affected_rows()) + $this->user_id = $user->getId(); + if (!$this->save()) return false; - $this->ht['user_id'] = $user->getId(); - $this->user = null; + unset($this->user); $this->collaborators = null; $this->recipients = null; @@ -2165,32 +2049,33 @@ implements RestrictedAccess, Threadable, TemplateVariable { return true; } - //Insert message from client + // Insert message from client function postMessage($vars, $origin='', $alerts=true) { global $cfg; if ($origin) $vars['origin'] = $origin; - if(isset($vars['ip'])) + if (isset($vars['ip'])) $vars['ip_address'] = $vars['ip']; - elseif(!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) + elseif (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; $errors = array(); - if(!($message = $this->getThread()->addMessage($vars, $errors))) + if (!($message = $this->getThread()->addMessage($vars, $errors))) return null; $this->setLastMessage($message); - //Add email recipients as collaborators... + // Add email recipients as collaborators... if ($vars['recipients'] - && (strtolower($origin) != 'email' || ($cfg && $cfg->addCollabsViaEmail())) - //Only add if we have a matched local address - && $vars['to-email-id']) { + && (strtolower($origin) != 'email' || ($cfg && $cfg->addCollabsViaEmail())) + //Only add if we have a matched local address + && $vars['to-email-id'] + ) { //New collaborators added by other collaborators are disable -- // requires staff approval. $info = array( - 'isactive' => ($message->getUserId() == $this->getUserId())? 1: 0); + 'isactive' => ($message->getUserId() == $this->getUserId())? 1: 0); $collabs = array(); foreach ($vars['recipients'] as $recipient) { // Skip virtual delivered-to addresses @@ -2199,15 +2084,21 @@ implements RestrictedAccess, Threadable, TemplateVariable { if (($user=User::fromVars($recipient))) if ($c=$this->addCollaborator($user, $info, $errors)) + // FIXME: This feels very unwise — should be a + // string indexed array for future $collabs[] = array((string)$c, $recipient['source']); } - //TODO: Can collaborators add others? + // TODO: Can collaborators add others? if ($collabs) { $this->logEvent('collab', array('add' => $collabs)); } } - if(!$alerts) return $message; //Our work is done... + // Set the last message time here + $this->lastmessage = SqlFunction::NOW(); + + if (!$alerts) + return $message; //Our work is done... // Do not auto-respond to bounces and other auto-replies $autorespond = isset($vars['flags']) @@ -2222,29 +2113,27 @@ implements RestrictedAccess, Threadable, TemplateVariable { $this->notifyCollaborators($message, array('signature' => '')); $dept = $this->getDept(); - - $variables = array( - 'message' => $message, - 'poster' => ($vars['poster'] ? $vars['poster'] : $this->getName()) - ); + 'message' => $message, + 'poster' => ($vars['poster'] ? $vars['poster'] : $this->getName()) + ); $options = array( - 'inreplyto' => $message->getEmailMessageId(), - 'references' => $message->getEmailReferences(), - 'thread'=>$message); - //If enabled...send alert to staff (New Message Alert) - if($cfg->alertONNewMessage() - && ($email = $dept->getAlertEmail()) - && ($tpl = $dept->getTemplate()) - && ($msg = $tpl->getNewMessageAlertMsgTemplate())) { - + 'inreplyto' => $message->getEmailMessageId(), + 'references' => $message->getEmailReferences(), + 'thread'=>$message + ); + // If enabled...send alert to staff (New Message Alert) + if ($cfg->alertONNewMessage() + && ($email = $dept->getAlertEmail()) + && ($tpl = $dept->getTemplate()) + && ($msg = $tpl->getNewMessageAlertMsgTemplate()) + ) { $msg = $this->replaceVars($msg->asArray(), $variables); - - //Build list of recipients and fire the alerts. - $recipients=array(); + // Build list of recipients and fire the alerts. + $recipients = array(); //Last respondent. - if($cfg->alertLastRespondentONNewMessage() || $cfg->alertAssignedONNewMessage()) - $recipients[]=$this->getLastRespondent(); + if ($cfg->alertLastRespondentONNewMessage() || $cfg->alertAssignedONNewMessage()) + $recipients[] = $this->getLastRespondent(); //Assigned staff if any...could be the last respondent if ($cfg->alertAssignedONNewMessage() && $this->isAssigned()) { @@ -2254,9 +2143,13 @@ implements RestrictedAccess, Threadable, TemplateVariable { $recipients = array_merge($recipients, $team->getMembers()); } - //Dept manager - if($cfg->alertDeptManagerONNewMessage() && $dept && ($manager=$dept->getManager())) + // Dept manager + if ($cfg->alertDeptManagerONNewMessage() + && $dept + && ($manager = $dept->getManager()) + ) { $recipients[]=$manager; + } // Account manager if ($cfg->alertAcctManagerONNewMessage() @@ -2268,67 +2161,75 @@ implements RestrictedAccess, Threadable, TemplateVariable { $recipients[] = $acct_manager; } - $sentlist=array(); //I know it sucks...but..it works. - foreach( $recipients as $k=>$staff) { - if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; + $sentlist = array(); //I know it sucks...but..it works. + foreach ($recipients as $k=>$staff) { + if (!$staff || !$staff->getEmail() + || !$staff->isAvailable() + || in_array($staff->getEmail(), $sentlist) + ) { + continue; + } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } - return $message; } function postCannedReply($canned, $message, $alert=true) { global $ost, $cfg; - if((!is_object($canned) && !($canned=Canned::lookup($canned))) || !$canned->isEnabled()) + if ((!is_object($canned) && !($canned=Canned::lookup($canned))) + || !$canned->isEnabled() + ) { return false; - + } $files = array(); foreach ($canned->attachments->getAll() as $file) $files[] = $file['id']; if ($cfg->isRichTextEnabled()) $response = new HtmlThreadEntryBody( - $this->replaceVars($canned->getHtml())); + $this->replaceVars($canned->getHtml())); else $response = new TextThreadEntryBody( - $this->replaceVars($canned->getPlainText())); + $this->replaceVars($canned->getPlainText())); $info = array('msgId' => $message instanceof ThreadEntry ? $message->getId() : 0, 'poster' => __('SYSTEM (Canned Reply)'), 'response' => $response, - 'cannedattachments' => $files); - + 'cannedattachments' => $files + ); $errors = array(); - if(!($response=$this->postReply($info, $errors, false))) + if (!($response=$this->postReply($info, $errors, false))) return null; $this->markUnAnswered(); - if(!$alert) return $response; + if (!$alert) + return $response; $dept = $this->getDept(); - if(($email=$dept->getEmail()) - && ($tpl = $dept->getTemplate()) - && ($msg=$tpl->getAutoReplyMsgTemplate())) { - - if($dept && $dept->isPublic()) + if (($email=$dept->getEmail()) + && ($tpl = $dept->getTemplate()) + && ($msg=$tpl->getAutoReplyMsgTemplate()) + ) { + if ($dept && $dept->isPublic()) $signature=$dept->getSignature(); else $signature=''; $msg = $this->replaceVars($msg->asArray(), - array( - 'response' => $response, - 'signature' => $signature, - 'recipient' => $this->getOwner(), - )); - - $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array(); + array( + 'response' => $response, + 'signature' => $signature, + 'recipient' => $this->getOwner(), + ) + ); + $attachments = ($cfg->emailAttachments() && $files) + ? $response->getAttachments() : array(); $options = array( 'inreplyto'=>$response->getEmailMessageId(), 'references'=>$response->getEmailReferences(), @@ -2336,7 +2237,6 @@ implements RestrictedAccess, Threadable, TemplateVariable { $email->sendAutoReply($this, $msg['subj'], $msg['body'], $attachments, $options); } - return $response; } @@ -2345,71 +2245,79 @@ implements RestrictedAccess, Threadable, TemplateVariable { global $thisstaff, $cfg; - if(!$vars['poster'] && $thisstaff) + if (!$vars['poster'] && $thisstaff) $vars['poster'] = $thisstaff; - if(!$vars['staffId'] && $thisstaff) + if (!$vars['staffId'] && $thisstaff) $vars['staffId'] = $thisstaff->getId(); if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; - if(!($response = $this->getThread()->addResponse($vars, $errors))) + if (!($response = $this->getThread()->addResponse($vars, $errors))) return null; $assignee = $this->getStaff(); // Set status - if checked. if ($vars['reply_status_id'] - && $vars['reply_status_id'] != $this->getStatusId()) + && $vars['reply_status_id'] != $this->getStatusId() + ) { $this->setStatus($vars['reply_status_id']); + } // Claim on response bypasses the department assignment restrictions - if($thisstaff && $this->isOpen() && !$this->getStaffId() - && $cfg->autoClaimTickets()) + if ($thisstaff && $this->isOpen() && !$this->getStaffId() + && $cfg->autoClaimTickets() + ) { $this->setStaffId($thisstaff->getId()); //direct assignment; + } - $this->lastrespondent = null; + $this->lastrespondent = null; // XXX: Set to $response->staff? $this->onResponse($response, array('assignee' => $assignee)); //do house cleaning.. /* email the user?? - if disabled - then bail out */ - if (!$alert) return $response; + if (!$alert) + return $response; $dept = $this->getDept(); - if($thisstaff && $vars['signature']=='mine') + if ($thisstaff && $vars['signature']=='mine') $signature=$thisstaff->getSignature(); - elseif($vars['signature']=='dept' && $dept && $dept->isPublic()) + elseif ($vars['signature']=='dept' && $dept && $dept->isPublic()) $signature=$dept->getSignature(); else $signature=''; $variables = array( - 'response' => $response, - 'signature' => $signature, - 'staff' => $thisstaff, - 'poster' => $thisstaff); + 'response' => $response, + 'signature' => $signature, + 'staff' => $thisstaff, + 'poster' => $thisstaff + ); $options = array( - 'inreplyto' => $response->getEmailMessageId(), - 'references' => $response->getEmailReferences(), - 'thread'=>$response); - - if(($email=$dept->getEmail()) - && ($tpl = $dept->getTemplate()) - && ($msg=$tpl->getReplyMsgTemplate())) { + 'inreplyto' => $response->getEmailMessageId(), + 'references' => $response->getEmailReferences(), + 'thread'=>$response + ); + if (($email=$dept->getEmail()) + && ($tpl = $dept->getTemplate()) + && ($msg=$tpl->getReplyMsgTemplate()) + ) { $msg = $this->replaceVars($msg->asArray(), - $variables + array('recipient' => $this->getOwner())); - + $variables + array('recipient' => $this->getOwner()) + ); $attachments = $cfg->emailAttachments()?$response->getAttachments():array(); $email->send($this->getOwner(), $msg['subj'], $msg['body'], $attachments, $options); } - if($vars['emailcollab']) + if ($vars['emailcollab']) { $this->notifyCollaborators($response, - array('signature' => $signature)); - + array('signature' => $signature) + ); + } return $response; } @@ -2425,20 +2333,19 @@ implements RestrictedAccess, Threadable, TemplateVariable { //Insert Internal Notes function logNote($title, $note, $poster='SYSTEM', $alert=true) { - - $errors = array(); - //Unless specified otherwise, assume HTML + // Unless specified otherwise, assume HTML if ($note && is_string($note)) $note = new HtmlThreadEntryBody($note); + $errors = array(); return $this->postNote( - array( - 'title' => $title, - 'note' => $note, - ), - $errors, - $poster, - $alert + array( + 'title' => $title, + 'note' => $note, + ), + $errors, + $poster, + $alert ); } @@ -2447,7 +2354,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { //Who is posting the note - staff or system? $vars['staffId'] = 0; - if($poster && is_object($poster)) { + if ($poster && is_object($poster)) { $vars['staffId'] = $poster->getId(); $vars['poster'] = $poster->getName(); } @@ -2460,7 +2367,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; - if(!($note=$this->getThread()->addNote($vars, $errors))) + if (!($note=$this->getThread()->addNote($vars, $errors))) return null; $alert = $alert && ( @@ -2474,7 +2381,8 @@ implements RestrictedAccess, Threadable, TemplateVariable { $assignee = $this->getStaff(); if ($vars['note_status_id'] - && ($status=TicketStatus::lookup($vars['note_status_id']))) { + && ($status=TicketStatus::lookup($vars['note_status_id'])) + ) { if ($this->setStatus($status)) $this->reload(); } @@ -2502,7 +2410,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { } } - //Print ticket... export the ticket thread as PDF. + // Print ticket... export the ticket thread as PDF. function pdfExport($psize='Letter', $notes=false) { global $thisstaff; @@ -2515,7 +2423,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { } $pdf = new Ticket2PDF($this, $psize, $notes); - $name='Ticket-'.$this->getNumber().'.pdf'; + $name = 'Ticket-'.$this->getNumber().'.pdf'; Http::download($name, 'application/pdf', $pdf->Output($name, 'S')); //Remember what the user selected - for autoselect on the next print. $_SESSION['PAPER_SIZE'] = $psize; @@ -2529,8 +2437,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { // Fetch thread prior to removing ticket entry $t = $this->getThread(); - $sql = 'DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1'; - if(!db_query($sql) || !db_affected_rows()) + if (!parent::delete()) return false; $t->delete(); @@ -2540,23 +2447,21 @@ implements RestrictedAccess, Threadable, TemplateVariable { $this->deleteDrafts(); - $sql = 'DELETE FROM '.TICKET_TABLE.'__cdata WHERE `ticket_id`=' - .db_input($this->getId()); - // If the CDATA table doesn't exist, that's not an error - db_query($sql, false); + if ($this->cdata) + $this->cdata->delete(); // Log delete $log = sprintf(__('Ticket #%1$s deleted by %2$s'), - $this->getNumber(), - $thisstaff ? $thisstaff->getName() : __('SYSTEM')); - + $this->getNumber(), + $thisstaff ? $thisstaff->getName() : __('SYSTEM') + ); if ($comments) $log .= sprintf('<hr>%s', $comments); $ost->logDebug( - sprintf( __('Ticket #%s deleted'), $this->getNumber()), - $log); - + sprintf( __('Ticket #%s deleted'), $this->getNumber()), + $log + ); return true; } @@ -2564,33 +2469,41 @@ implements RestrictedAccess, Threadable, TemplateVariable { Draft::deleteForNamespace('ticket.%.' . $this->getId()); } - function update($vars, &$errors) { + function save($refetch=false) { + if ($this->dirty) { + $this->updated = SqlFunction::NOW(); + } + return parent::save($this->dirty || $refetch); + } + function update($vars, &$errors) { global $cfg, $thisstaff; if (!$cfg - || !($this->checkStaffPerm($thisstaff, - TicketModel::PERM_EDIT))) + || !($this->checkStaffPerm($thisstaff, + TicketModel::PERM_EDIT)) + ) { return false; - - $fields=array(); + } + $fields = array(); $fields['topicId'] = array('type'=>'int', 'required'=>1, 'error'=>__('Help topic selection is required')); $fields['slaId'] = array('type'=>'int', 'required'=>0, 'error'=>__('Select a valid SLA')); $fields['duedate'] = array('type'=>'date', 'required'=>0, 'error'=>__('Invalid date format - must be MM/DD/YY')); $fields['user_id'] = array('type'=>'int', 'required'=>0, 'error'=>__('Invalid user-id')); - if(!Validator::process($fields, $vars, $errors) && !$errors['err']) + if (!Validator::process($fields, $vars, $errors) && !$errors['err']) $errors['err'] = __('Missing or invalid data - check the errors and try again'); - if($vars['duedate']) { - if($this->isClosed()) + if ($vars['duedate']) { + if ($this->isClosed()) $errors['duedate']=__('Due date can NOT be set on a closed ticket'); - elseif(!$vars['time'] || strpos($vars['time'],':')===false) + elseif (!$vars['time'] || strpos($vars['time'],':') === false) $errors['time']=__('Select a time from the list'); - elseif(strtotime($vars['duedate'].' '.$vars['time'])===false) + elseif (strtotime($vars['duedate'].' '.$vars['time']) === false) $errors['duedate']=__('Invalid due date'); - elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time()) + // FIXME: Using time() violates database and user timezone + elseif (strtotime($vars['duedate'].' '.$vars['time']) <= time()) $errors['duedate']=__('Due date must be in the future'); } @@ -2611,21 +2524,20 @@ implements RestrictedAccess, Threadable, TemplateVariable { if ($errors) return false; - $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW() ' - .' ,topic_id='.db_input($vars['topicId']) - .' ,sla_id='.db_input($vars['slaId']) - .' ,source='.db_input($vars['source']) - .' ,duedate='.($vars['duedate']?db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))):'NULL'); + $this->topic_id = $vars['topic_id']; + $this->sla_id = $vars['sla_id']; + $this->source = $vars['source']; + $this->duedate = $vars['duedate'] + ? date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time'])) + : null; - if($vars['user_id']) - $sql.=', user_id='.db_input($vars['user_id']); - if($vars['duedate']) { //We are setting new duedate... - $sql.=' ,isoverdue=0'; - } - - $sql.=' WHERE ticket_id='.db_input($this->getId()); + if ($vars['user_id']) + $this->user_id = $vars['user_id']; + if ($vars['duedate']) + // We are setting new duedate... + $this->isoverdue = 0; - if(!db_query($sql) || !db_affected_rows()) + if (!$this->save()) return false; if ($vars['note']) @@ -2652,13 +2564,12 @@ implements RestrictedAccess, Threadable, TemplateVariable { if ($changes) $this->logEvent('edited', array('fields' => $changes)); - // Reload the ticket so we can do further checking - $this->reload(); - // Reselect SLA if transient if (!$keepSLA - && (!$this->getSLA() || $this->getSLA()->isTransient())) + && (!$this->getSLA() || $this->getSLA()->isTransient()) + ) { $this->selectSLAId(); + } // Update estimated due date in database $estimatedDueDate = $this->getEstDueDate(); @@ -2666,9 +2577,9 @@ implements RestrictedAccess, Threadable, TemplateVariable { // Clear overdue flag if duedate or SLA changes and the ticket is no longer overdue. if($this->isOverdue() - && (!$estimatedDueDate //Duedate + SLA cleared - || Misc::db2gmtime($estimatedDueDate) > Misc::gmtime() //New due date in the future. - )) { + && (!$estimatedDueDate //Duedate + SLA cleared + || Misc::db2gmtime($estimatedDueDate) > Misc::gmtime() //New due date in the future. + )) { $this->clearOverdue(); } @@ -2676,60 +2587,37 @@ implements RestrictedAccess, Threadable, TemplateVariable { return true; } - /*============== Static functions. Use Ticket::function(params); =============nolint*/ - function getIdByNumber($number, $email=null) { + function getIdByNumber($number, $email=null, $ticket=false) { - if(!$number) + if (!$number) return 0; - $sql ='SELECT ticket.ticket_id FROM '.TICKET_TABLE.' ticket ' - .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id' - .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id' - .' WHERE ticket.`number`='.db_input($number); + $query = static::objects() + ->filter(array('number' => $number)); - if($email) - $sql .= ' AND email.address = '.db_input($email); + if ($email) + $query->filter(array('user__emails__address' => $email)); - if(($res=db_query($sql)) && db_num_rows($res)) - list($id)=db_fetch_row($res); - return $id; - } - - - - function lookup($id) { //Assuming local ID is the only lookup used! - return ($id - && is_numeric($id) - && ($ticket= new Ticket($id)) - && $ticket->getId()==$id) - ?$ticket:null; + if (!$ticket) { + $query = $query->values_flat('ticket_id'); + if ($row = $query->first()) + return $row[0]; + } + else { + return $query->first(); + } } function lookupByNumber($number, $email=null) { - return self::lookup(self:: getIdByNumber($number, $email)); + return self::getIdByNumber($number, $email, true); } static function isTicketNumberUnique($number) { - return 0 == db_num_rows(db_query( - 'SELECT ticket_id FROM '.TICKET_TABLE.' WHERE `number`='.db_input($number))); - } - - function getIdByMessageId($mid, $email) { - - if(!$mid || !$email) - return 0; - - $sql='SELECT ticket.ticket_id FROM '.TICKET_TABLE. ' ticket '. - ' LEFT JOIN '.TICKET_THREAD_TABLE.' msg USING(ticket_id) '. - ' INNER JOIN '.TICKET_EMAIL_INFO_TABLE.' emsg ON (msg.id = emsg.message_id) '. - ' WHERE email_mid='.db_input($mid).' AND email='.db_input($email); - $id=0; - if(($res=db_query($sql)) && db_num_rows($res)) - list($id)=db_fetch_row($res); - - return $id; + return 0 === static::objects() + ->filter(array('number' => $number)) + ->count(); } /* Quick staff's tickets stats */ @@ -3187,28 +3075,28 @@ implements RestrictedAccess, Threadable, TemplateVariable { //We are ready son...hold on to the rails. $number = $topic ? $topic->getNewTicketNumber() : $cfg->getNewTicketNumber(); - $sql='INSERT INTO '.TICKET_TABLE.' SET created=NOW() ' - .' ,lastupdate= NOW() ' - .' ,lastmessage= NOW()' - .' ,user_id='.db_input($user->getId()) - .' ,`number`='.db_input($number) - .' ,dept_id='.db_input($deptId) - .' ,topic_id='.db_input($topicId) - .' ,ip_address='.db_input($ipaddress) - .' ,source='.db_input($source); + $ticket = parent::create(array( + 'created' => SqlFunction::NOW(), + 'lastupdate' => SqlFunction::NOW(), + 'user' => $user, + 'dept' => $deptId, + 'topicId' => $topicId, + 'ip_address' => $ipaddress, + 'source' => $source, + )); if (isset($vars['emailId']) && $vars['emailId']) - $sql.=', email_id='.db_input($vars['emailId']); + $ticket->email_id = $vars['emailId']; //Make sure the origin is staff - avoid firebug hack! - if($vars['duedate'] && !strcasecmp($origin,'staff')) - $sql.=' ,duedate='.db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))); + if ($vars['duedate'] && !strcasecmp($origin,'staff')) + $ticket->duedate = date('Y-m-d G:i', + Misc::dbtime($vars['duedate'].' '.$vars['time'])); - if(!db_query($sql) - || !($id=db_insert_id()) - || !($thread=TicketThread::create($id)) - || !($ticket =Ticket::lookup($id))) + if (!$ticket->save()) + return null; + if (!($thread = TicketThread::create($ticket->getId()))) return null; /* -------------------- POST CREATE ------------------------ */ @@ -3221,12 +3109,12 @@ implements RestrictedAccess, Threadable, TemplateVariable { $form->setAnswer('subject', $topic->getFullName()); } } - $form->setTicketId($id); + $form->setTicketId($ticket->getId()); $form->save(); // Save the form data from the help-topic form, if any foreach ($topic_forms as $topic_form) { - $topic_form->setTicketId($id); + $topic_form->setTicketId($ticket->getId()); $topic_form->save(); } @@ -3270,7 +3158,7 @@ implements RestrictedAccess, Threadable, TemplateVariable { $ticket->selectSLAId($vars['slaId']); // Assign ticket to staff or team (new ticket by staff) - if($vars['assignId']) { + if ($vars['assignId']) { $ticket->assign($vars['assignId'], $vars['note']); } else { @@ -3309,20 +3197,20 @@ implements RestrictedAccess, Threadable, TemplateVariable { if ($autorespond && $message instanceof ThreadEntry && $message->isAutoReply()) $autorespond = false; - //post canned auto-response IF any (disables new ticket auto-response). + // Post canned auto-response IF any (disables new ticket auto-response). if ($vars['cannedResponseId'] && $ticket->postCannedReply($vars['cannedResponseId'], $message, $autorespond)) { $ticket->markUnAnswered(); //Leave the ticket as unanswred. $autorespond = false; } - //Check department's auto response settings + // Check department's auto response settings // XXX: Dept. setting doesn't affect canned responses. - if($autorespond && $dept && !$dept->autoRespONNewTicket()) + if ($autorespond && $dept && !$dept->autoRespONNewTicket()) $autorespond=false; - //Don't send alerts to staff when the message is a bounce - // this is necessary to avoid possible loop (especially on new ticket) + // Don't send alerts to staff when the message is a bounce + // this is necessary to avoid possible loop (especially on new ticket) if ($alertstaff && $message instanceof ThreadEntry && $message->isBounce()) $alertstaff = false; @@ -3330,10 +3218,11 @@ implements RestrictedAccess, Threadable, TemplateVariable { $ticket->onNewTicket($message, $autorespond, $alertstaff); /************ check if the user JUST reached the max. open tickets limit **********/ - if($cfg->getMaxOpenTickets()>0 - && ($user=$ticket->getOwner()) - && ($user->getNumOpenTickets()==$cfg->getMaxOpenTickets())) { - $ticket->onOpenLimit(($autorespond && strcasecmp($origin, 'staff'))); + if ($cfg->getMaxOpenTickets()>0 + && ($user=$ticket->getOwner()) + && ($user->getNumOpenTickets()==$cfg->getMaxOpenTickets()) + ) { + $ticket->onOpenLimit($autorespond && strcasecmp($origin, 'staff')); } /* Start tracking ticket lifecycle events */ @@ -3354,11 +3243,16 @@ implements RestrictedAccess, Threadable, TemplateVariable { if (!$thisstaff || !$thisstaff->hasPerm(TicketModel::PERM_CREATE)) return false; - if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other'))) - $errors['source']=sprintf(__('Invalid source given - %s'),Format::htmlchars($vars['source'])); + if ($vars['source'] && !in_array( + strtolower($vars['source']), array('email','phone','other')) + ) { + $errors['source'] = sprintf( + __('Invalid source given - %s'),Format::htmlchars($vars['source']) + ); + } if (!$vars['uid']) { - //Special validation required here + // Special validation required here if (!$vars['email'] || !Validator::is_email($vars['email'])) $errors['email'] = __('Valid email address is required'); @@ -3369,15 +3263,14 @@ implements RestrictedAccess, Threadable, TemplateVariable { if (!$thisstaff->hasPerm(TicketModel::PERM_ASSIGN)) unset($vars['assignId']); - //TODO: Deny action based on selected department. - + // TODO: Deny action based on selected department. $create_vars = $vars; $tform = TicketForm::objects()->one()->getForm($create_vars); $create_vars['cannedattachments'] = $tform->getField('message')->getWidget()->getAttachments()->getClean(); - if(!($ticket=Ticket::create($create_vars, $errors, 'staff', false))) + if (!($ticket=Ticket::create($create_vars, $errors, 'staff', false))) return false; $vars['msgId']=$ticket->getLastMsgId(); @@ -3388,7 +3281,6 @@ implements RestrictedAccess, Threadable, TemplateVariable { // post response - if any $response = null; if($vars['response'] && $role->hasPerm(TicketModel::PERM_REPLY)) { - $vars['response'] = $ticket->replaceVars($vars['response']); // $vars['cannedatachments'] contains the attachments placed on // the response form. @@ -3405,41 +3297,42 @@ implements RestrictedAccess, Threadable, TemplateVariable { $ticket->reload(); - if(!$cfg->notifyONNewStaffTicket() - || !isset($vars['alertuser']) - || !($dept=$ticket->getDept())) + if (!$cfg->notifyONNewStaffTicket() + || !isset($vars['alertuser']) + || !($dept=$ticket->getDept()) + ) { return $ticket; //No alerts. - - //Send Notice to user --- if requested AND enabled!! - if(($tpl=$dept->getTemplate()) - && ($msg=$tpl->getNewTicketNoticeMsgTemplate()) - && ($email=$dept->getEmail())) { - + } + // Send Notice to user --- if requested AND enabled!! + if (($tpl=$dept->getTemplate()) + && ($msg=$tpl->getNewTicketNoticeMsgTemplate()) + && ($email=$dept->getEmail()) + ) { $message = (string) $ticket->getLastMessage(); - if($response) { + if ($response) { $message .= ($cfg->isRichTextEnabled()) ? "<br><br>" : "\n\n"; $message .= $response->getBody(); } - if($vars['signature']=='mine') + if ($vars['signature']=='mine') $signature=$thisstaff->getSignature(); - elseif($vars['signature']=='dept' && $dept && $dept->isPublic()) + elseif ($vars['signature']=='dept' && $dept && $dept->isPublic()) $signature=$dept->getSignature(); else $signature=''; - $attachments =($cfg->emailAttachments() && $response)?$response->getAttachments():array(); + $attachments = ($cfg->emailAttachments() && $response) + ? $response->getAttachments() : array(); $msg = $ticket->replaceVars($msg->asArray(), - array( - 'message' => $message, - 'signature' => $signature, - 'response' => ($response) ? $response->getBody() : '', - 'recipient' => $ticket->getOwner(), //End user - 'staff' => $thisstaff, - ) - ); - + array( + 'message' => $message, + 'signature' => $signature, + 'response' => ($response) ? $response->getBody() : '', + 'recipient' => $ticket->getOwner(), //End user + 'staff' => $thisstaff, + ) + ); $references = array(); $message = $ticket->getLastMessage(); if (isset($message)) @@ -3453,12 +3346,21 @@ implements RestrictedAccess, Threadable, TemplateVariable { $email->send($ticket->getOwner(), $msg['subj'], $msg['body'], $attachments, $options); } - return $ticket; - } - function checkOverdue() { + static function checkOverdue() { + /* + $overdue = static::objects() + ->filter(array( + 'isoverdue' => 0, + Q::any(array( + Q::all(array( + 'reopened__isnull' => true, + 'duedate__isnull' => true, + + Punt for now + */ $sql='SELECT ticket_id FROM '.TICKET_TABLE.' T1 ' .' INNER JOIN '.TICKET_STATUS_TABLE.' status diff --git a/include/class.user.php b/include/class.user.php index 337eaf9501aba38f68752d0b1cdc7a1e187c773a..655edfd00e4dcb9cd6f6023018a1192ff54c02c6 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -39,7 +39,7 @@ class UserModel extends VerySimpleModel { static $meta = array( 'table' => USER_TABLE, 'pk' => array('id'), - 'select_related' => array('account', 'default_email'), + 'select_related' => array('default_email', 'org', 'account'), 'joins' => array( 'emails' => array( 'reverse' => 'UserEmailModel.user',