This addresses an issue where `Task::getLastRespondent()` returns the wrong Agent leading to the wrong people receiving alerts, etc. This updates the `Task::getLastRespondent()` function to make it limit the last respondent search to the Task's own Thread Entries rather than searching through all entries.
1825 lines
56 KiB
PHP
1825 lines
56 KiB
PHP
<?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' => $this->getThread()->entries
|
|
->filter(array(
|
|
'type' => 'R',
|
|
'staff_id__gt' => 0
|
|
))
|
|
->values_flat('staff_id')
|
|
->order_by('-id')
|
|
->limit('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;
|
|
}
|
|
|
|
}
|
|
?>
|