Server IP : 184.154.167.98 / Your IP : 3.16.212.203 Web Server : Apache System : Linux pink.dnsnetservice.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 User : puertode ( 1767) PHP Version : 7.2.34 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /home/puertode/www/mesa/include/ |
Upload File : |
<?php /********************************************************************* class.task.php Task Peter Rotich <peter@osticket.com> Copyright (c) 2014 osTicket http://www.osticket.com Released under the GNU General Public License WITHOUT ANY WARRANTY. See LICENSE.TXT for details. vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ include_once INCLUDE_DIR.'class.role.php'; class TaskModel extends VerySimpleModel { static $meta = array( 'table' => TASK_TABLE, 'pk' => array('id'), 'joins' => array( 'dept' => array( 'constraint' => array('dept_id' => 'Dept.id'), ), 'lock' => array( 'constraint' => array('lock_id' => 'Lock.lock_id'), 'null' => true, ), 'staff' => array( 'constraint' => array('staff_id' => 'Staff.staff_id'), 'null' => true, ), 'team' => array( 'constraint' => array('team_id' => 'Team.team_id'), 'null' => true, ), 'thread' => array( 'constraint' => array( 'id' => 'TaskThread.object_id', "'A'" => 'TaskThread.object_type', ), 'list' => false, 'null' => false, ), 'cdata' => array( 'constraint' => array('id' => 'TaskCData.task_id'), 'list' => false, ), 'entries' => array( 'constraint' => array( "'A'" => 'DynamicFormEntry.object_type', 'id' => 'DynamicFormEntry.object_id', ), 'list' => true, ), 'ticket' => array( 'constraint' => array( 'object_id' => 'Ticket.ticket_id', ), 'null' => true, ), ), ); const PERM_CREATE = 'task.create'; const PERM_EDIT = 'task.edit'; const PERM_ASSIGN = 'task.assign'; const PERM_TRANSFER = 'task.transfer'; const PERM_REPLY = 'task.reply'; const PERM_CLOSE = 'task.close'; const PERM_DELETE = 'task.delete'; static protected $perms = array( self::PERM_CREATE => array( 'title' => /* @trans */ 'Create', 'desc' => /* @trans */ 'Ability to create tasks'), self::PERM_EDIT => array( 'title' => /* @trans */ 'Edit', 'desc' => /* @trans */ 'Ability to edit tasks'), self::PERM_ASSIGN => array( 'title' => /* @trans */ 'Assign', 'desc' => /* @trans */ 'Ability to assign tasks to agents or teams'), self::PERM_TRANSFER => array( 'title' => /* @trans */ 'Transfer', 'desc' => /* @trans */ 'Ability to transfer tasks between departments'), self::PERM_REPLY => array( 'title' => /* @trans */ 'Post Reply', 'desc' => /* @trans */ 'Ability to post task update'), self::PERM_CLOSE => array( 'title' => /* @trans */ 'Close', 'desc' => /* @trans */ 'Ability to close tasks'), self::PERM_DELETE => array( 'title' => /* @trans */ 'Delete', 'desc' => /* @trans */ 'Ability to delete tasks'), ); const ISOPEN = 0x0001; const ISOVERDUE = 0x0002; protected function hasFlag($flag) { return ($this->get('flags') & $flag) !== 0; } protected function clearFlag($flag) { return $this->set('flags', $this->get('flags') & ~$flag); } protected function setFlag($flag) { return $this->set('flags', $this->get('flags') | $flag); } function getId() { return $this->id; } function getNumber() { return $this->number; } function getStaffId() { return $this->staff_id; } function getStaff() { return $this->staff; } function getTeamId() { return $this->team_id; } function getTeam() { return $this->team; } function getDeptId() { return $this->dept_id; } function getDept() { return $this->dept; } function getCreateDate() { return $this->created; } function getDueDate() { return $this->duedate; } function getCloseDate() { return $this->isClosed() ? $this->closed : ''; } function isOpen() { return $this->hasFlag(self::ISOPEN); } function isClosed() { return !$this->isOpen(); } function isCloseable() { if ($this->isClosed()) return true; $warning = null; if ($this->getMissingRequiredFields()) { $warning = sprintf( __( '%1$s is missing data on %2$s one or more required fields %3$s and cannot be closed'), __('This task'), '', ''); } return $warning ?: true; } protected function close() { return $this->clearFlag(self::ISOPEN); } protected function reopen() { return $this->setFlag(self::ISOPEN); } function isAssigned($to=null) { if (!$this->isOpen()) return false; if (is_null($to)) return ($this->getStaffId() || $this->getTeamId()); switch (true) { case $to instanceof Staff: return ($to->getId() == $this->getStaffId() || $to->isTeamMember($this->getTeamId())); break; case $to instanceof Team: return ($to->getId() == $this->getTeamId()); break; } return false; } function isOverdue() { return $this->hasFlag(self::ISOVERDUE); } static function getPermissions() { return self::$perms; } } RolePermission::register(/* @trans */ 'Tasks', TaskModel::getPermissions()); class Task extends TaskModel implements RestrictedAccess, Threadable { var $form; var $entry; var $_thread; var $_entries; var $_answers; var $lastrespondent; function __onload() { $this->loadDynamicData(); } function loadDynamicData() { if (!isset($this->_answers)) { $this->_answers = array(); foreach (DynamicFormEntryAnswer::objects() ->filter(array( 'entry__object_id' => $this->getId(), 'entry__object_type' => ObjectModel::OBJECT_TYPE_TASK )) as $answer ) { $tag = mb_strtolower($answer->field->name) ?: 'field.' . $answer->field->id; $this->_answers[$tag] = $answer; } } return $this->_answers; } function getStatus() { return $this->isOpen() ? __('Open') : __('Completed'); } function getTitle() { return $this->__cdata('title', ObjectModel::OBJECT_TYPE_TASK); } function checkStaffPerm($staff, $perm=null) { // Must be a valid staff if (!$staff instanceof Staff && !($staff=Staff::lookup($staff))) return false; // Check access based on department or assignment if (!$staff->canAccessDept($this->getDept()) && $this->isOpen() && $staff->getId() != $this->getStaffId() && !$staff->isTeamMember($this->getTeamId())) return false; // At this point staff has access unless a specific permission is // requested if ($perm === null) return true; // Permission check requested -- get role. if (!($role=$staff->getRole($this->getDept()))) return false; // Check permission based on the effective role return $role->hasPerm($perm); } function getAssignee() { if (!$this->isOpen() || !$this->isAssigned()) return false; if ($this->staff) return $this->staff; if ($this->team) return $this->team; return null; } function getAssigneeId() { if (!($assignee=$this->getAssignee())) return null; $id = ''; if ($assignee instanceof Staff) $id = 's'.$assignee->getId(); elseif ($assignee instanceof Team) $id = 't'.$assignee->getId(); return $id; } function getAssignees() { $assignees=array(); if ($this->staff) $assignees[] = $this->staff->getName(); //Add team assignment if ($this->team) $assignees[] = $this->team->getName(); return $assignees; } function getAssigned($glue='/') { $assignees = $this->getAssignees(); return $assignees ? implode($glue, $assignees):''; } function getLastRespondent() { if (!isset($this->lastrespondent)) { $this->lastrespondent = Staff::objects() ->filter(array( 'staff_id' => static::objects() ->filter(array( 'thread__entries__type' => 'R', 'thread__entries__staff_id__gt' => 0 )) ->values_flat('thread__entries__staff_id') ->order_by('-thread__entries__id') ->limit('1,1') )) ->first() ?: false; } return $this->lastrespondent; } function getField($fid) { if (is_numeric($fid)) return $this->getDymanicFieldById($fid); // Special fields switch ($fid) { case 'duedate': return DateTimeField::init(array( 'id' => $fid, 'name' => $fid, 'default' => Misc::db2gmtime($this->getDueDate()), 'label' => __('Due Date'), 'configuration' => array( 'min' => Misc::gmtime(), 'time' => true, 'gmt' => false, 'future' => true, ) )); } } function getDymanicFieldById($fid) { foreach (DynamicFormEntry::forObject($this->getId(), ObjectModel::OBJECT_TYPE_TASK) as $form) { foreach ($form->getFields() as $field) if ($field->getId() == $fid) return $field; } } function getDynamicFields($criteria=array()) { $fields = DynamicFormField::objects()->filter(array( 'id__in' => $this->entries ->filter($criteria) ->values_flat('answers__field_id'))); return ($fields && count($fields)) ? $fields : array(); } function getMissingRequiredFields() { return $this->getDynamicFields(array( 'answers__field__flags__hasbit' => DynamicFormField::FLAG_ENABLED, 'answers__field__flags__hasbit' => DynamicFormField::FLAG_CLOSE_REQUIRED, 'answers__value__isnull' => true, )); } function getParticipants() { $participants = array(); foreach ($this->getThread()->collaborators as $c) $participants[] = $c->getName(); return $participants ? implode(', ', $participants) : ' '; } function getThreadId() { return $this->thread->getId(); } function getThread() { return $this->thread; } function getThreadEntry($id) { return $this->getThread()->getEntry($id); } function getThreadEntries($type=false) { $thread = $this->getThread()->getEntries(); if ($type && is_array($type)) $thread->filter(array('type__in' => $type)); return $thread; } function postThreadEntry($type, $vars, $options=array()) { $errors = array(); $poster = isset($options['poster']) ? $options['poster'] : null; $alert = isset($options['alert']) ? $options['alert'] : true; switch ($type) { case 'N': case 'M': return $this->getThread()->addDescription($vars); break; default: return $this->postNote($vars, $errors, $poster, $alert); } } function getForm() { if (!isset($this->form)) { // Look for the entry first if ($this->form = DynamicFormEntry::lookup( array('object_type' => ObjectModel::OBJECT_TYPE_TASK))) { return $this->form; } // Make sure the form is in the database elseif (!($this->form = DynamicForm::lookup( array('type' => ObjectModel::OBJECT_TYPE_TASK)))) { $this->__loadDefaultForm(); return $this->getForm(); } // Create an entry to be saved later $this->form = $this->form->instanciate(); $this->form->object_type = ObjectModel::OBJECT_TYPE_TASK; } return $this->form; } function getAssignmentForm($source=null, $options=array()) { global $thisstaff; $prompt = $assignee = ''; // Possible assignees $dept = $this->getDept(); switch (strtolower($options['target'])) { case 'agents': if (!$source && $this->isOpen() && $this->staff) $assignee = sprintf('s%d', $this->staff->getId()); $prompt = __('Select an Agent'); break; case 'teams': if (!$source && $this->isOpen() && $this->team) $assignee = sprintf('t%d', $this->team->getId()); $prompt = __('Select a Team'); break; } // Default to current assignee if source is not set if (!$source) $source = array('assignee' => array($assignee)); $form = AssignmentForm::instantiate($source, $options); // Field configurations if ($f=$form->getField('assignee')) { $f->configure('dept', $dept); $f->configure('staff', $thisstaff); if ($prompt) $f->configure('prompt', $prompt); if ($options['target']) $f->configure('target', $options['target']); } return $form; } function getClaimForm($source=null, $options=array()) { global $thisstaff; $id = sprintf('s%d', $thisstaff->getId()); if(!$source) $source = array('assignee' => array($id)); $form = ClaimForm::instantiate($source, $options); $form->setAssignees(array($id => $thisstaff->getName())); return $form; } function getTransferForm($source=null) { if (!$source) $source = array('dept' => array($this->getDeptId())); return TransferForm::instantiate($source); } function addDynamicData($data) { $tf = TaskForm::getInstance($this->id, true); foreach ($tf->getFields() as $f) if (isset($data[$f->get('name')])) $tf->setAnswer($f->get('name'), $data[$f->get('name')]); $tf->save(); return $tf; } function getDynamicData($create=true) { if (!isset($this->_entries)) { $this->_entries = DynamicFormEntry::forObject($this->id, ObjectModel::OBJECT_TYPE_TASK)->all(); if (!$this->_entries && $create) { $f = TaskForm::getInstance($this->id, true); $f->save(); $this->_entries[] = $f; } } return $this->_entries ?: array(); } function setStatus($status, $comments='', &$errors=array()) { global $thisstaff; $ecb = null; switch($status) { case 'open': if ($this->isOpen()) return false; $this->reopen(); $this->closed = null; $ecb = function ($t) use($thisstaff) { $t->logEvent('reopened', false, null, 'closed'); if ($t->ticket) { $t->ticket->reopen(); $vars = array( 'title' => sprintf('Task %s Reopened', $t->getNumber()), 'note' => __('Task reopened') ); $t->ticket->logNote($vars['title'], $vars['note'], $thisstaff); } }; break; case 'closed': if ($this->isClosed()) return false; // Check if task is closeable $closeable = $this->isCloseable(); if ($closeable !== true) $errors['err'] = $closeable ?: sprintf(__('%s cannot be closed'), __('This task')); if ($errors) return false; $this->close(); $this->closed = SqlFunction::NOW(); $ecb = function($t) use($thisstaff) { $t->logEvent('closed'); if ($t->ticket) { $vars = array( 'title' => sprintf('Task %s Closed', $t->getNumber()), 'note' => __('Task closed') ); $t->ticket->logNote($vars['title'], $vars['note'], $thisstaff); } }; break; default: return false; } if (!$this->save(true)) return false; // Log events via callback if ($ecb) $ecb($this); if ($comments) { $errors = array(); $this->postNote(array( 'note' => $comments, 'title' => sprintf( __('Status changed to %s'), $this->getStatus()) ), $errors, $thisstaff); } return true; } function to_json() { $info = array( 'id' => $this->getId(), 'title' => $this->getTitle() ); return JsonDataEncoder::encode($info); } function __cdata($field, $ftype=null) { foreach ($this->getDynamicData() as $e) { // Make sure the form type matches if (!$e->form || ($ftype && $ftype != $e->form->get('type'))) continue; // Get the named field and return the answer if ($a = $e->getAnswer($field)) return $a; } return null; } function __toString() { return (string) $this->getTitle(); } /* util routines */ function logEvent($state, $data=null, $user=null, $annul=null) { switch ($state) { case 'transferred': case 'edited': $type = $data; $type['type'] = $state; break; case 'assigned': break; default: $type = array('type' => $state); break; } if ($type) Signal::send('object.created', $this, $type); $this->getThread()->getEvents()->log($this, $state, $data, $user, $annul); } function claim(ClaimForm $form, &$errors) { global $thisstaff; $dept = $this->getDept(); $assignee = $form->getAssignee(); if (!($assignee instanceof Staff) || !$thisstaff || $thisstaff->getId() != $assignee->getId()) { $errors['err'] = __('Unknown assignee'); } elseif (!$assignee->isAvailable()) { $errors['err'] = __('Agent is unavailable for assignment'); } elseif (!$dept->canAssign($assignee)) { $errors['err'] = __('Permission denied'); } if ($errors) return false; $type = array('type' => 'assigned', 'claim' => true); Signal::send('object.edited', $this, $type); return $this->assignToStaff($assignee, $form->getComments(), false); } function assignToStaff($staff, $note, $alert=true) { if(!is_object($staff) && !($staff = Staff::lookup($staff))) return false; if (!$staff->isAvailable()) return false; $this->staff_id = $staff->getId(); if (!$this->save()) return false; $this->onAssignment($staff, $note, $alert); global $thisstaff; $data = array(); if ($thisstaff && $staff->getId() == $thisstaff->getId()) $data['claim'] = true; else $data['staff'] = $staff->getId(); $this->logEvent('assigned', $data); return true; } function assign(AssignmentForm $form, &$errors, $alert=true) { global $thisstaff; $evd = array(); $audit = array(); $assignee = $form->getAssignee(); if ($assignee instanceof Staff) { $dept = $this->getDept(); if ($this->getStaffId() == $assignee->getId()) { $errors['assignee'] = sprintf(__('%s already assigned to %s'), __('Task'), __('the agent') ); } elseif(!$assignee->isAvailable()) { $errors['assignee'] = __('Agent is unavailable for assignment'); } elseif (!$dept->canAssign($assignee)) { $errors['err'] = __('Permission denied'); } else { $this->staff_id = $assignee->getId(); if ($thisstaff && $thisstaff->getId() == $assignee->getId()) $evd['claim'] = true; else $evd['staff'] = array($assignee->getId(), $assignee->getName()); $audit = array('staff' => $assignee->getName()->name); } } elseif ($assignee instanceof Team) { if ($this->getTeamId() == $assignee->getId()) { $errors['assignee'] = sprintf(__('%s already assigned to %s'), __('Task'), __('the team') ); } else { $this->team_id = $assignee->getId(); $evd = array('team' => $assignee->getId()); $audit = array('team' => $assignee->getName()); } } else { $errors['assignee'] = __('Unknown assignee'); } if ($errors || !$this->save(true)) return false; $this->logEvent('assigned', $evd); $type = array('type' => 'assigned'); $type += $audit; Signal::send('object.edited', $this, $type); $this->onAssignment($assignee, $form->getField('comments')->getClean(), $alert); return true; } function onAssignment($assignee, $comments='', $alert=true) { global $thisstaff, $cfg; if (!is_object($assignee)) return false; $assigner = $thisstaff ?: __('SYSTEM (Auto Assignment)'); //Assignment completed... post internal note. $note = null; if ($comments) { $title = sprintf(__('Task assigned to %s'), (string) $assignee); $errors = array(); $note = $this->postNote( array('note' => $comments, 'title' => $title), $errors, $assigner, false); } // Send alerts out if enabled. if (!$alert || !$cfg->alertONTaskAssignment()) return false; if (!($dept=$this->getDept()) || !($tpl = $dept->getTemplate()) || !($email = $dept->getAlertEmail()) ) { return true; } // Recipients $recipients = array(); if ($assignee instanceof Staff) { if ($cfg->alertStaffONTaskAssignment()) $recipients[] = $assignee; } elseif (($assignee instanceof Team) && $assignee->alertsEnabled()) { if ($cfg->alertTeamMembersONTaskAssignment() && ($members=$assignee->getMembersForAlerts())) $recipients = array_merge($recipients, $members); elseif ($cfg->alertTeamLeadONTaskAssignment() && ($lead=$assignee->getTeamLead())) $recipients[] = $lead; } if ($recipients && ($msg=$tpl->getTaskAssignmentAlertMsgTemplate())) { $msg = $this->replaceVars($msg->asArray(), array('comments' => $comments, 'assignee' => $assignee, 'assigner' => $assigner ) ); // Send the alerts. $sentlist = array(); $options = $note instanceof ThreadEntry ? array('thread' => $note) : array(); foreach ($recipients as $k => $staff) { if (!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) { continue; } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } return true; } function transfer(TransferForm $form, &$errors, $alert=true) { global $thisstaff, $cfg; $cdept = $this->getDept(); $dept = $form->getDept(); if (!$dept || !($dept instanceof Dept)) $errors['dept'] = __('Department selection is required'); elseif ($dept->getid() == $this->getDeptId()) $errors['dept'] = __('Task already in the department'); else $this->dept_id = $dept->getId(); if ($errors || !$this->save(true)) return false; // Log transfer event $this->logEvent('transferred', array('dept' => $dept->getName())); // Post internal note if any $note = $form->getField('comments')->getClean(); if ($note) { $title = sprintf(__('%1$s transferred from %2$s to %3$s'), __('Task'), $cdept->getName(), $dept->getName()); $_errors = array(); $note = $this->postNote( array('note' => $note, 'title' => $title), $_errors, $thisstaff, false); } // Send alerts if requested && enabled. if (!$alert || !$cfg->alertONTaskTransfer()) return true; if (($email = $dept->getAlertEmail()) && ($tpl = $dept->getTemplate()) && ($msg=$tpl->getTaskTransferAlertMsgTemplate())) { $msg = $this->replaceVars($msg->asArray(), array('comments' => $note, 'staff' => $thisstaff)); // Recipients $recipients = array(); // Assigned staff or team... if any if ($this->isAssigned() && $cfg->alertAssignedONTaskTransfer()) { if($this->getStaffId()) $recipients[] = $this->getStaff(); elseif ($this->getTeamId() && ($team=$this->getTeam()) && ($members=$team->getMembersForAlerts()) ) { $recipients = array_merge($recipients, $members); } } elseif ($cfg->alertDeptMembersONTaskTransfer() && !$this->isAssigned()) { // Only alerts dept members if the task is NOT assigned. foreach ($dept->getMembersForAlerts() as $M) $recipients[] = $M; } // Always alert dept manager?? if ($cfg->alertDeptManagerONTaskTransfer() && ($manager=$dept->getManager())) { $recipients[] = $manager; } $sentlist = $options = array(); if ($note instanceof ThreadEntry) { $options += array('thread'=>$note); } foreach ($recipients as $k=>$staff) { if (!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist) ) { continue; } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } return true; } function postNote($vars, &$errors, $poster='', $alert=true) { global $cfg, $thisstaff; $vars['staffId'] = 0; $vars['poster'] = 'SYSTEM'; if ($poster && is_object($poster)) { $vars['staffId'] = $poster->getId(); $vars['poster'] = $poster->getName(); } elseif ($poster) { //string $vars['poster'] = $poster; } if (!($note=$this->getThread()->addNote($vars, $errors))) return null; $assignee = $this->getStaff(); if (isset($vars['task:status'])) $this->setStatus($vars['task:status']); $this->onActivity(array( 'activity' => $note->getActivity(), 'threadentry' => $note, 'assignee' => $assignee ), $alert); $type = array('type' => 'note'); Signal::send('object.created', $this, $type); return $note; } /* public */ function postReply($vars, &$errors, $alert = true) { global $thisstaff, $cfg; if (!$vars['poster'] && $thisstaff) $vars['poster'] = $thisstaff; if (!$vars['staffId'] && $thisstaff) $vars['staffId'] = $thisstaff->getId(); if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR']) $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; if (!($response = $this->getThread()->addResponse($vars, $errors))) return null; $assignee = $this->getStaff(); if (isset($vars['task:status'])) $this->setStatus($vars['task:status']); /* // TODO: add auto claim setting for tasks. // Claim on response bypasses the department assignment restrictions if ($thisstaff && $this->isOpen() && !$this->getStaffId() && $cfg->autoClaimTasks) ) { $this->staff_id = $thisstaff->getId(); } */ // Send activity alert to agents $activity = $vars['activity'] ?: $response->getActivity(); $this->onActivity( array( 'activity' => $activity, 'threadentry' => $response, 'assignee' => $assignee, )); $this->lastrespondent = $response->staff; $this->save(); // Send alert to collaborators if ($alert && $vars['emailcollab']) { $signature = ''; $this->notifyCollaborators($response, array('signature' => $signature) ); } $type = array('type' => 'message'); Signal::send('object.created', $this, $type); return $response; } function pdfExport($options=array()) { global $thisstaff; require_once(INCLUDE_DIR.'class.pdf.php'); if (!isset($options['psize'])) { if ($_SESSION['PAPER_SIZE']) $psize = $_SESSION['PAPER_SIZE']; elseif (!$thisstaff || !($psize = $thisstaff->getDefaultPaperSize())) $psize = 'Letter'; $options['psize'] = $psize; } $pdf = new Task2PDF($this, $options); $name = 'Task-'.$this->getNumber().'.pdf'; Http::download($name, 'application/pdf', $pdf->output($name, 'S')); //Remember what the user selected - for autoselect on the next print. $_SESSION['PAPER_SIZE'] = $options['psize']; exit; } /* util routines */ function replaceVars($input, $vars = array()) { global $ost; return $ost->replaceTemplateVariables($input, array_merge($vars, array('task' => $this))); } function asVar() { return $this->getNumber(); } function getVar($tag) { global $cfg; if ($tag && is_callable(array($this, 'get'.ucfirst($tag)))) return call_user_func(array($this, 'get'.ucfirst($tag))); switch(mb_strtolower($tag)) { case 'phone': case 'phone_number': return $this->getPhoneNumber(); case 'ticket_link': if ($ticket = $this->ticket) { return sprintf('%s/scp/tickets.php?id=%d#tasks', $cfg->getBaseUrl(), $ticket->getId()); } case 'staff_link': return sprintf('%s/scp/tasks.php?id=%d', $cfg->getBaseUrl(), $this->getId()); case 'create_date': return new FormattedDate($this->getCreateDate()); case 'due_date': if ($due = $this->getDueDate()) return new FormattedDate($due); break; case 'close_date': if ($this->isClosed()) return new FormattedDate($this->getCloseDate()); break; case 'last_update': return new FormattedDate($this->updated); case 'description': return Format::display($this->getThread()->getVar('original') ?: ''); case 'subject': return Format::htmlchars($this->getTitle()); default: if (isset($this->_answers[$tag])) // The answer object is retrieved here which will // automatically invoke the toString() method when the // answer is coerced into text return $this->_answers[$tag]; } return false; } static function getVarScope() { $base = array( 'assigned' => __('Assigned Agent / Team'), 'close_date' => array( 'class' => 'FormattedDate', 'desc' => __('Date Closed'), ), 'create_date' => array( 'class' => 'FormattedDate', 'desc' => __('Date Created'), ), 'dept' => array( 'class' => 'Dept', 'desc' => __('Department'), ), 'description' => __('Description'), 'due_date' => array( 'class' => 'FormattedDate', 'desc' => __('Due Date'), ), 'number' => __('Task Number'), 'recipients' => array( 'class' => 'UserList', 'desc' => __('List of all recipient names'), ), 'status' => __('Status'), 'staff' => array( 'class' => 'Staff', 'desc' => __('Assigned/closing agent'), ), 'subject' => 'Subject', 'team' => array( 'class' => 'Team', 'desc' => __('Assigned/closing team'), ), 'thread' => array( 'class' => 'TaskThread', 'desc' => __('Task Thread'), ), 'staff_link' => __('Link to view the task'), 'ticket_link' => __('Link to view the task inside the ticket'), 'last_update' => array( 'class' => 'FormattedDate', 'desc' => __('Time of last update'), ), ); $extra = VariableReplacer::compileFormScope(TaskForm::getInstance()); return $base + $extra; } function onActivity($vars, $alert=true) { global $cfg, $thisstaff; if (!$alert // Check if alert is enabled || !$cfg->alertONTaskActivity() || !($dept=$this->getDept()) || !($email=$cfg->getAlertEmail()) || !($tpl = $dept->getTemplate()) || !($msg=$tpl->getTaskActivityAlertMsgTemplate()) ) { return; } // Alert recipients $recipients = array(); //Last respondent. if ($cfg->alertLastRespondentONTaskActivity()) $recipients[] = $this->getLastRespondent(); // Assigned staff / team if ($cfg->alertAssignedONTaskActivity()) { 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->getMembersForAlerts()); } // Dept manager if ($cfg->alertDeptManagerONTaskActivity() && $dept && $dept->getManagerId()) $recipients[] = $dept->getManager(); $options = array(); $staffId = $thisstaff ? $thisstaff->getId() : 0; if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) { $options = array('thread' => $vars['threadentry']); // Activity details if (!$vars['message']) $vars['message'] = $vars['threadentry']; // Staff doing the activity $staffId = $vars['threadentry']->getStaffId() ?: $staffId; } $msg = $this->replaceVars($msg->asArray(), array( 'note' => $vars['threadentry'], // For compatibility 'activity' => $vars['activity'], 'message' => $vars['message'])); $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 task || ($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 addCollaborator($user, $vars, &$errors, $event=true) { if ($c = $this->getThread()->addCollaborator($user, $vars, $errors, $event)) { $this->collaborators = null; $this->recipients = null; } return $c; } /* * Notify collaborators on response or new message * */ function notifyCollaborators($entry, $vars = array()) { global $cfg; if (!$entry instanceof ThreadEntry || !($recipients=$this->getThread()->getRecipients()) || !($dept=$this->getDept()) || !($tpl=$dept->getTemplate()) || !($msg=$tpl->getTaskActivityNoticeMsgTemplate()) || !($email=$dept->getEmail()) ) { return; } // Who posted the entry? $skip = array(); if ($entry instanceof MessageThreadEntry) { $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)) { $skip[$R2->getUserId()] = true; break; } } } } else { $poster = $entry->getStaff(); } $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('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, $options); } } function update($forms, $vars, &$errors) { global $thisstaff; if (!$forms || !$this->checkStaffPerm($thisstaff, Task::PERM_EDIT)) return false; foreach ($forms as $form) { $form->setSource($vars); if (!$form->isValid(function($f) { return $f->isVisibleToStaff() && $f->isEditableToStaff(); }, array('mode'=>'edit'))) { $errors = array_merge($errors, $form->errors()); } } if ($errors) return false; // Update dynamic meta-data $changes = array(); foreach ($forms as $f) { $changes += $f->getChanges(); $f->save(); } if ($vars['note']) { $_errors = array(); $this->postNote(array( 'note' => $vars['note'], 'title' => _S('Task Updated'), ), $_errors, $thisstaff); } $this->updated = SqlFunction::NOW(); if ($changes) $this->logEvent('edited', array('fields' => $changes)); Signal::send('model.updated', $this); return $this->save(); } function updateField($form, &$errors) { global $thisstaff, $cfg; if (!($field = $form->getField('field'))) return null; if (!($changes = $field->getChanges())) $errors['field'] = sprintf(__('%s is already assigned this value'), __($field->getLabel())); else { if ($field->answer) { if (!$field->isEditableToStaff()) $errors['field'] = sprintf(__('%s can not be edited'), __($field->getLabel())); elseif (!$field->save(true)) $errors['field'] = __('Unable to update field'); $changes['fields'] = array($field->getId() => $changes); } else { $val = $field->getClean(); $fid = $field->get('name'); // Convert duedate to DB timezone. if ($fid == 'duedate') { if (empty($val)) $val = null; elseif ($dt = Format::parseDateTime($val)) { // Make sure the due date is valid if (Misc::user2gmtime($val) <= Misc::user2gmtime()) $errors['field']=__('Due date must be in the future'); else { $dt->setTimezone(new DateTimeZone($cfg->getDbTimezone())); $val = $dt->format('Y-m-d H:i:s'); } } } elseif (is_object($val)) $val = $val->getId(); $changes = array(); $this->{$fid} = $val; foreach ($this->dirty as $F=>$old) { switch ($F) { case 'sla_id': case 'topic_id': case 'user_id': case 'source': $changes[$F] = array($old, $this->{$F}); } } if (!$errors && !$this->save()) $errors['field'] = __('Unable to update field'); } } if ($errors) return false; // Record the changes $this->logEvent('edited', $changes); // Log comments (if any) if (($comments = $form->getField('comments')->getClean())) { $title = sprintf(__('%s updated'), __($field->getLabel())); $_errors = array(); $this->postNote( array('note' => $comments, 'title' => $title), $_errors, $thisstaff, false); } $this->updated = SqlFunction::NOW(); $this->save(); Signal::send('model.updated', $this); return true; } /* static routines */ static function lookupIdByNumber($number) { if (($task = self::lookup(array('number' => $number)))) return $task->getId(); } static function isNumberUnique($number) { return !self::lookupIdByNumber($number); } static function create($vars=false) { global $thisstaff, $cfg; if (!is_array($vars) || !$thisstaff || !$thisstaff->hasPerm(Task::PERM_CREATE, false)) return null; $task = new static(array( 'flags' => self::ISOPEN, 'object_id' => $vars['object_id'], 'object_type' => $vars['object_type'], 'number' => $cfg->getNewTaskNumber(), 'created' => new SqlFunction('NOW'), 'updated' => new SqlFunction('NOW'), )); if ($vars['internal_formdata']['dept_id']) $task->dept_id = $vars['internal_formdata']['dept_id']; if ($vars['internal_formdata']['duedate']) $task->duedate = date('Y-m-d G:i', Misc::dbtime($vars['internal_formdata']['duedate'])); if (!$task->save(true)) return false; // Add dynamic data $task->addDynamicData($vars['default_formdata']); // Create a thread + message. $thread = TaskThread::create($task); $desc = $thread->addDescription($vars); // Set the ORIGINAL_MESSAGE Flag if Description is added if ($desc) { $desc->setFlag(ThreadEntry::FLAG_ORIGINAL_MESSAGE); $desc->save(); } $task->logEvent('created', null, $thisstaff); // Get role for the dept $role = $thisstaff->getRole($task->getDept()); // Assignment $assignee = $vars['internal_formdata']['assignee']; if ($assignee // skip assignment if the user doesn't have perm. && $role->hasPerm(Task::PERM_ASSIGN)) { $_errors = array(); $assigneeId = sprintf('%s%d', ($assignee instanceof Staff) ? 's' : 't', $assignee->getId()); $form = AssignmentForm::instantiate(array('assignee' => $assigneeId)); $task->assign($form, $_errors); } $task->onNewTask(); Signal::send('task.created', $task); return $task; } function onNewTask($vars=array()) { global $cfg, $thisstaff; if (!$cfg->alertONNewTask() // Check if alert is enabled || !($dept=$this->getDept()) || ($dept->isGroupMembershipEnabled() == Dept::ALERTS_DISABLED) || !($email=$cfg->getAlertEmail()) || !($tpl = $dept->getTemplate()) || !($msg=$tpl->getNewTaskAlertMsgTemplate()) ) { return; } // Check if Dept recipients is Admin Only $adminOnly = ($dept->isGroupMembershipEnabled() == Dept::ALERTS_ADMIN_ONLY); // Alert recipients $recipients = array(); // Department Manager if ($cfg->alertDeptManagerONNewTask() && $dept->getManagerId() && !$adminOnly) $recipients[] = $dept->getManager(); // Department Members if ($cfg->alertDeptMembersONNewTask() && !$adminOnly) foreach ($dept->getMembersForAlerts() as $M) $recipients[] = $M; $options = array(); $staffId = $thisstaff ? $thisstaff->getId() : 0; $msg = $this->replaceVars($msg->asArray(), $vars); $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 task || !$this->checkStaffPerm($staff) ) { continue; } $alert = $this->replaceVars($msg, array('recipient' => $staff)); $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); $sentlist[$staff->getEmail()] = 1; } // Alert Admin ONLY if not already a staff if ($cfg->alertAdminONNewTask() && !in_array($cfg->getAdminEmail(), $sentlist)) { $alert = $this->replaceVars($msg, array('recipient' => __('Admin'))); $email->sendAlert($cfg->getAdminEmail(), $alert['subj'], $alert['body'], null, $options); } } function delete($comments='') { global $ost, $thisstaff; $thread = $this->getThread(); if (!parent::delete()) return false; $thread->delete(); $this->logEvent('deleted'); Draft::deleteForNamespace('task.%.' . $this->getId()); foreach (DynamicFormEntry::forObject($this->getId(), ObjectModel::OBJECT_TYPE_TASK) as $form) $form->delete(); // Log delete $log = sprintf(__('Task #%1$s deleted by %2$s'), $this->getNumber(), $thisstaff ? $thisstaff->getName() : __('SYSTEM')); if ($comments) $log .= sprintf('<hr>%s', $comments); $ost->logDebug( sprintf( __('Task #%s deleted'), $this->getNumber()), $log); return true; } static function __loadDefaultForm() { require_once INCLUDE_DIR.'class.i18n.php'; $i18n = new Internationalization(); $tpl = $i18n->getTemplate('form.yaml'); foreach ($tpl->getData() as $f) { if ($f['type'] == ObjectModel::OBJECT_TYPE_TASK) { $form = DynamicForm::create($f); $form->save(); break; } } } /* Quick staff's stats */ static function getStaffStats($staff) { global $cfg; /* Unknown or invalid staff */ if (!$staff || (!is_object($staff) && !($staff=Staff::lookup($staff))) || !$staff->isStaff()) return null; $where = array('(task.staff_id='.db_input($staff->getId()) .sprintf(' AND task.flags & %d != 0 ', TaskModel::ISOPEN) .') '); $where2 = ''; if(($teams=$staff->getTeams())) $where[] = ' ( task.team_id IN('.implode(',', db_input(array_filter($teams))) .') AND ' .sprintf('task.flags & %d != 0 ', TaskModel::ISOPEN) .')'; if(!$staff->showAssignedOnly() && ($depts=$staff->getDepts())) //Staff with limited access just see Assigned tasks. $where[] = 'task.dept_id IN('.implode(',', db_input($depts)).') '; $where = implode(' OR ', $where); if ($where) $where = 'AND ( '.$where.' ) '; $sql = 'SELECT \'open\', count(task.id ) AS tasks ' .'FROM ' . TASK_TABLE . ' task ' . sprintf(' WHERE task.flags & %d != 0 ', TaskModel::ISOPEN) . $where . $where2 .'UNION SELECT \'overdue\', count( task.id ) AS tasks ' .'FROM ' . TASK_TABLE . ' task ' . sprintf(' WHERE task.flags & %d != 0 ', TaskModel::ISOPEN) . sprintf(' AND task.flags & %d != 0 ', TaskModel::ISOVERDUE) . $where .'UNION SELECT \'assigned\', count( task.id ) AS tasks ' .'FROM ' . TASK_TABLE . ' task ' . sprintf(' WHERE task.flags & %d != 0 ', TaskModel::ISOPEN) .'AND task.staff_id = ' . db_input($staff->getId()) . ' ' . $where .'UNION SELECT \'closed\', count( task.id ) AS tasks ' .'FROM ' . TASK_TABLE . ' task ' . sprintf(' WHERE task.flags & %d = 0 ', TaskModel::ISOPEN) . $where; $res = db_query($sql); $stats = array(); while ($row = db_fetch_row($res)) $stats[$row[0]] = $row[1]; return $stats; } static function getAgentActions($agent, $options=array()) { if (!$agent) return; require STAFFINC_DIR.'templates/tasks-actions.tmpl.php'; } } class TaskCData extends VerySimpleModel { static $meta = array( 'pk' => array('task_id'), 'table' => TASK_CDATA_TABLE, 'joins' => array( 'task' => array( 'constraint' => array('task_id' => 'TaskModel.task_id'), ), ), ); } class TaskForm extends DynamicForm { static $instance; static $defaultForm; static $internalForm; static $forms; static $cdata = array( 'table' => TASK_CDATA_TABLE, 'object_id' => 'task_id', 'object_type' => ObjectModel::OBJECT_TYPE_TASK, ); static function objects() { $os = parent::objects(); return $os->filter(array('type'=>ObjectModel::OBJECT_TYPE_TASK)); } static function getDefaultForm() { if (!isset(static::$defaultForm)) { if (($o = static::objects()) && $o[0]) static::$defaultForm = $o[0]; } return static::$defaultForm; } static function getInstance($object_id=0, $new=false) { if ($new || !isset(static::$instance)) static::$instance = static::getDefaultForm()->instanciate(); static::$instance->object_type = ObjectModel::OBJECT_TYPE_TASK; if ($object_id) static::$instance->object_id = $object_id; return static::$instance; } static function getInternalForm($source=null, $options=array()) { if (!isset(static::$internalForm)) static::$internalForm = new TaskInternalForm($source, $options); return static::$internalForm; } } class TaskInternalForm extends AbstractForm { static $layout = 'GridFormLayout'; function buildFields() { $fields = array( 'dept_id' => new DepartmentField(array( 'id'=>1, 'label' => __('Department'), 'required' => true, 'layout' => new GridFluidCell(6), )), 'assignee' => new AssigneeField(array( 'id'=>2, 'label' => __('Assignee'), 'required' => false, 'layout' => new GridFluidCell(6), )), 'duedate' => new DatetimeField(array( 'id' => 3, 'label' => __('Due Date'), 'required' => false, 'configuration' => array( 'min' => Misc::gmtime(), 'time' => true, 'gmt' => false, 'future' => true, ), )), ); $mode = @$this->options['mode']; if ($mode && $mode == 'edit') { unset($fields['dept_id']); unset($fields['assignee']); } return $fields; } } // Task thread class class TaskThread extends ObjectThread { function addDescription($vars, &$errors=array()) { $vars['threadId'] = $this->getId(); if (!isset($vars['message']) && $vars['description']) $vars['message'] = $vars['description']; unset($vars['description']); return MessageThreadEntry::add($vars, $errors); } static function create($task=false) { assert($task !== false); $id = is_object($task) ? $task->getId() : $task; $thread = parent::create(array( 'object_id' => $id, 'object_type' => ObjectModel::OBJECT_TYPE_TASK )); if ($thread->save()) return $thread; } } ?>