Helpdesk da PluGzOne, baseado no osTicket
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1826 lines
56 KiB

<?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;
}
}
?>