diff --git a/bootstrap.php b/bootstrap.php index 47f617b62aeb63317b897aa2e95ebf91755460f2..a49d8dcd98530af5842123898776108b9411c606 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -94,14 +94,17 @@ class Bootstrap { define('THREAD_ENTRY_TABLE', $prefix.'thread_entry'); define('THREAD_ENTRY_EMAIL_TABLE', $prefix.'thread_entry_email'); - define('TICKET_TABLE',$prefix.'ticket'); define('LOCK_TABLE',$prefix.'lock'); + + define('TICKET_TABLE',$prefix.'ticket'); + define('TICKET_CDATA_TABLE', $prefix.'ticket__cdata'); define('TICKET_EVENT_TABLE',$prefix.'ticket_event'); define('THREAD_COLLABORATOR_TABLE', $prefix.'thread_collaborator'); define('TICKET_STATUS_TABLE', $prefix.'ticket_status'); define('TICKET_PRIORITY_TABLE',$prefix.'ticket_priority'); - define('TASK_TABLE',$prefix.'task'); + define('TASK_TABLE', $prefix.'task'); + define('TASK_CDATA_TABLE', $prefix.'task__cdata'); define('PRIORITY_TABLE',TICKET_PRIORITY_TABLE); diff --git a/include/ajax.content.php b/include/ajax.content.php index 0706757641e7bd82c5603218bafb76110aa92971..c12d56ab00bdd63863b2dee5e96ccac82a0bc592 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -150,7 +150,7 @@ class ContentAjaxAPI extends AjaxController { $langs = Internationalization::getConfiguredSystemLanguages(); $translations = $content->getAllTranslations(); $info = array( - 'title' => $content->getTitle(), + 'title' => $content->getName(), 'body' => $content->getBody(), ); foreach ($translations as $t) { diff --git a/include/ajax.search.php b/include/ajax.search.php index d52d7ca4784db657856df3e351f1fc1967679522..d228018e911f963d9ec8b201aca8d2921e201c70 100644 --- a/include/ajax.search.php +++ b/include/ajax.search.php @@ -74,7 +74,7 @@ class SearchAjaxAPI extends AjaxController { $field->form->getLocal('title'), $field->getLocal('label') )); $fields = SavedSearch::getSearchField($impl, $name); - $form = new Form($fields); + $form = new SimpleForm($fields); // Check the box to search the field by default if ($F = $form->getField("{$name}+search")) $F->value = true; diff --git a/include/ajax.tasks.php b/include/ajax.tasks.php index cb6728a2dbd0eee8245c2050184e4cb1c795cffe..8181b4eaea1646aba8e229a96a6ac8fd9d2755b2 100644 --- a/include/ajax.tasks.php +++ b/include/ajax.tasks.php @@ -22,6 +22,46 @@ require_once(INCLUDE_DIR.'class.task.php'); class TasksAjaxAPI extends AjaxController { + function add() { + global $thisstaff; + + $info=$errors=array(); + if ($_POST) { + Draft::deleteForNamespace('task.add', $thisstaff->getId()); + // Default form + $form = TaskForm::getInstance(); + $form->setSource($_POST); + // Internal form + $iform = TaskForm::getInternalForm($_POST); + $isvalid = true; + if (!$iform->isValid()) + $isvalid = false; + if (!$form->isValid()) + $isvalid = false; + + if ($isvalid) { + $vars = $_POST; + $vars['default_formdata'] = $form->getClean(); + $vars['internal_formdata'] = $iform->getClean(); + $desc = $form->getField('description'); + if ($desc + && $desc->isAttachmentsEnabled() + && ($attachments=$desc->getWidget()->getAttachments())) + $vars['cannedattachments'] = $attachments->getClean(); + $vars['staffId'] = $thisstaff->getId(); + $vars['poster'] = $thisstaff; + $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; + if (($task=Task::create($vars, $errors))) + Http::response(201, $task->getId()); + } + + $info['error'] = __('Error adding task - try again!'); + } + + include STAFFINC_DIR . 'templates/task.tmpl.php'; + } + + function preview($tid) { global $thisstaff; @@ -54,6 +94,220 @@ class TasksAjaxAPI extends AjaxController { include STAFFINC_DIR . 'templates/task-edit.tmpl.php'; } + function massProcess($action) { + global $thisstaff; + + $actions = array( + 'transfer' => array( + 'verbed' => __('transferred'), + ), + 'assign' => array( + 'verbed' => __('assigned'), + ), + 'delete' => array( + 'verbed' => __('deleted'), + ), + 'reopen' => array( + 'verbed' => __('reopen'), + ), + 'close' => array( + 'verbed' => __('closed'), + ), + ); + + if (!isset($actions[$action])) + Http::response(404, __('Unknown action')); + + + $errors = $e = array(); + $inc = null; + $i = $count = 0; + if ($_POST) { + if (!$_POST['tids'] || !($count=count($_POST['tids']))) + $errors['err'] = sprintf( + __('You must select at least %s.'), + __('one task')); + } else { + $count = $_REQUEST['count']; + } + + switch ($action) { + case 'assign': + $inc = 'task-assign.tmpl.php'; + $form = AssignmentForm::instantiate($_POST); + if ($_POST && $form->isValid()) { + foreach ($_POST['tids'] as $tid) { + if (($t=Task::lookup($tid)) + // Make sure the agent is allowed to + // access and assign the task. + && $t->checkStaffPerm($thisstaff, Task::PERM_ASSIGN) + // Do the transfer + && $t->assign($form, $e) + ) + $i++; + } + + if (!$i) { + $info['error'] = sprintf( + __('Unable to %1$s %2$s'), + __('assign'), + _N('selected task', 'selected tasks', $count)); + } + } + break; + case 'transfer': + $inc = 'task-transfer.tmpl.php'; + $form = TransferForm::instantiate($_POST); + if ($_POST && $form->isValid()) { + foreach ($_POST['tids'] as $tid) { + if (($t=Task::lookup($tid)) + // Make sure the agent is allowed to + // access and transfer the task. + && $t->checkStaffPerm($thisstaff, Task::PERM_TRANSFER) + // Do the transfer + && $t->transfer($form, $e) + ) + $i++; + } + + if (!$i) { + $info['error'] = sprintf( + __('Unable to %1$s %2$s'), + __('transfer'), + _N('selected task', 'selected tasks', $count)); + } + } + break; + case 'reopen': + $info['status'] = 'open'; + case 'close': + $inc = 'task-status.tmpl.php'; + $info['status'] = $info['status'] ?: 'closed'; + $perm = ''; + switch ($info['status']) { + case 'open': + // If an agent can create a task then they're allowed to + // reopen closed ones. + $perm = Task::PERM_CREATE; + break; + case 'closed': + $perm = Task::PERM_CLOSE; + break; + default: + $errors['err'] = __('Unknown action'); + } + // Check generic permissions -- department specific permissions + // will be checked below. + if ($perm && !$thisstaff->hasPerm($perm)) + $errors['err'] = sprintf( + __('You do not have permission to %s %s'), + __($action), + __('tasks')); + + if ($_POST && !$errors) { + if (!$_POST['status'] + || !in_array($_POST['status'], array('open', 'closed'))) + $errors['status'] = __('Status selection required'); + else { + foreach ($_POST['tids'] as $tid) { + if (($t=Task::lookup($tid)) + && $t->checkStaffPerm($thisstaff, $perm ?: null) + && $t->setStatus($_POST['status'], $_POST['comments']) + ) + $i++; + } + + if (!$i) { + $info['error'] = sprintf( + __('Unable to change status of %1$s'), + _N('selected task', 'selected tasks', $count)); + } + } + } + break; + case 'delete': + $inc = 'task-delete.tmpl.php'; + $info[':placeholder'] = sprintf(__( + 'Optional reason for deleting %s'), + _N('selected task', 'selected tasks', $count)); + $info['warn'] = sprintf(__( + 'Are you sure you want to DELETE %s?'), + _N('selected task', 'selected tasks', $count)); + $info[':extra'] = sprintf('<strong>%s</strong>', + __('Deleted tasks CANNOT be recovered, including any associated attachments.') + ); + + if ($_POST && !$errors) { + foreach ($_POST['tids'] as $tid) { + if (($t=Task::lookup($tid)) + && $t->getDeptId() != $_POST['dept_id'] + && $t->checkStaffPerm($thisstaff, Task::PERM_DELETE) + && $t->delete($_POST, $e) + ) + $i++; + } + + if (!$i) { + $info['error'] = sprintf( + __('Unable to %1$s %2$s'), + __('delete'), + _N('selected task', 'selected tasks', $count)); + } + } + break; + default: + Http::response(404, __('Unknown action')); + } + + + if ($_POST && $i) { + + // Assume success + if ($i==$count) { + $msg = sprintf(__('Successfully %s %s.'), + $actions[$action]['verbed'], + sprintf(__('%1$d %2$s'), + $count, + _N('selected task', 'selected tasks', $count)) + ); + $_SESSION['::sysmsgs']['msg'] = $msg; + } else { + $warn = sprintf( + __('%1$d of %2$d %3$s %4$s'), $i, $count, + _N('selected task', 'selected tasks', + $count), + $actions[$action]['verbed']); + $_SESSION['::sysmsgs']['warn'] = $warn; + } + Http::response(201, 'processed'); + } elseif($_POST && !isset($info['error'])) { + $info['error'] = $errors['err'] ?: sprintf( + __('Unable to %1$s %2$s'), + __('process'), + _N('selected task', 'selected tasks', $count)); + } + + if ($_POST) + $info = array_merge($info, Format::htmlchars($_POST)); + + + include STAFFINC_DIR . "templates/$inc"; + // Copy checked tasks to the form. + echo " + <script type=\"text/javascript\"> + $(function() { + $('form#tasks input[name=\"tids[]\"]:checkbox:checked') + .each(function() { + $('<input>') + .prop('type', 'hidden') + .attr('name', 'tids[]') + .val($(this).val()) + .appendTo('form.mass-action'); + }); + }); + </script>"; + } + function transfer($tid) { global $thisstaff; @@ -63,37 +317,76 @@ class TasksAjaxAPI extends AjaxController { if (!$task->checkStaffPerm($thisstaff, Task::PERM_TRANSFER)) Http::response(403, __('Permission Denied')); - $info = $errors = array(); - if ($_POST) { - if ($task->transfer($_POST, $errors)) { + $errors = array(); + + $info = array( + ':title' => sprintf(__('Task #%s: %s'), + $task->getNumber(), + __('Tranfer')), + ':action' => sprintf('#tasks/%d/transfer', + $task->getId()) + ); + + $form = $task->getTransferForm($_POST); + if ($_POST && $form->isValid()) { + if ($task->transfer($form, $errors)) { + $_SESSION['::sysmsgs']['msg'] = sprintf( + __('%s successfully'), + sprintf( + __('%s transferred to %s department'), + __('Task'), + $task->getDept() + ) + ); Http::response(201, $task->getId()); - } - $info = Format::htmlchars($_POST); + $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to transfer task'); } + $info['dept_id'] = $info['dept_id'] ?: $task->getDeptId(); + include STAFFINC_DIR . 'templates/task-transfer.tmpl.php'; } function assign($tid) { global $thisstaff; - if(!($task=Task::lookup($tid))) + if (!($task=Task::lookup($tid))) Http::response(404, __('No such task')); if (!$task->checkStaffPerm($thisstaff, Task::PERM_ASSIGN)) Http::response(403, __('Permission Denied')); - $info = $errors = array(); - if ($_POST) { - if ($task->assign($_POST, $errors)) { - Http::response(201, $task->getId()); + $errors = array(); + $info = array( + ':title' => sprintf(__('Task #%s: %s'), + $task->getNumber(), + $task->isAssigned() ? __('Reassign') : __('Assign')), + ':action' => sprintf('#tasks/%d/assign', + $task->getId()), + ); + if ($task->isAssigned()) { + $info['notice'] = sprintf(__('%s is currently assigned to %s'), + __('Task'), + $task->getAssigned()); + } + $form = $task->getAssignmentForm($_POST); + if ($_POST && $form->isValid()) { + if ($task->assign($form, $errors)) { + $_SESSION['::sysmsgs']['msg'] = sprintf( + __('%s successfully'), + sprintf( + __('%s assigned to %s'), + __('Task'), + $form->getAssignee()) + ); + Http::response(201, $task->getId()); } - $info = Format::htmlchars($_POST); + $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to assign task'); } @@ -109,23 +402,34 @@ class TasksAjaxAPI extends AjaxController { if (!$task->checkStaffPerm($thisstaff, Task::PERM_DELETE)) Http::response(403, __('Permission Denied')); - $info = $errors = array(); + $errors = array(); + $info = array( + ':title' => sprintf(__('Task #%s: %s'), + $task->getNumber(), + __('Delete')), + ':action' => sprintf('#tasks/%d/delete', + $task->getId()), + ); + if ($_POST) { if ($task->delete($_POST, $errors)) { + $_SESSION['::sysmsgs']['msg'] = sprintf( + __('%s #%s deleted successfully'), + __('Task'), + $task->getNumber(), + $task->getDept()); Http::response(201, 0); - } - - $info = Format::htmlchars($_POST); + $info = array_merge($info, Format::htmlchars($_POST)); $info['error'] = $errors['err'] ?: __('Unable to delete task'); } - $info['placeholder'] = sprintf(__( + $info[':placeholder'] = sprintf(__( 'Optional reason for deleting %s'), __('this task')); $info['warn'] = sprintf(__( 'Are you sure you want to DELETE %s?'), __('this task')); - $info['extra'] = sprintf('<strong>%s</strong>', + $info[':extra'] = sprintf('<strong>%s</strong>', __('Deleted tasks CANNOT be recovered, including any associated attachments.') ); @@ -141,7 +445,7 @@ class TasksAjaxAPI extends AjaxController { Http::response(404, __('No such task')); $info=$errors=array(); - $task_note_form = new Form(array( + $note_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:note', 'configuration' => array('extensions'=>''))) @@ -152,14 +456,14 @@ class TasksAjaxAPI extends AjaxController { switch ($_POST['a']) { case 'postnote': $vars = $_POST; - $attachments = $task_note_form->getField('attachments')->getClean(); + $attachments = $note_form->getField('attachments')->getClean(); $vars['cannedattachments'] = array_merge( $vars['cannedattachments'] ?: array(), $attachments); if(($note=$task->postNote($vars, $errors, $thisstaff))) { $msg=__('Note posted successfully'); // Clear attachment list - $task_note_form->setSource(array()); - $task_note_form->getField('attachments')->reset(); + $note_form->setSource(array()); + $note_form->getField('attachments')->reset(); Draft::deleteForNamespace('task.note.'.$task->getId(), $thisstaff->getId()); } else { diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 28ed7d2b4fbfcd6de7af37038c6321d02dc3e628..d1ea440c0495351edb894f76acccb13599d670be 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -924,7 +924,8 @@ class TicketsAjaxAPI extends AjaxController { sprintf('ticket.%d.task', $ticket->getId()), $thisstaff->getId()); // Default form - $form = TaskForm::getDefaultForm()->getForm($_POST); + $form = TaskForm::getInstance(); + $form->setSource($_POST); // Internal form $iform = TaskForm::getInternalForm($_POST); $isvalid = true; @@ -963,5 +964,51 @@ class TicketsAjaxAPI extends AjaxController { include STAFFINC_DIR . 'templates/task.tmpl.php'; } + + function task($tid, $id) { + global $thisstaff; + + if (!($ticket=Ticket::lookup($tid)) + || !$ticket->checkStaffPerm($thisstaff)) + Http::response(404, 'Unknown ticket'); + + // Lookup task and check access + if (!($task=Task::lookup($id)) + || !$task->checkStaffPerm($thisstaff)) + Http::response(404, 'Unknown task'); + + $info=$errors=array(); + $note_form = new SimpleForm(array( + 'attachments' => new FileUploadField(array('id'=>'attach', + 'name'=>'attach:note', + 'configuration' => array('extensions'=>''))) + )); + + if ($_POST) { + switch ($_POST['a']) { + case 'postnote': + $vars = $_POST; + $attachments = $note_form->getField('attachments')->getClean(); + $vars['cannedattachments'] = array_merge( + $vars['cannedattachments'] ?: array(), $attachments); + if(($note=$task->postNote($vars, $errors, $thisstaff))) { + $msg=__('Note posted successfully'); + // Clear attachment list + $note_form->setSource(array()); + $note_form->getField('attachments')->reset(); + Draft::deleteForNamespace('task.note.'.$task->getId(), + $thisstaff->getId()); + } else { + if(!$errors['err']) + $errors['err'] = __('Unable to post the note - missing or invalid data.'); + } + break; + default: + $errors['err'] = __('Unknown action'); + } + } + + include STAFFINC_DIR . 'templates/task-view.tmpl.php'; + } } ?> diff --git a/include/class.config.php b/include/class.config.php index a2cad713eb487ab4542f4186fb19cc4c5f308fe1..a85fc696263e4316da1f0181e6ccf6a4ea6921ce 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -847,6 +847,88 @@ class OsticketConfig extends Config { return ($this->get('overlimit_notice_active')); } + /* Tasks */ + + function alertONNewTask() { + return ($this->get('task_alert_active')); + } + + function alertAdminONNewTask() { + return ($this->get('task_alert_admin')); + } + + function alertDeptManagerONNewTask() { + return ($this->get('task_alert_dept_manager')); + } + + function alertDeptMembersONNewTask() { + return ($this->get('task_alert_dept_members')); + } + + function alertONTaskActivity() { + return ($this->get('task_activity_alert_active')); + } + + function alertLastRespondentONTaskActivity() { + return ($this->get('task_activity_alert_laststaff')); + } + + function alertAssignedONTaskActivity() { + return ($this->get('task_activity_alert_assigned')); + } + + function alertDeptManagerONTaskActivity() { + return ($this->get('task_activity_alert_dept_manager')); + } + + function alertONTaskTransfer() { + return ($this->get('task_transfer_alert_active')); + } + + function alertAssignedONTaskTransfer() { + return ($this->get('task_transfer_alert_assigned')); + } + + function alertDeptManagerONTaskTransfer() { + return ($this->get('task_transfer_alert_dept_manager')); + } + + function alertDeptMembersONTaskTransfer() { + return ($this->get('task_transfer_alert_dept_members')); + } + + function alertONTaskAssignment() { + return ($this->get('task_assignment_alert_active')); + } + + function alertStaffONTaskAssignment() { + return ($this->get('task_assignment_alert_staff')); + } + + function alertTeamLeadONTaskAssignment() { + return ($this->get('task_assignment_alert_team_lead')); + } + + function alertTeamMembersONTaskAssignment() { + return ($this->get('task_assignment_alert_team_members')); + } + + function alertONOverdueTask() { + return ($this->get('task_overdue_alert_active')); + } + + function alertAssignedONOverdueTask() { + return ($this->get('task_overdue_alert_assigned')); + } + + function alertDeptManagerONOverdueTask() { + return ($this->get('task_overdue_alert_dept_manager')); + } + + function alertDeptMembersONOverdueTask() { + return ($this->get('task_overdue_alert_dept_members')); + } + /* Error alerts sent to admin email when enabled */ function alertONSQLError() { return ($this->get('send_sql_errors')); @@ -905,6 +987,9 @@ class OsticketConfig extends Config { case 'tickets': return $this->updateTicketsSettings($vars, $errors); break; + case 'tasks': + return $this->updateTasksSettings($vars, $errors); + break; case 'emails': return $this->updateEmailsSettings($vars, $errors); break; @@ -1056,6 +1141,82 @@ class OsticketConfig extends Config { )); } + function updateTasksSettings($vars, &$errors) { + $f=array(); + $f['default_task_sla_id']=array('type'=>'int', 'required'=>1, 'error'=>__('Selection required')); + $f['default_task_priority_id']=array('type'=>'int', 'required'=>1, 'error'=>__('Selection required')); + + if (!preg_match('`(?!<\\\)#`', $vars['task_number_format'])) + $errors['task_number_format'] = 'Task number format requires at least one hash character (#)'; + + Validator::process($f, $vars, $errors); + + if ($vars['task_alert_active'] + && (!isset($vars['task_alert_admin']) + && !isset($vars['task_alert_dept_manager']) + && !isset($vars['task_alert_dept_members']) + && !isset($vars['task_alert_acct_manager']))) { + $errors['task_alert_active'] = __('Select recipient(s)'); + } + + if ($vars['task_activity_alert_active'] + && (!isset($vars['task_activity_alert_laststaff']) + && !isset($vars['task_activity_alert_assigned']) + && !isset($vars['task_activity_alert_dept_manager']))) { + $errors['task_activity_alert_active'] = __('Select recipient(s)'); + } + + if ($vars['task_transfer_alert_active'] + && (!isset($vars['task_transfer_alert_assigned']) + && !isset($vars['task_transfer_alert_dept_manager']) + && !isset($vars['task_transfer_alert_dept_members']))) { + $errors['task_transfer_alert_active'] = __('Select recipient(s)'); + } + + if ($vars['task_overdue_alert_active'] + && (!isset($vars['task_overdue_alert_assigned']) + && !isset($vars['task_overdue_alert_dept_manager']) + && !isset($vars['task_overdue_alert_dept_members']))) { + $errors['task_overdue_alert_active'] = __('Select recipient(s)'); + } + + if ($vars['task_assignment_alert_active'] + && (!isset($vars['task_assignment_alert_staff']) + && !isset($vars['task_assignment_alert_team_lead']) + && !isset($vars['task_assignment_alert_team_members']))) { + $errors['task_assignment_alert_active'] = __('Select recipient(s)'); + } + + if ($errors) + return false; + + return $this->updateAll(array( + 'task_number_format'=>$vars['task_number_format'] ?: '######', + 'task_sequence_id'=>$vars['task_sequence_id'] ?: 0, + 'default_task_priority_id'=>$vars['default_task_priority_id'], + 'default_task_sla_id'=>$vars['default_task_sla_id'], + 'task_alert_active'=>$vars['task_alert_active'], + 'task_alert_admin'=>isset($vars['task_alert_admin']) ? 1 : 0, + 'task_alert_dept_manager'=>isset($vars['task_alert_dept_manager']) ? 1 : 0, + 'task_alert_dept_members'=>isset($vars['task_alert_dept_members']) ? 1 : 0, + 'task_activity_alert_active'=>$vars['task_activity_alert_active'], + 'task_activity_alert_laststaff'=>isset($vars['task_activity_alert_laststaff']) ? 1 : 0, + 'task_activity_alert_assigned'=>isset($vars['task_activity_alert_assigned']) ? 1 : 0, + 'task_activity_alert_dept_manager'=>isset($vars['task_activity_alert_dept_manager']) ? 1 : 0, + 'task_assignment_alert_active'=>$vars['task_assignment_alert_active'], + 'task_assignment_alert_staff'=>isset($vars['task_assignment_alert_staff']) ? 1 : 0, + 'task_assignment_alert_team_lead'=>isset($vars['task_assignment_alert_team_lead']) ? 1 : 0, + 'task_assignment_alert_team_members'=>isset($vars['task_assignment_alert_team_members']) ? 1 : 0, + 'task_transfer_alert_active'=>$vars['task_transfer_alert_active'], + 'task_transfer_alert_assigned'=>isset($vars['task_transfer_alert_assigned']) ? 1 : 0, + 'task_transfer_alert_dept_manager'=>isset($vars['task_transfer_alert_dept_manager']) ? 1 : 0, + 'task_transfer_alert_dept_members'=>isset($vars['task_transfer_alert_dept_members']) ? 1 : 0, + 'task_overdue_alert_active'=>$vars['task_overdue_alert_active'], + 'task_overdue_alert_assigned'=>isset($vars['task_overdue_alert_assigned']) ? 1 : 0, + 'task_overdue_alert_dept_manager'=>isset($vars['task_overdue_alert_dept_manager']) ? 1 : 0, + 'task_overdue_alert_dept_members'=>isset($vars['task_overdue_alert_dept_members']) ? 1 : 0, + )); + } function updateEmailsSettings($vars, &$errors) { $f=array(); diff --git a/include/class.dept.php b/include/class.dept.php index 43a98692d1af69cf0c67e2dd3d90dac30d0741ae..8343d6f6690c9917a8fbe8f4e145d7a5f08e03bd 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -175,6 +175,7 @@ implements TemplateVariable { 'onvacation' => 0, )); + $members->distinct('staff_id'); switch ($cfg->getDefaultNameFormat()) { case 'last': case 'lastfirst': diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index e569f83792ae45880ee428c63b961e40d63cedf8..c2c90a3a78510b85886801992bcaf6f43af2321d 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -57,6 +57,10 @@ class DynamicForm extends VerySimpleModel { return $base; } + function getId() { + return $this->id; + } + /** * Fetch a list of field implementations for the fields defined in this * form. This method should *always* be preferred over @@ -112,7 +116,7 @@ class DynamicForm extends VerySimpleModel { if ($source) $this->reset(); $fields = $this->getFields(); - $form = new Form($fields, $source, array( + $form = new SimpleForm($fields, $source, array( 'title' => $this->getLocal('title'), 'instructions' => $this->getLocal('instructions')) ); @@ -254,6 +258,87 @@ class DynamicForm extends VerySimpleModel { return true; } + static function ensureDynamicDataView() { + + if (!($cdata=static::$cdata) || !$cdata['table']) + return false; + + $sql = 'SHOW TABLES LIKE \''.$cdata['table'].'\''; + if (!db_num_rows(db_query($sql))) + return static::buildDynamicDataView($cdata); + } + + static function buildDynamicDataView($cdata) { + $sql = 'CREATE TABLE `'.$cdata['table'].'` (PRIMARY KEY + ('.$cdata['object_id'].')) AS ' + . static::getCrossTabQuery( $cdata['object_type'], $cdata['object_id']); + db_query($sql); + } + + static function dropDynamicDataView($table) { + db_query('DROP TABLE IF EXISTS `'.$table.'`'); + } + + static function updateDynamicDataView($answer, $data) { + // TODO: Detect $data['dirty'] for value and value_id + // We're chiefly concerned with Ticket form answers + + $cdata = static::$cdata; + if (!$cdata + || !$cdata['table'] + || !($e = $answer->getEntry()) + || $e->form->get('type') != $cdata['object_type']) + return; + + // $record = array(); + // $record[$f] = $answer->value' + // TicketFormData::objects()->filter(array('ticket_id'=>$a)) + // ->merge($record); + $sql = 'SHOW TABLES LIKE \''.$cdata['table'].'\''; + if (!db_num_rows(db_query($sql))) + return; + + $f = $answer->getField(); + $name = $f->get('name') ? $f->get('name') + : 'field_'.$f->get('id'); + $fields = sprintf('`%s`=', $name) . db_input( + implode(',', $answer->getSearchKeys())); + $sql = 'INSERT INTO `'.$cdata['table'].'` SET '.$fields + . sprintf(', `%s`= %s', + $cdata['object_id'], + db_input($answer->getEntry()->get('object_id'))) + .' ON DUPLICATE KEY UPDATE '.$fields; + if (!db_query($sql) || !db_affected_rows()) + return self::dropDynamicDataView($cdata['table']); + } + + static function updateDynamicFormEntryAnswer($answer, $data) { + if (!$answer + || !($e = $answer->getEntry()) + || !$e->form) + return; + + switch ($e->form->get('type')) { + case 'T': + return TicketForm::updateDynamicDataView($answer, $data); + case 'A': + return TaskForm::updateDynamicDataView($answer, $data); + } + + } + + static function updateDynamicFormField($field, $data) { + if (!$field || !$field->form) + return; + + switch ($field->form->get('type')) { + case 'T': + return TicketForm::dropDynamicDataView(TicketForm::$cdata['table']); + case 'A': + return TaskForm::dropDynamicDataView(TaskForm::$cdata['table']); + } + + } static function getCrossTabQuery($object_type, $object_id='object_id', $exclude=array()) { $fields = static::getDynamicDataViewFields($exclude); @@ -264,8 +349,7 @@ class DynamicForm extends VerySimpleModel { WHERE entry.object_type='$object_type' GROUP BY entry.object_id"; } - // Materialized View for Ticket custom data (MySQL FlexViews would be - // nice) + // Materialized View for custom data (MySQL FlexViews would be nice) // // @see http://code.google.com/p/flexviews/ static function getDynamicDataViewFields($exclude) { @@ -347,6 +431,12 @@ Filter::addSupportedMatches(/* @trans */ 'User Data', function() { class TicketForm extends DynamicForm { static $instance; + static $cdata = array( + 'table' => TICKET_CDATA_TABLE, + 'object_id' => 'ticket_id', + 'object_type' => 'T', + ); + static function objects() { $os = parent::objects(); return $os->filter(array('type'=>'T')); @@ -435,27 +525,24 @@ Filter::addSupportedMatches(/* @trans */ 'Ticket Data', function() { }, 30); // Manage materialized view on custom data updates Signal::connect('model.created', - array('TicketForm', 'updateDynamicDataView'), + array('DynamicForm', 'updateDynamicFormEntryAnswer'), 'DynamicFormEntryAnswer'); Signal::connect('model.updated', - array('TicketForm', 'updateDynamicDataView'), + array('DynamicForm', 'updateDynamicFormEntryAnswer'), 'DynamicFormEntryAnswer'); // Recreate the dynamic view after new or removed fields to the ticket // details form Signal::connect('model.created', - array('TicketForm', 'dropDynamicDataView'), - 'DynamicFormField', - function($o) { return $o->form->type == 'T'; }); + array('DynamicForm', 'updateDynamicFormField'), + 'DynamicFormField'); Signal::connect('model.deleted', - array('TicketForm', 'dropDynamicDataView'), - 'DynamicFormField', - function($o) { return $o->form->type == 'T'; }); + array('DynamicForm', 'updateDynamicFormField'), + 'DynamicFormField'); // If the `name` column is in the dirty list, we would be renaming a // column. Delete the view instead. Signal::connect('model.updated', - array('TicketForm', 'dropDynamicDataView'), + array('DynamicForm', 'updateDynamicFormField'), 'DynamicFormField', - // TODO: Lookup the dynamic form to verify {type == 'T'} function($o, $d) { return isset($d['dirty']) && (isset($d['dirty']['name']) || isset($d['dirty']['type'])); }); @@ -898,7 +985,7 @@ class DynamicFormEntry extends VerySimpleModel { function getForm() { if (!isset($this->_form)) { // XXX: Should source be $this? - $form = new Form($this->getFields(), $this->getSource(), + $form = new SimpleForm($this->getFields(), $this->getSource(), array( 'title' => $this->getTitle(), 'instructions' => $this->getInstructions(), diff --git a/include/class.filter_action.php b/include/class.filter_action.php index 7de00efe9e82d79e974d3bd2411c60161920baf6..fc3f356f7495342c633429e1bdb2cfa151bfe3b9 100644 --- a/include/class.filter_action.php +++ b/include/class.filter_action.php @@ -138,7 +138,7 @@ abstract class TriggerAction { foreach ($options as $f) { $f->set('id', $uid++); } - $this->_cform = new Form($options, $source); + $this->_cform = new SimpleForm($options, $source); if (!$source) { foreach ($this->_cform->getFields() as $name=>$f) { if ($config && isset($config[$name])) diff --git a/include/class.forms.php b/include/class.forms.php index 59b2d7251b23155e2c9dad61465a88c006415e44..75acdbd92a89fca54b6c751fad420b7384f0d32f 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -19,6 +19,9 @@ * data for a ticket */ class Form { + + static $id = 0; + var $fields = array(); var $title = ''; var $instructions = ''; @@ -28,26 +31,42 @@ class Form { var $_errors = null; var $_source = false; - function __construct($fields=array(), $source=null, $options=array()) { - $this->fields = $fields; - foreach ($fields as $k=>$f) { - $f->setForm($this); - if (!$f->get('name') && $k && !is_numeric($k)) - $f->set('name', $k); - } + function __construct($source=null, $options=array()) { + if (isset($options['title'])) $this->title = $options['title']; if (isset($options['instructions'])) $this->instructions = $options['instructions']; + if (isset($options['id'])) + $this->id = $options['id']; + // Use POST data if source was not specified $this->_source = ($source) ? $source : $_POST; } + + function getId() { + return static::$id; + } + function data($source) { foreach ($this->fields as $name=>$f) if (isset($source[$name])) $f->value = $source[$name]; } + function setFields($fields) { + + if (!is_array($fields)) + return; + + $this->fields = $fields; + foreach ($fields as $k=>$f) { + $f->setForm($this); + if (!$f->get('name') && $k && !is_numeric($k)) + $f->set('name', $k); + } + } + function getFields() { return $this->fields; } @@ -120,8 +139,21 @@ class Form { return ($formOnly) ? $this->_errors['form'] : $this->_errors; } - function addError($message) { - $this->_errors['form'][] = $message; + function addError($message, $index=false) { + + if ($index) + $this->_errors[$index] = $message; + else + $this->_errors['form'][] = $message; + } + + function addErrors($errors=array()) { + foreach ($errors as $k => $v) { + if (($f=$this->getField($k))) + $f->addError($v); + else + $this->addError($v, $k); + } } function addValidator($function) { @@ -163,6 +195,42 @@ class Form { } } + function emitJavascript($options=array()) { + + // Check if we need to emit javascript + if (!($fid=$this->getId())) + return; + ?> + <script type="text/javascript"> + $(function() { + <?php + //XXX: We ONLY want to watch field on this form. We'll only + // watch form inputs if form_id is specified. Current FORM API + // doesn't generate the entire form (just fields) + if ($fid) { + ?> + $(document).off('change.<?php echo $fid; ?>'); + $(document).on('change.<?php echo $fid; ?>', + 'form#<?php echo $fid; ?> :input', + function() { + //Clear any current errors... + var errors = $('#field'+$(this).attr('id')+'_error'); + if (errors.length) + errors.slideUp('fast', function (){ + $(this).remove(); + }); + //TODO: Validation input inplace or via ajax call + // and set any new errors AND visibilty changes + } + ); + <?php + } + ?> + }); + </script> + <?php + } + static function emitMedia($url, $type) { if ($url[0] == '/') $url = ROOT_PATH . substr($url, 1); @@ -221,6 +289,28 @@ class Form { } } } + + /* + * Initialize a generic static form + */ + static function instantiate() { + $r = new ReflectionClass(get_called_class()); + return $r->newInstanceArgs(func_get_args()); + } +} + +/** + * SimpleForm + * Wrapper for inline/static forms. + * + */ +class SimpleForm extends Form { + + function __construct($fields=array(), $source=null, $options=array()) { + parent::__construct($source, $options); + $this->setFields($fields); + } + } require_once(INCLUDE_DIR . "class.json.php"); @@ -340,14 +430,22 @@ class FormField { $this->_clean = $this->_widget = null; } + function getValue() { + return $this->getWidget()->getValue(); + } + function errors() { return $this->_errors; } - function addError($message, $field=false) { + function addError($message, $index=false) { if ($field) - $this->_errors[$field] = $message; + $this->_errors[$index] = $message; else $this->_errors[] = $message; + + // Update parent form errors for the field + if ($this->_form) + $this->_form->addError($this->errors(), $this->get('id')); } function isValidEntry() { @@ -818,7 +916,7 @@ class FormField { $clazz = $type[1]; $T = new $clazz($this->ht); $config = $this->getConfiguration(); - $this->_cform = new Form($T->getConfigurationOptions(), $source); + $this->_cform = new SimpleForm($T->getConfigurationOptions(), $source); if (!$source) { foreach ($this->_cform->getFields() as $name=>$f) { if ($config && isset($config[$name])) @@ -1008,7 +1106,7 @@ class TextareaField extends FormField { } function searchable($value) { - $value = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $value); + $value = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $value); //<?php $value = Format::htmldecode(Format::striptags($value)); return Format::searchable($value); } @@ -1138,6 +1236,7 @@ class BooleanField extends FormField { class ChoiceField extends FormField { static $widget = 'ChoicesWidget'; + var $_choices; function getConfigurationOptions() { return array( @@ -1213,6 +1312,18 @@ class ChoiceField extends FormField { return (string) $value; } + /* + Return criteria to which the choice should be filtered by + */ + function getCriteria() { + $config = $this->getConfiguration(); + $criteria = array(); + if (isset($config['criteria'])) + $criteria = $config['criteria']; + + return $criteria; + } + function getChoice($value) { $choices = $this->getChoices(); @@ -1693,6 +1804,9 @@ FormField::addFieldTypes(/*@trans*/ 'Dynamic Fields', function() { class AssigneeField extends ChoiceField { + var $_choices = array(); + var $_criteria = null; + function getWidget() { $widget = parent::getWidget(); if (is_object($widget->value)) @@ -1700,20 +1814,62 @@ class AssigneeField extends ChoiceField { return $widget; } + function getCriteria() { + + if (!isset($this->_criteria)) { + $this->_criteria = array('available' => true); + if (($c=parent::getCriteria())) + $this->_criteria = array_merge($this->_criteria, $c); + } + + return $this->_criteria; + } + function hasIdValue() { return true; } function getChoices() { global $cfg; - $choices = array(); - if (($agents = Staff::getAvailableStaffMembers())) + + if (!$this->_choices) { + $config = $this->getConfiguration(); + $choices = array( + __('Agents') => new ArrayObject(), + __('Teams') => new ArrayObject()); + $A = current($choices); + $criteria = $this->getCriteria(); + $agents = array(); + if (($dept=$config['dept']) && $dept->assignMembersOnly()) { + if (($members = $dept->getMembers($criteria))) + foreach ($members as $member) + $agents[$member->getId()] = $member; + } else { + $agents = Staff::getStaffMembers($criteria); + } + foreach ($agents as $id => $name) - $choices[$id] = $name; + $A['s'.$id] = $name; - return $choices; + next($choices); + $T = current($choices); + if (($teams = Team::getTeams())) + foreach ($teams as $id => $name) + $T['t'.$id] = $name; + + $this->_choices = $choices; + } + + return $this->_choices; + } + + function getValue() { + + if (($value = parent::getValue()) && ($id=$this->getClean())) + return $value[$id]; } + function parse($id) { return $this->to_php(null, $id); } @@ -2291,7 +2447,7 @@ class InlineFormField extends FormField { function getInlineForm($data=false) { $form = $this->get('form'); if (is_array($form)) { - $form = new Form($form, $data ?: $this->value ?: $this->getSource()); + $form = new SimpleForm($form, $data ?: $this->value ?: $this->getSource()); } return $form; } @@ -2573,14 +2729,9 @@ class ChoicesWidget extends Widget { <?php if (!$have_def && !$config['multiselect']) { ?> <option value="<?php echo $def_key; ?>">— <?php echo $def_val; ?> —</option> - <?php } - foreach ($choices as $key => $name) { - if (!$have_def && $key == $def_key) - continue; ?> - <option value="<?php echo $key; ?>" <?php - if (isset($values[$key])) echo 'selected="selected"'; - ?>><?php echo $name; ?></option> - <?php } ?> +<?php + } + $this->emitChoices($choices, $values); ?> </select> <?php if ($config['multiselect']) { @@ -2595,6 +2746,35 @@ class ChoicesWidget extends Widget { } } + function emitChoices($choices, $values=array()) { + reset($choices); + if (is_array(current($choices)) || current($choices) instanceof Traversable) + return $this->emitComplexChoices($choices, $values); + + foreach ($choices as $key => $name) { + if (!$have_def && $key == $def_key) + continue; ?> + <option value="<?php echo $key; ?>" <?php + if (isset($values[$key])) echo 'selected="selected"'; + ?>><?php echo $name; ?></option> + <?php + } + } + + function emitComplexChoices($choices, $values=array()) { + foreach ($choices as $label => $group) { ?> + <optgroup label="<?php echo $label; ?>"><?php + foreach ($group as $key => $name) { + if (!$have_def && $key == $def_key) + continue; ?> + <option value="<?php echo $key; ?>" <?php + if (isset($values[$key])) echo 'selected="selected"'; + ?>><?php echo $name; ?></option> +<?php } ?> + </optgroup><?php + } + } + function getValue() { if (!($value = parent::getValue())) @@ -2606,12 +2786,23 @@ class ChoicesWidget extends Widget { // Assume multiselect $values = array(); $choices = $this->field->getChoices(); - if (is_array($value)) { - foreach($value as $k => $v) { - if (isset($choices[$v])) - $values[$v] = $choices[$v]; - elseif (($i=$this->field->lookupChoice($v))) - $values += $i; + + if ($choices && is_array($value)) { + // Complex choices + if (is_array(current($choices)) + || current($choices) instanceof Traversable) { + foreach ($choices as $label => $group) { + foreach ($group as $k => $v) + if (in_array($k, $value)) + $values[$k] = $v; + } + } else { + foreach($value as $k => $v) { + if (isset($choices[$v])) + $values[$v] = $choices[$v]; + elseif (($i=$this->field->lookupChoice($v))) + $values += $i; + } } } @@ -3056,4 +3247,203 @@ class VisibilityConstraint { } } +class AssignmentForm extends Form { + + static $id = 'assign'; + var $_assignee = null; + var $_dept = null; + + function __construct($source=null, $options=array()) { + parent::__construct($source, $options); + // Department of the object -- if necessary to limit assinees + if (isset($options['dept'])) + $this->_dept = $options['dept']; + } + + function getFields() { + + if ($this->fields) + return $this->fields; + + $fields = array( + 'assignee' => new AssigneeField(array( + 'id'=>1, + 'label' => __('Assignee'), + 'flags' => hexdec(0X450F3), + 'required' => true, + 'validator-error' => __('Assignee selection required'), + 'configuration' => array( + 'criteria' => array( + 'available' => true, + ), + 'dept' => $this->_dept ?: null, + ), + ) + ), + 'comments' => new TextareaField(array( + 'id' => 2, + 'label'=> '', + 'required'=>false, + 'default'=>'', + 'configuration' => array( + 'html' => true, + 'size' => 'large', + 'placeholder' => __('Optional reason for the assignment'), + ), + ) + ), + ); + + $this->setFields($fields); + + return $this->fields; + } + + function isValid() { + + if (!parent::isValid()) + return false; + + // Do additional assignment validation + if (!($assignee = $this->getAssignee())) { + $this->getField('assignee')->addError( + __('Unknown assignee')); + } elseif ($assignee instanceof Staff) { + // Make sure the agent is available + if (!$assignee->isAvailable()) + $this->getField('assignee')->addError( + __('Agent is unavailable for assignment') + ); + } + + return !$this->errors(); + } + + function getClean() { + return parent::getClean(); + } + + function render($options) { + + switch(strtolower($options['template'])) { + case 'simple': + $inc = STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php'; + break; + default: + throw new Exception(sprintf(__('%s: Unknown template style %s'), + 'FormUtils', $options['template'])); + } + + $form = $this; + include $inc; + } + + function getAssignee() { + + if (!isset($this->_assignee)) { + $value = $this->getField('assignee')->getClean(); + if ($value[0] == 's') + $this->_assignee = Staff::lookup(substr($value, 1)); + elseif ($value[0] == 't') + $this->_assignee = Team::lookup(substr($value, 1)); + } + + return $this->_assignee; + } + + function assigneeCriteria() { + $dept = $this->id; + return function () use($dept) { + return array('dept_id' =>$dept); + }; + } +} + +class TransferForm extends Form { + + static $id = 'transfer'; + var $_dept = null; + + function __construct($source=null, $options=array()) { + parent::__construct($source, $options); + } + + function getFields() { + + if ($this->fields) + return $this->fields; + + $fields = array( + 'dept' => new DepartmentField(array( + 'id'=>1, + 'label' => __('Department'), + 'flags' => hexdec(0X450F3), + 'required' => true, + 'validator-error' => __('Department selection required'), + ) + ), + 'comments' => new TextareaField(array( + 'id' => 2, + 'label'=> '', + 'required'=>false, + 'default'=>'', + 'configuration' => array( + 'html' => true, + 'size' => 'small', + 'placeholder' => __('Optional reason for the transfer'), + ), + ) + ), + ); + + $this->setFields($fields); + + return $this->fields; + } + + function isValid() { + + if (!parent::isValid()) + return false; + + // Do additional validations + if (!($dept = $this->getDept())) + $this->getField('dept')->addError( + __('Unknown department')); + + return !$this->errors(); + } + + function getClean() { + return parent::getClean(); + } + + function render($options) { + + switch(strtolower($options['template'])) { + case 'simple': + $inc = STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php'; + break; + default: + throw new Exception(sprintf(__('%s: Unknown template style %s'), + get_class(), $options['template'])); + } + + $form = $this; + include $inc; + + } + + function getDept() { + + if (!isset($this->_dept)) { + if (($id = $this->getField('dept')->getClean())) + $this->_dept = Dept::lookup($id); + } + + return $this->_dept; + } +} + + ?> diff --git a/include/class.nav.php b/include/class.nav.php index 6dee08171b05fbf4c7fb7c6eb7658980b89c641a..7d3c119b753d515ddf3e24a76359627eec9723c2 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -123,7 +123,9 @@ class StaffNav { 'desc' => __('Users'), 'href' => 'users.php', 'title' => __('User Directory') ); } + $this->tabs['tasks'] = array('desc'=>__('Tasks'), 'href'=>'tasks.php', 'title'=>__('Task Queue')); $this->tabs['tickets'] = array('desc'=>__('Tickets'),'href'=>'tickets.php','title'=>__('Ticket Queue')); + $this->tabs['kbase'] = array('desc'=>__('Knowledgebase'),'href'=>'kb.php','title'=>__('Knowledgebase')); if (count($this->getRegisteredApps())) $this->tabs['apps']=array('desc'=>__('Applications'),'href'=>'apps.php','title'=>__('Applications')); @@ -140,6 +142,9 @@ class StaffNav { foreach($this->getTabs() as $k=>$tab){ $subnav=array(); switch(strtolower($k)){ + case 'tasks': + $subnav[]=array('desc'=>__('Tasks'), 'href'=>'tasks.php', 'iconclass'=>'Ticket', 'droponly'=>true); + break; case 'tickets': $subnav[]=array('desc'=>__('Tickets'),'href'=>'tickets.php','iconclass'=>'Ticket', 'droponly'=>true); if($staff) { @@ -241,6 +246,7 @@ class AdminNav extends StaffNav{ $subnav[]=array('desc'=>__('Company'),'href'=>'settings.php?t=pages','iconclass'=>'pages'); $subnav[]=array('desc'=>__('System'),'href'=>'settings.php?t=system','iconclass'=>'preferences'); $subnav[]=array('desc'=>__('Tickets'),'href'=>'settings.php?t=tickets','iconclass'=>'ticket-settings'); + $subnav[]=array('desc'=>__('Tasks'),'href'=>'settings.php?t=tasks','iconclass'=>'lists'); $subnav[]=array('desc'=>__('Access'),'href'=>'settings.php?t=access','iconclass'=>'users'); $subnav[]=array('desc'=>__('Knowledgebase'),'href'=>'settings.php?t=kb','iconclass'=>'kb-settings'); $subnav[]=array('desc'=>__('Autoresponder'),'href'=>'settings.php?t=autoresp','iconclass'=>'email-autoresponders'); diff --git a/include/class.plugin.php b/include/class.plugin.php index 4df5688435dc2eb1ae63c1c9cbdac1a1f3c8ffeb..c71d86524df95d44be3ebd3f598029bc17eb7074 100644 --- a/include/class.plugin.php +++ b/include/class.plugin.php @@ -32,7 +32,7 @@ class PluginConfig extends Config { */ function getForm() { if (!isset($this->form)) { - $this->form = new Form($this->getOptions()); + $this->form = new SimpleForm($this->getOptions()); if ($_SERVER['REQUEST_METHOD'] != 'POST') $this->form->data($this->getInfo()); } diff --git a/include/class.search.php b/include/class.search.php index 7225f652e7ab3397ca79fcd6c687013c0b313733..d41f0a59a3a6a871fef236d018cbdde2d3555ffb 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -630,7 +630,7 @@ class SavedSearch extends VerySimpleModel { $fields = array_merge($fields, self::getSearchField($field, $name)); } - $form = new Form($fields, $source); + $form = new SimpleForm($fields, $source); $form->addValidator(function($form) { $selected = 0; foreach ($form->getFields() as $F) { diff --git a/include/class.staff.php b/include/class.staff.php index 9e093d7fcc93dcf349b1761f1bacd1a77ef2d839..b295a15e194272d0e23ca1284f7bf4eec60ef4c5 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -490,6 +490,22 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { return ($stats=$this->getTicketsStats())?$stats['closed']:0; } + function getTasksStats() { + + if (!$this->stats['tasks']) + $this->stats['tasks'] = Task::getStaffStats($this); + + return $this->stats['tasks']; + } + + function getNumAssignedTasks() { + return ($stats=$this->getTasksStats()) ? $stats['assigned'] : 0; + } + + function getNumClosedTasks() { + return ($stats=$this->getTasksStats()) ? $stats['closed'] : 0; + } + function getExtraAttr($attr=false, $default=null) { if (!isset($this->_extra) && isset($this->extra)) $this->_extra = JsonDataParser::decode($this->extra); @@ -681,11 +697,12 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { return null; } - static function getStaffMembers($availableonly=false) { + static function getStaffMembers($criteria=array()) { global $cfg; + $members = static::objects(); - if ($availableonly) { + if (isset($criteria['available'])) { $members = $members->filter(array( 'group__flags__hasbit' => Group::FLAG_ENABLED, 'onvacation' => 0, @@ -713,7 +730,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } static function getAvailableStaffMembers() { - return self::getStaffMembers(true); + return self::getStaffMembers(array('available'=>true)); } static function getIdByUsername($username) { diff --git a/include/class.task.php b/include/class.task.php index a39c28a00ceecbc306086a94bf0afe3dba85c4df..c629379f6e88470408ab6eab9e36467576a38958 100644 --- a/include/class.task.php +++ b/include/class.task.php @@ -33,6 +33,22 @@ class TaskModel extends VerySimpleModel { '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, + ), ), ); @@ -104,14 +120,26 @@ class TaskModel extends VerySimpleModel { 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; } @@ -156,13 +184,12 @@ RolePermission::register(/* @trans */ 'Tasks', TaskModel::getPermissions()); class Task extends TaskModel { var $form; var $entry; - var $thread; + var $_thread; var $_entries; - function getStatus() { - return $this->isOpen() ? _('Open') : _('Closed'); + return $this->isOpen() ? __('Open') : __('Completed'); } function getTitle() { @@ -195,6 +222,34 @@ class Task extends TaskModel { 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(); @@ -202,7 +257,7 @@ class Task extends TaskModel { $assignees[] = $this->staff->getName(); //Add team assignment - if (isset($this->team)) + if ($this->team) $assignees[] = $this->team->getName(); return $assignees; @@ -215,13 +270,6 @@ class Task extends TaskModel { } function getThread() { - - if (!$this->thread) - $this->thread = TaskThread::lookup(array( - 'object_id' => $this->getId(), - 'object_type' => ObjectModel::OBJECT_TYPE_TASK) - ); - return $this->thread; } @@ -257,6 +305,23 @@ class Task extends TaskModel { return $this->form; } + function getAssignmentForm($source=null) { + + if (!$source) + $source = array('assignee' => array($this->getAssigneeId())); + + return AssignmentForm::instantiate($source, + array('dept' => $this->getDept())); + } + + 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); @@ -283,6 +348,40 @@ class Task extends TaskModel { return $this->_entries ?: array(); } + function setStatus($status, $comments='') { + global $thisstaff; + + switch($status) { + case 'open': + if ($this->isOpen()) + return false; + + $this->reopen(); + break; + case 'closed': + if ($this->isClosed()) + return false; + $this->close(); + break; + default: + return false; + } + + $this->save(true); + if ($comments) { + $errors = array(); + $this->postNote(array( + 'note' => $comments, + 'title' => sprintf( + __('Status changed to %s'), + $this->getStatus()) + ), + $errors, + $thisstaff); + } + + return true; + } function to_json() { @@ -298,13 +397,13 @@ class Task extends TaskModel { foreach ($this->getDynamicData() as $e) { // Make sure the form type matches - if (!$e->getForm() - || ($ftype && $ftype != $e->getForm()->get('type'))) + if (!$e->form + || ($ftype && $ftype != $e->form->get('type'))) continue; // Get the named field and return the answer - if ($f = $e->getForm()->getField($field)) - return $f->getAnswer(); + if ($a = $e->getAnswer($field)) + return $a; } return null; @@ -315,44 +414,82 @@ class Task extends TaskModel { } /* util routines */ - function assign($vars, &$errors) { - global $thisstaff; + function assign(AssignmentForm $form, &$errors, $alert=true) { + + $assignee = $form->getAssignee(); + if ($assignee instanceof Staff) { + 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'); + } else { + $this->staff_id = $assignee->getId(); + } + } 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(); - if (!isset($vars['staff_id']) || !($staff=Staff::lookup($vars['staff_id']))) - $errors['staff_id'] = __('Agent selection required'); - elseif ($staff->getid() == $this->getStaffId()) - $errors['dept_id'] = __('Task already assigned to agent'); - else - $this->staff_id = $staff->getId(); + } + } else { + $errors['assignee'] = __('Unknown assignee'); + } - if ($errors || !$this->save()) + if ($errors || !$this->save(true)) return false; - // Transfer completed... post internal note. + $this->onAssignment($assignee, + $form->getField('comments')->getClean(), + $alert); + + return true; + } + + function onAssignment($assignee, $note='', $alert=true) { + global $thisstaff; + + if (!is_object($assignee)) + return false; + + $assigner = $thisstaff ?: __('SYSTEM (Auto Assignment)'); + //Assignment completed... post internal note. $title = sprintf(__('Task assigned to %s'), - $staff->getName()); - if ($vars['comments']) { - $note = $vars['comments']; - } else { + (string) $assignee); + + if (!$note) { $note = $title; $title = ''; } - $this->postNote( + $errors = array(); + $note = $this->postNote( array('note' => $note, 'title' => $title), $errors, - $thisstaff); + $assigner, + false); + + // Send alerts out + if (!$alert) + return false; return true; } - function transfer($vars, &$errors) { + function transfer(TransferForm $form, &$errors, $alert=true) { global $thisstaff; - if (!isset($vars['dept_id']) || !($dept=Dept::lookup($vars['dept_id']))) - $errors['dept_id'] = __('Department selection required'); + $dept = $form->getDept(); + if (!$dept || !($dept instanceof Dept)) + $errors['dept'] = __('Department selection required'); elseif ($dept->getid() == $this->getDeptId()) - $errors['dept_id'] = __('Task already in the department'); + $errors['dept'] = __('Task already in the department'); else $this->dept_id = $dept->getId(); @@ -360,19 +497,24 @@ class Task extends TaskModel { return false; // Transfer completed... post internal note. - $title = sprintf(__('Task transfered to %s department'), + $title = sprintf(__('%s transferred to %s department'), + __('Task'), $dept->getName()); - if ($vars['comments']) { - $note = $vars['comments']; - } else { + + $note = $form->getField('comments')->getClean(); + if (!$note) { $note = $title; $title = ''; } - - $this->postNote( + $_errors = array(); + $note = $this->postNote( array('note' => $note, 'title' => $title), - $errors, - $thisstaff); + $_errors, $thisstaff, false); + + // Send alerts if requested && enabled. + if (!$alert) + return true; + return true; } @@ -392,14 +534,8 @@ class Task extends TaskModel { if (!($note=$this->getThread()->addNote($vars, $errors))) return null; - if (isset($vars['task_status'])) { - if ($vars['task_status']) - $this->reopen(); - else - $this->close(); - - $this->save(true); - } + if (isset($vars['task_status'])) + $this->setStatus($vars['task_status']); return $note; } @@ -496,8 +632,86 @@ class Task extends TaskModel { } } } + + /* 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[] = ' ( flags.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 tickets. + $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; @@ -505,6 +719,12 @@ class TaskForm extends DynamicForm { static $forms; + static $cdata = array( + 'table' => TASK_CDATA_TABLE, + 'object_id' => 'task_id', + 'object_type' => 'A', + ); + static function objects() { $os = parent::objects(); return $os->filter(array('type'=>ObjectModel::OBJECT_TYPE_TASK)); @@ -533,7 +753,7 @@ class TaskForm extends DynamicForm { static function getInternalForm($source=null) { if (!isset(static::$internalForm)) - static::$internalForm = new Form(self::getInternalFields(), $source); + static::$internalForm = new SimpleForm(self::getInternalFields(), $source); return static::$internalForm; } diff --git a/include/class.template.php b/include/class.template.php index a48e5255d47dac70bea9234388b6ef81f35b6bf3..c1bdb80df2135a6b81c09a33c6d7973dbd03c5a4 100644 --- a/include/class.template.php +++ b/include/class.template.php @@ -23,12 +23,13 @@ class EmailTemplateGroup { var $_templates; static $all_groups = array( 'sys' => /* @trans */ 'System Management Templates', - 'ticket.user' => /* @trans */ 'End-User Email Templates', - 'ticket.staff' => /* @trans */ 'Agent Email Templates', + 'a.ticket.user' => /* @trans */ 'Ticket End-User Email Templates', + 'b.ticket.staff' => /* @trans */ 'Ticket Agent Email Templates', + 'c.task' => /* @trans */ 'Task Email Templates', ); static $all_names=array( 'ticket.autoresp'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'New Ticket Auto-response', 'desc'=>/* @trans */ 'Autoresponse sent to user, if enabled, on new ticket.', 'context' => array( @@ -36,7 +37,7 @@ class EmailTemplateGroup { ), ), 'ticket.autoreply'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'New Ticket Auto-reply', 'desc'=>/* @trans */ 'Canned Auto-reply sent to user on new ticket, based on filter matches. Overwrites "normal" auto-response.', 'context' => array( @@ -44,7 +45,7 @@ class EmailTemplateGroup { ), ), 'message.autoresp'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'New Message Auto-response', 'desc'=>/* @trans */ 'Confirmation sent to user when a new message is appended to an existing ticket.', 'context' => array( @@ -52,7 +53,7 @@ class EmailTemplateGroup { ), ), 'ticket.notice'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'New Ticket Notice', 'desc'=>/* @trans */ 'Notice sent to user, if enabled, on new ticket created by an agent on their behalf (e.g phone calls).', 'context' => array( @@ -60,7 +61,7 @@ class EmailTemplateGroup { ), ), 'ticket.overlimit'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'Over Limit Notice', 'desc'=>/* @trans */ 'A one-time notice sent, if enabled, when user has reached the maximum allowed open tickets.', 'context' => array( @@ -68,7 +69,7 @@ class EmailTemplateGroup { ), ), 'ticket.reply'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'Response/Reply Template', 'desc'=>/* @trans */ 'Template used on ticket response/reply', 'context' => array( @@ -76,7 +77,7 @@ class EmailTemplateGroup { ), ), 'ticket.activity.notice'=>array( - 'group'=>'ticket.user', + 'group'=>'a.ticket.user', 'name'=>/* @trans */ 'New Activity Notice', 'desc'=>/* @trans */ 'Template used to notify collaborators on ticket activity (e.g CC on reply)', 'context' => array( @@ -84,7 +85,7 @@ class EmailTemplateGroup { ), ), 'ticket.alert'=>array( - 'group'=>'ticket.staff', + 'group'=>'b.ticket.staff', 'name'=>/* @trans */ 'New Ticket Alert', 'desc'=>/* @trans */ 'Alert sent to agents, if enabled, on new ticket.', 'context' => array( @@ -92,7 +93,7 @@ class EmailTemplateGroup { ), ), 'message.alert'=>array( - 'group'=>'ticket.staff', + 'group'=>'b.ticket.staff', 'name'=>/* @trans */ 'New Message Alert', 'desc'=>/* @trans */ 'Alert sent to agents, if enabled, when user replies to an existing ticket.', 'context' => array( @@ -100,7 +101,7 @@ class EmailTemplateGroup { ), ), 'note.alert'=>array( - 'group'=>'ticket.staff', + 'group'=>'b.ticket.staff', 'name'=>/* @trans */ 'Internal Activity Alert', 'desc'=>/* @trans */ 'Alert sent out to Agents when internal activity such as an internal note or an agent reply is appended to a ticket.', 'context' => array( @@ -108,7 +109,7 @@ class EmailTemplateGroup { ), ), 'assigned.alert'=>array( - 'group'=>'ticket.staff', + 'group'=>'b.ticket.staff', 'name'=>/* @trans */ 'Ticket Assignment Alert', 'desc'=>/* @trans */ 'Alert sent to agents on ticket assignment.', 'context' => array( @@ -116,7 +117,7 @@ class EmailTemplateGroup { ), ), 'transfer.alert'=>array( - 'group'=>'ticket.staff', + 'group'=>'b.ticket.staff', 'name'=>/* @trans */ 'Ticket Transfer Alert', 'desc'=>/* @trans */ 'Alert sent to agents on ticket transfer.', 'context' => array( @@ -124,13 +125,45 @@ class EmailTemplateGroup { ), ), 'ticket.overdue'=>array( - 'group'=>'ticket.staff', + 'group'=>'b.ticket.staff', 'name'=>/* @trans */ 'Overdue Ticket Alert', 'desc'=>/* @trans */ 'Alert sent to agents on stale or overdue tickets.', 'context' => array( 'ticket', 'recipient', 'comments', ), ), + 'task.alert' => array( + 'group'=>'c.task', + 'name'=>/* @trans */ 'New Task Alert', + 'desc'=>/* @trans */ 'Alert sent to agents, if enabled, on new task.', + ), + 'task.activity.notice' => array( + 'group'=>'c.task', + 'name'=>/* @trans */ 'New Activity Notice', + 'desc'=>/* @trans */ 'Template used to notify collaborators on task activity.' + ), + 'task.activity.alert'=>array( + 'group'=>'c.task', + 'name'=>/* @trans */ 'New Activity Alert', + 'desc'=>/* @trans */ 'Alert sent to selected agents, if enabled, on new activity.', + + ), + 'task.assignment.alert' => array( + 'group'=>'c.task', + 'name'=>/* @trans */ 'Task Assignment Alert', + 'desc'=>/* @trans */ 'Alert sent to agents on task assignment.', + ), + 'task.transfer.alert'=>array( + 'group'=>'c.task', + 'name'=>/* @trans */ 'Task Transfer Alert', + 'desc'=>/* @trans */ 'Alert sent to agents on task transfer.', + ), + 'task.overdue.alert'=>array( + 'group'=>'c.task', + 'name'=>/* @trans */ 'Overdue Task Alert', + 'desc'=>/* @trans */ 'Alert sent to agents on stale or overdue + task.', + ), ); function EmailTemplateGroup($id){ @@ -300,6 +333,27 @@ class EmailTemplateGroup { return $this->getMsgTemplate('ticket.overdue'); } + /* Tasks templates */ + function getNewTaskAlertMsgTemplate() { + return $this->getMsgTemplate('task.alert'); + } + + function getTaskActivityNoticeMsgTemplate() { + return $this->getMsgTemplate('task.activity.notice'); + } + + function getTaskTransferAlertMsgTemplate() { + return $this->getMsgTemplate('task.transfer.alert'); + } + + function getTaskAssignmentAlertMsgTemplate() { + return $this->getMsgTemplate('task.assignment.alert'); + } + + function getTaskOverdueAlertMsgTemplate() { + return $this->getMsgTemplate('task.overdue.alert'); + } + function update($vars,&$errors) { if(!$vars['isactive'] && $this->isInUse()) $errors['isactive']=__('In-use template set cannot be disabled!'); diff --git a/include/class.thread.php b/include/class.thread.php index 4b3771e10c864cf0c611ea4d6b35d9a23b333fe9..bd50ecb20c48a8c7e3a2e935cfdf878042c07748 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -89,6 +89,15 @@ class Thread extends VerySimpleModel { return $base; } + function render($type=false) { + + $entries = $this->getEntries(); + if ($type && is_array($type)) + $entries->filter(array('type__in' => $type)); + + include STAFFINC_DIR . 'templates/thread-entries.tmpl.php'; + } + function getEntry($id) { return ThreadEntry::lookup($id, $this->getId()); } diff --git a/include/class.ticket.php b/include/class.ticket.php index c275127847b51731ed838b4240d7d56d8bd594e1..cbb51b4143eca41bdfdf9570835237d5e51db4be 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -876,10 +876,11 @@ implements RestrictedAccess, Threadable, TemplateVariable { } function getThreadEntries($type=false) { - $thread = $this->getThread()->getEntries(); + $entries = $this->getThread()->getEntries(); if ($type && is_array($type)) - $thread->filter(array('type__in' => $type)); - return $thread; + $entries->filter(array('type__in' => $type)); + + return $entries; } //Collaborators diff --git a/include/i18n/en_US/help/tips/settings.tasks.yaml b/include/i18n/en_US/help/tips/settings.tasks.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6d1f056187ec87bc7e9ce8d42a05bd702f5bf7c0 --- /dev/null +++ b/include/i18n/en_US/help/tips/settings.tasks.yaml @@ -0,0 +1,110 @@ +# +# This is popup help messages for the Admin Panel -> Settings -> Tasks +# +# Fields: +# title - Shown in bold at the top of the popover window +# content - The body of the help popover +# links - List of links shows below the content +# title - Link title +# href - href of link (links starting with / are translated to the +# helpdesk installation path) +# +# The key names such as 'helpdesk_name' should not be translated as they +# must match the HTML #ids put into the page template. +# +--- +number_format: + title: Task Number Format + content: > + This setting is used to generate task numbers. Use hash signs + (`#`) where digits are to be placed. Any other text in the number + format will be preserved. + <br/><br/> + For example, for six-digit numbers, use <code>######</code>. + +sequence_id: + title: Task Number Sequence + content: > + Choose a sequence from which to derive new task numbers. The + system has a incrementing sequence and a random sequence by default. + You may create as many sequences as you wish. + +default_sla: + title: Default SLA + content: > + Choose the default Service Level Agreement to manage how long a task + can remain Open before it is rendered Overdue. + links: + - title: Create more SLA Plans + href: /scp/slas.php + +default_priority: + title: Default Priority + content: > + Choose a default <span class="doc-desc-title">priority</span> for + tasks not assigned a priority automatically. + +task_attachment_settings: + title: Task Thread Attachments + content: > + Configure settings for files attached to the <span + class="doc-desc-title">description</span> field. These settings + are used for all new tasks and new messages regardless of the + source channel (web portal, email, api, etc.). + +page_title: + title: Alerts and Notices + content: > + Alerts and Notices are automated email notifications sent to Agents + when various task events are triggered. + +task_alert: + title: New Task Alert + content: > + <p> + Alert sent out to Agents when a new task is created. + </p><p class="info-banner"> + <i class="icon-info-sign"></i> + This alert is not sent out if the task is auto-assigned. + </p> + links: + - title: Default New Task Alert Template + href: /scp/templates.php?default_for=task.alert + +activity_alert: + title: New Activity Alert + content: > + Alert sent out to Agents when a new message is appended to an + existing task. + links: + - title: Default New Activity Alert Template + href: /scp/templates.php?default_for=task.activity.alert + +assignment_alert: + title: Task Assignment Alert + content: > + Alert sent out to Agents on task assignment. + links: + - title: Default Task Assignment Alert Template + href: /scp/templates.php?default_for=task.assignment.alert + +transfer_alert: + title: Task Transfer Alert + content: > + Alert sent out to Agents on task transfer between Departments. + links: + - title: Default Task Transfer Alert Template + href: /scp/templates.php?default_for=task.transfer.alert + +overdue_alert: + title: Overdue Task Alert + content: > + Alert sent out to Agents when a task becomes overdue based on SLA + or Due Date. + links: + - title: Default Stale Task Alert Template + href: /scp/templates.php?default_for=task.overdue.alert + + - title: Manage SLAs + href: /scp/slas.php + diff --git a/include/i18n/en_US/templates/email/task.activity.alert.yaml b/include/i18n/en_US/templates/email/task.activity.alert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..82e7c4ce04474cf4214844d8492560074451dc1d --- /dev/null +++ b/include/i18n/en_US/templates/email/task.activity.alert.yaml @@ -0,0 +1,54 @@ +# +# Email template: task.activity.alert.yaml +# +# Sent to agents when a new note/message is posted to a task. +# This can occur if a collaborator or an agent responds to an email from the +# system or visits the web portal and posts a new message there. +# +# +--- +notes: | + Sent to agents when a new message/note is posted to a task. This can + occur if a collaborator or an agent responds to an email from the system + or visits the web portal and posts a new message there. + +subject: | + New Activity Alert +body: | + <h3><strong>Hi %{recipient.name},</strong></h3> + New message appended to task <a + href="%{ticket.staff_link}">#%{task.number}</a> + <br> + <br> + <table> + <tbody> + <tr> + <td> + <strong>Title</strong>: + </td> + <td> + %{task.title} + </td> + </tr> + <tr> + <td> + <strong>Department</strong>: + </td> + <td> + %{task.dept.name} + </td> + </tr> + </tbody> + </table> + <br> + %{message} + <br> + <br> + <hr> + <div>To view or respond to the task, please <a + href="%{task.staff_link}"><span style="color: rgb(84, 141, 212);" + >login</span></a> to the support system</div> + <em style="color: rgb(127,127,127); font-size: small; ">Your friendly + Customer Support System</em><br> + <img src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc" + alt="Powered by osTicket" width="126" height="19" style="width: 126px;"> diff --git a/include/i18n/en_US/templates/email/task.activity.notice.yaml b/include/i18n/en_US/templates/email/task.activity.notice.yaml new file mode 100644 index 0000000000000000000000000000000000000000..45d1e3e51aecdf64851b0f54fc230f71824be969 --- /dev/null +++ b/include/i18n/en_US/templates/email/task.activity.notice.yaml @@ -0,0 +1,28 @@ +# +# Email template: task.activity.notice.yaml +# +# Notice sent to collaborators on task activity e.g reply or message +# +--- +notes: | + Notice sent to collaborators on task activity e.g note or message. + +subject: | + Re: %{task.title} [#%{task.number}] +body: | + <h3><strong>Dear %{recipient.name.first},</strong></h3> + <div> + <em>%{poster.name}</em> just logged a message to a task in which you participate. + </div> + <br> + %{message} + <br> + <br> + <hr> + <div style="color: rgb(127, 127, 127); font-size: small; text-align: center;"> + <em>You're getting this email because you are a collaborator + on task <a href="%{recipient.task_link}" style="color: rgb(84, 141, 212);" + >#%{task.number}</a>. To participate, simply reply to this email + or <a href="%{recipient.task_link}" style="color: rgb(84, 141, 212);" + >click here</a> for a complete archive of the task thread.</em> + </div> diff --git a/include/i18n/en_US/templates/email/task.alert.yaml b/include/i18n/en_US/templates/email/task.alert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..08e311fe09f322b8c5606fb1d4e28d8d614aa678 --- /dev/null +++ b/include/i18n/en_US/templates/email/task.alert.yaml @@ -0,0 +1,41 @@ +# +# Email template: task.alert.yaml +# +# Sent to an agent when a new task is created in the system. +# +# +--- +notes: | + Sent to an agent when a new task is created in the system. + +subject: | + New Task Alert +body: | + <h2>Hi %{recipient.name},</h2> + New task #%{task.number} created + <br> + <br> + <table> + <tbody> + <tr> + <td> + <strong>Department</strong>: + </td> + <td> + %{task.dept.name} + </td> + </tr> + </tbody> + </table> + <br> + %{task.description} + <br> + <br> + <hr> + <div>To view or respond to the ticket, please <a + href="%{task.staff_link}">login</a> to the support system</div> + <em style="font-size: small">Your friendly Customer Support System</em> + <br> + <a href="http://osticket.com/"><img width="126" height="19" + style="width: 126px; " alt="Powered By osTicket" + src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc"/></a> diff --git a/include/i18n/en_US/templates/email/task.assignment.alert.yaml b/include/i18n/en_US/templates/email/task.assignment.alert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4c509b08bb812f629966688d727e09a494256ac9 --- /dev/null +++ b/include/i18n/en_US/templates/email/task.assignment.alert.yaml @@ -0,0 +1,45 @@ +# +# Email template: task.assignment.alert.yaml +# +# Sent to agents when a ticket is assigned to them or the team to which +# they belong. +# Use %{assigner} to distinguish who made the assignment. +# +--- +notes: | + Sent to agents when a ticket is assigned to them or the team to which + they belong. Use %{assigner} to distinguish who made the assignment. + +subject: | + Task Assigned to you +body: | + <h3><strong>Hi %{assignee.name.first},</strong></h3> + Task <a href="%{task.staff_link}">#%{task.number}</a> has been + assigned to you by %{assigner.name.short} + <br> + <br> + <table> + <tbody> + <tr> + <td> + <strong>Title</strong>: + </td> + <td> + %{task.title} + </td> + </tr> + </tbody> + </table> + <br> + %{comments} + <br> + <br> + <hr> + <div>To view/respond to the task, please <a + href="%{task.staff_link}"><span style="color: rgb(84, 141, 212);" + >login</span></a> to the support system</div> + <em style="font-size: small; ">Your friendly Customer Support + System</em> + <br> + <img src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc" + alt="Powered by osTicket" width="126" height="19" style="width: 126px;"> diff --git a/include/i18n/en_US/templates/email/task.overdue.alert.yaml b/include/i18n/en_US/templates/email/task.overdue.alert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0b48a7337e04efd6dbdf6c0f8c7cfeb15d7b463f --- /dev/null +++ b/include/i18n/en_US/templates/email/task.overdue.alert.yaml @@ -0,0 +1,38 @@ +# +# Email template: task.overdue.alert.yaml +# +# Sent to agents when a tasks transitions to overdue in the system. +# Overdue tasks occur based on set due-date as well as the SLA +# defined for the task. +# +--- +notes: | + Sent to agents when a task transitions to overdue in the system. + Overdue tasks occur based on the set due-date as well as the SLA + defined for the task. + +subject: | + Stale Task Alert +body: | + <h3><strong>Hi %{recipient.name}</strong>,</h3> + A task, <a href="%{task.staff_link}">#%{task.number}</a> is + seriously overdue. + <br> + <br> + We should all work hard to guarantee that all tasks are being + addressed in a timely manner. + <br> + <br> + Signed,<br> + %{task.dept.manager.name} + <hr> + <div>To view or respond to the task, please <a + href="%{task.staff_link}"><span style="color: rgb(84, 141, 212);" + >login</span></a> to the support system. You're receiving this + notice because the task is assigned directly to you or to a team or + department of which you're a member.</div> + <em style="font-size: small">Your friendly <span style="font-size: smaller" + >(although with limited patience)</span> Customer Support + System</em><br> + <img src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc" height="19" + alt="Powered by osTicket" width="126" style="width: 126px;"> diff --git a/include/i18n/en_US/templates/email/task.transfer.alert.yaml b/include/i18n/en_US/templates/email/task.transfer.alert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6e2d26a26865a03c58495045965d87d24e215137 --- /dev/null +++ b/include/i18n/en_US/templates/email/task.transfer.alert.yaml @@ -0,0 +1,31 @@ +# +# Email template: task.transfer.alert.yaml +# +# Sent to agents when a task is transfered to their department. +# +--- +notes: | + Sent to agents when a task is transfered to a department to which + they are a member. +subject: | + Task #%{task.number} transfer - %{task.dept.name} +body: | + <h3>Hi %{recipient.name},</h3> + Task <a href="%{task.staff_link}">#%{task.number}</a> has been + transferred to the %{task.dept.name} department by + <strong>%{staff.name.short}</strong> + <br> + <br> + <blockquote> + %{comments} + </blockquote> + <hr> + <div>To view or respond to the task, please <a + href="%{task.staff_link}">login</a> to the support system. + </div> + <em style="font-size: small; ">Your friendly Customer Support + System</em> + <br> + <a href="http://osticket.com/"><img width="126" height="19" + alt="Powered By osTicket" style="width: 126px;" + src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc"/></a> diff --git a/include/staff/settings-tasks.inc.php b/include/staff/settings-tasks.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..d6a0d61d69d8cf73028dd566ac74f193486ef98f --- /dev/null +++ b/include/staff/settings-tasks.inc.php @@ -0,0 +1,311 @@ +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); +if(!($maxfileuploads=ini_get('max_file_uploads'))) + $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; +?> +<h2><?php echo __('Tasks Settings and Options');?></h2> +<form action="settings.php?t=tasks" method="post" id="save"> +<?php csrf_token(); ?> +<input type="hidden" name="t" value="tasks" > + +<ul class="tabs" id="tasks-tabs"> + <li class="active"><a href="#settings"> + <i class="icon-asterisk"></i> <?php echo __('Settings'); ?></a></li> + <li><a href="#alerts"> + <i class="icon-bell-alt"></i> <?php echo __('Alerts & Notices'); ?></a></li> +</ul> +<div id="tasks-tabs_container"> + <div id="settings" class="tab_content"> + <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> + <thead> + <tr> + <th colspan="2"> + <em><?php echo __('Global default task settings and options.'); ?></em> + </th> + </tr> + </thead> + <tbody> + <tr> + <td> + <?php echo __('Default Task Number Format'); ?>: + </td> + <td> + <input type="text" name="task_number_format" value="<?php + echo $config['task_number_format']; ?>"/> + <span class="faded"><?php echo __('e.g.'); ?> <span id="format-example"><?php + if ($config['task_sequence_id']) + $seq = Sequence::lookup($config['task_sequence_id']); + if (!isset($seq)) + $seq = new RandomSequence(); + echo $seq->current($config['task_number_format']); + ?></span></span> + <i class="help-tip icon-question-sign" href="#number_format"></i> + <div class="error"><?php echo $errors['task_number_format']; ?></div> + </td> + </tr> + <tr><td width="220"><?php echo __('Default Task Number Sequence'); ?>:</td> + <?php $selected = 'selected="selected"'; ?> + <td> + <select name="task_sequence_id"> + <option value="0" <?php if ($config['task_sequence_id'] == 0) echo $selected; + ?>>— <?php echo __('Random'); ?> —</option> + <?php foreach (Sequence::objects() as $s) { ?> + <option value="<?php echo $s->id; ?>" <?php + if ($config['task_sequence_id'] == $s->id) echo $selected; + ?>><?php echo $s->name; ?></option> + <?php } ?> + </select> + <button class="action-button pull-right" onclick="javascript: + $.dialog('ajax.php/sequence/manage', 205); + return false; + "><i class="icon-gear"></i> <?php echo __('Manage'); ?></button> + <i class="help-tip icon-question-sign" href="#sequence_id"></i> + </td> + </tr> + <tr> + <td width="180" class="required"><?php echo __('Default Priority');?>:</td> + <td> + <select name="default_task_priority_id"> + <?php + $priorities= db_query('SELECT priority_id,priority_desc FROM '.TICKET_PRIORITY_TABLE); + while (list($id,$tag) = db_fetch_row($priorities)){ ?> + <option value="<?php echo $id; ?>"<?php echo + ($config['default_task_priority_id']==$id)?'selected':''; ?>><?php echo $tag; ?></option> + <?php + } ?> + </select> + <span class="error">* <?php echo + $errors['default_task_priority_id']; ?></span> <i class="help-tip icon-question-sign" href="#default_priority"></i> + </td> + </tr> + <tr> + <td width="180" class="required"> + <?php echo __('Default SLA');?>: + </td> + <td> + <span> + <select name="default_task_sla_id"> + <option value="0">— <?php echo __('None');?> —</option> + <?php + if($slas=SLA::getSLAs()) { + foreach($slas as $id => $name) { + echo sprintf('<option value="%d" %s>%s</option>', + $id, + ($config['default_task_sla_id'] && + $id==$config['default_task_sla_id'])?'selected="selected"':'', + $name); + } + } + ?> + </select> + <span class="error">* <?php + echo $errors['default_task_sla_id']; ?></span> <i class="help-tip icon-question-sign" href="#default_sla"></i> + </span> + </td> + </tr> + </tbody> + </table> + </div> + <div id="alerts" class="tab_content" style="display:none;"> + <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> + <tbody> + <tr><th><em><b><?php echo __('New Task Alert'); ?></b>: + <i class="help-tip icon-question-sign" href="#task_alert"></i> + </em></th></tr> + <tr> + <td><em><b><?php echo __('Status'); ?>:</b></em> + <input type="radio" name="task_alert_active" value="1" + <?php echo $config['task_alert_active'] ? 'checked="checked"' : ''; ?> + /> <?php echo __('Enable'); ?> + <input type="radio" name="task_alert_active" value="0" + <?php echo !$config['task_alert_active'] ? 'checked="checked"' : ''; ?> /> + <?php echo __('Disable'); ?> + <font class="error"> <?php echo $errors['task_alert_active']; ?></font></em> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_alert_admin" <?php + echo $config['task_alert_admin'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Admin Email'); ?> <em>(<?php echo $cfg->getAdminEmail(); ?>)</em> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_alert_dept_manager" + <?php echo $config['task_alert_dept_manager'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Manager'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_alert_dept_members" + <?php echo $config['task_alert_dept_members'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Members'); ?> + </td> + </tr> + <tr><th><em><b><?php echo __('New Activity Alert'); ?></b>: + <i class="help-tip icon-question-sign" href="#activity_alert"></i> + </em></th></tr> + <tr> + <td><em><b><?php echo __('Status'); ?>:</b></em> + <input type="radio" name="task_activity_alert_active" value="1" + <?php echo $config['task_activity_alert_active'] ? 'checked="checked"' : ''; ?> /> + <?php echo __('Enable'); ?> + + <input type="radio" name="task_activity_alert_active" value="0" + <?php echo !$config['task_activity_alert_active'] ? 'checked="checked"' : ''; ?> /> + <?php echo __('Disable'); ?> + <font class="error"> <?php echo $errors['task_activity_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_activity_alert_laststaff" <?php + echo $config['task_activity_alert_laststaff'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Last Respondent'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_activity_alert_assigned" + <?php echo $config['task_activity_alert_assigned'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Assigned Agent / Team'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_activity_alert_dept_manager" + <?php echo $config['task_activity_alert_dept_manager'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Manager'); ?> + </td> + </tr> + <tr><th><em><b><?php echo __('Task Assignment Alert'); ?></b>: + <i class="help-tip icon-question-sign" href="#assignment_alert"></i> + </em></th></tr> + <tr> + <td><em><b><?php echo __('Status'); ?>: </b></em> + <input name="task_assignment_alert_active" value="1" type="radio" + <?php echo $config['task_assignment_alert_active'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Enable'); ?> + + <input name="task_assignment_alert_active" value="0" type="radio" + <?php echo !$config['task_assignment_alert_active'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Disable'); ?> + <font class="error"> <?php echo $errors['task_assignment_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_assignment_alert_staff" <?php echo + $config['task_assignment_alert_staff'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Assigned Agent / Team'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox"name="task_assignment_alert_team_lead" <?php + echo $config['task_assignment_alert_team_lead'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Team Lead'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox"name="task_assignment_alert_team_members" + <?php echo $config['task_assignment_alert_team_members'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Team Members'); ?> + </td> + </tr> + <tr><th><em><b><?php echo __('Task Transfer Alert'); ?></b>: + <i class="help-tip icon-question-sign" href="#transfer_alert"></i> + </em></th></tr> + <tr> + <td><em><b><?php echo __('Status'); ?>:</b></em> + <input type="radio" name="task_transfer_alert_active" value="1" + <?php echo $config['task_transfer_alert_active'] ? 'checked="checked"' : ''; ?> /> + <?php echo __('Enable'); ?> + <input type="radio" name="task_transfer_alert_active" value="0" + <?php echo !$config['task_transfer_alert_active'] ? 'checked="checked"' : ''; ?> /> + <?php echo __('Disable'); ?> + <font class="error"> <?php + echo $errors['task_transfer_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_transfer_alert_assigned" + <?php echo $config['task_transfer_alert_assigned']?'checked="checked"':''; ?>> + <?php echo __('Assigned Agent / Team'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_transfer_alert_dept_manager" + <?php echo $config['task_transfer_alert_dept_manager'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Manager'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_transfer_alert_dept_members" + <?php echo $config['task_transfer_alert_dept_members'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Members'); ?> + </td> + </tr> + <tr><th><em><b><?php echo __('Overdue Task Alert'); ?></b>: + <i class="help-tip icon-question-sign" href="#overdue_alert"></i> + </em></th></tr> + <tr> + <td><em><b><?php echo __('Status'); ?>:</b></em> + <input type="radio" name="task_overdue_alert_active" value="1" + <?php echo $config['task_overdue_alert_active'] ? 'checked="checked"' : ''; ?> /> <?php echo __('Enable'); ?> + <input type="radio" name="task_overdue_alert_active" value="0" + <?php echo !$config['task_overdue_alert_active'] ? 'checked="checked"' : ''; ?> /> <?php echo __('Disable'); ?> + <font class="error"> <?php echo $errors['task_overdue_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_overdue_alert_assigned" <?php + echo $config['task_overdue_alert_assigned'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Assigned Agent / Team'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_overdue_alert_dept_manager" <?php + echo $config['task_overdue_alert_dept_manager'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Manager'); ?> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="task_overdue_alert_dept_members" <?php + echo $config['task_overdue_alert_dept_members'] ? 'checked="checked"' : ''; ?>> + <?php echo __('Department Members'); ?> + </td> + </tr> + </tbody> + </table> + </div> +</div> +<p style="padding-left:250px;"> + <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes');?>"> + <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes');?>"> +</p> +</form> +<script type="text/javascript"> +$(function() { + var request = null, + update_example = function() { + request && request.abort(); + request = $.get('ajax.php/sequence/' + + $('[name=task_sequence_id] :selected').val(), + {'format': $('[name=task_number_format]').val()}, + function(data) { $('#format-example').text(data); } + ); + }; + $('[name=task_sequence_id]').on('change', update_example); + $('[name=task_number_format]').on('keyup', update_example); +}); +</script> diff --git a/include/staff/task-view.inc.php b/include/staff/task-view.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..a6b140481ad83effa08d99218803327a19c814e4 --- /dev/null +++ b/include/staff/task-view.inc.php @@ -0,0 +1,5 @@ +<div id="task_content"> +<?php +require STAFFINC_DIR.'templates/task-view.tmpl.php'; +?> +</div> diff --git a/include/staff/tasks.inc.php b/include/staff/tasks.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..cd07dfa21bbd3edb44012f34cba80a06ecc9dd37 --- /dev/null +++ b/include/staff/tasks.inc.php @@ -0,0 +1,402 @@ +<?php +$tasks = TaskModel::objects(); +$date_header = $date_col = false; + +// Figure out REFRESH url — which might not be accurate after posting a +// response +list($path,) = explode('?', $_SERVER['REQUEST_URI'], 2); +$args = array(); +parse_str($_SERVER['QUERY_STRING'], $args); + +// Remove commands from query +unset($args['id']); +unset($args['a']); + +$refresh_url = $path . '?' . http_build_query($args); + +$queue_name = strtolower($_GET['status'] ?: $_GET['a']); //Status is overloaded +switch ($queue_name) { +case 'closed': + $status='closed'; + $results_type=__('Closed Tasks'); + $showassigned=true; //closed by. + break; +case 'overdue': + $status='open'; + $results_type=__('Overdue Tasks'); + $tasks->filter(array('isoverdue'=>1)); + break; +case 'assigned': + $status='open'; + $staffId=$thisstaff->getId(); + $results_type=__('My Tasks'); + $tasks->filter(array('staff_id'=>$thisstaff->getId())); + break; +default: +case 'search': + // Consider basic search + if ($_REQUEST['query']) { + $results_type=__('Search Results'); + $tasks = $tasks->filter(Q::any(array( + 'number__startswith' => $_REQUEST['query'], + 'cdata__title__contains' => $_REQUEST['query'], + ))); + break; + } elseif (isset($_SESSION['advsearch'])) { + // XXX: De-duplicate and simplify this code + $form = $search->getFormFromSession('advsearch'); + $form->loadState($_SESSION['advsearch']); + $tasks = $search->mangleQuerySet($tasks, $form); + $results_type=__('Advanced Search') + . '<a class="action-button" href="?clear_filter"><i class="icon-ban-circle"></i> <em>' . __('clear') . '</em></a>'; + break; + } + // Fall-through and show open tickets +case 'open': + $status='open'; + $results_type=__('Open Tasks'); + break; +} + +// Apply filters +$filters = array(); +$SQ = new Q(array('flags__hasbit' => TaskModel::ISOPEN)); +if ($status && !strcasecmp($status, 'closed')) + $SQ->negate(); + +$filters[] = $SQ; +$tasks->filter($filters); + +// Impose visibility constraints +// ------------------------------------------------------------ +// -- Open and assigned to me +$visibility = array( + new Q(array('flags__hasbit' => TaskModel::ISOPEN, 'staff_id' => $thisstaff->getId())) +); +// -- Routed to a department of mine +if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts())) + $visibility[] = new Q(array('dept_id__in' => $depts)); +// -- Open and assigned to a team of mine +if (($teams = $thisstaff->getTeams()) && count(array_filter($teams))) + $visibility[] = new Q(array( + 'team_id__in' => array_filter($teams), + 'flags__hasbit' => TaskModel::ISOPEN + )); +$tasks->filter(Q::any($visibility)); + +// Add in annotations +$tasks->annotate(array( + //'collab_count' => SqlAggregate::COUNT('collaborators'), + 'attachment_count' => SqlAggregate::COUNT('thread__entries__attachments'), + 'thread_count' => SqlAggregate::COUNT('thread__entries'), + // 'isopen' => new SqlExpr(array('flags__hasbit' => TaskModel::ISOPEN)), +)); + +$tasks->values('id', 'number', 'created', 'staff_id', 'team_id', + 'staff__firstname', 'staff__lastname', 'team__name', + 'dept__name', 'cdata__title', 'flags'); +// Apply requested quick filter + +// Apply requested sorting +$queue_sort_key = sprintf(':Q:%s:sort', $queue_name); + +if (isset($_GET['sort'])) + $_SESSION[$queue_sort_key] = $_GET['sort']; +switch ($_SESSION[$queue_sort_key]) { +case 'number': + $tasks->extra(array( + 'order_by'=>array(SqlExpression::times(new SqlField('number'), 1)) + )); + break; +case 'priority,due': + $tasks->order_by('cdata__:priority__priority_urgency'); + // Fall through to add in due date filter +case 'due': + $date_header = __('Due Date'); + $date_col = 'est_duedate'; + $tasks->values('est_duedate'); + $tasks->filter(array('est_duedate__isnull'=>false)); + $tasks->order_by('est_duedate'); + break; +case 'updated': + $tasks->order_by('cdata__:priority__priority_urgency', '-lastupdate'); + break; +case 'created': +default: + $tasks->order_by('-created'); + break; +} + +// Apply requested pagination +$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; +$pageNav=new Pagenate($tasks->count(), $page, PAGE_LIMIT); +$pageNav->setURL('tasks.php', $args); +$tasks = $pageNav->paginate($tasks); + +TaskForm::ensureDynamicDataView(); + +// Save the query to the session for exporting +$_SESSION[':Q:tasks'] = $tasks; + +// Mass actions +$actions = array(); + +if ($thisstaff->hasPerm(Task::PERM_ASSIGN)) { + $actions += array( + 'assign' => array( + 'icon' => 'icon-user', + 'action' => __('Assign Tasks') + )); +} + +if ($thisstaff->hasPerm(Task::PERM_TRANSFER)) { + $actions += array( + 'transfer' => array( + 'icon' => 'icon-share', + 'action' => __('Transfer Tasks') + )); +} + +if ($thisstaff->hasPerm(Task::PERM_DELETE)) { + $actions += array( + 'delete' => array( + 'icon' => 'icon-trash', + 'action' => __('Delete Tasks') + )); +} + + +?> +<!-- SEARCH FORM START --> +<div id='basic_search'> + <form action="tasks.php" method="get"> + <input type="hidden" name="a" value="search"> + <table> + <tr> + <td><input type="text" id="basic-ticket-search" name="query" + size=30 value="<?php echo Format::htmlchars($_REQUEST['query'], + true); ?>" + autocomplete="off" autocorrect="off" autocapitalize="off"></td> + <td><input type="submit" class="button" value="<?php echo __('Search'); ?>"></td> + <td> <a href="#" onclick="javascript: + $.dialog('ajax.php/tasks/search', 201);" + >[<?php echo __('advanced'); ?>]</a> <i class="help-tip icon-question-sign" href="#advanced"></i></td> + </tr> + </table> + </form> +</div> +<!-- SEARCH FORM END --> +<div class="clear"></div> +<div style="margin-bottom:20px; padding-top:10px;"> +<div> + <div class="pull-left flush-left"> + <h2><a href="<?php echo $refresh_url; ?>" + title="<?php echo __('Refresh'); ?>"><i class="icon-refresh"></i> <?php echo + $results_type.$showing; ?></a></h2> + </div> + <div class="pull-right flush-right"> + <span style="display:inline-block"> + <span style="vertical-align: baseline">Sort:</span> + <select name="sort" onchange="javascript:addSearchParam('sort', $(this).val());"> +<?php foreach (array( + 'created' => __('Most Recently Created'), + 'updated' => __('Most Recently Updated'), + 'due' => __('Due Soon'), + 'priority,due' => __('Priority + Due Soon'), + 'number' => __('Task Number'), +) as $mode => $desc) { ?> + <option value="<?php echo $mode; ?>" <?php if ($mode == $_SESSION[$queue_sort_key]) echo 'selected="selected"'; ?>><?php echo $desc; ?></option> +<?php } ?> + </select> + </span> + <?php + Task::getAgentActions($thisstaff, array('status' => $status)); + ?> + </div> +</div> +<div class="clear" style="margin-bottom:10px;"></div> +<form action="tasks.php" method="POST" name='tasks' id="tasks"> +<?php csrf_token(); ?> + <input type="hidden" name="a" value="mass_process" > + <input type="hidden" name="do" id="action" value="" > + <input type="hidden" name="status" value="<?php echo + Format::htmlchars($_REQUEST['status'], true); ?>" > + <table class="list" border="0" cellspacing="1" cellpadding="2" width="940"> + <thead> + <tr> + <?php if ($thisstaff->canManageTickets()) { ?> + <th width="8px"> </th> + <?php } ?> + <th width="70"> + <?php echo __('Number'); ?></th> + <th width="70"> + <?php echo $date_header ?: __('Date'); ?></th> + <th width="280"> + <?php echo __('Title'); ?></th> + <th width="250"> + <?php echo __('Department');?></th> + <th width="250"> + <?php echo __('Assignee');?></th> + </tr> + </thead> + <tbody> + <?php + // Setup Subject field for display + $total=0; + $title_field = TaskForm::getInstance()->getField('title'); + $ids=($errors && $_POST['tids'] && is_array($_POST['tids']))?$_POST['tids']:null; + foreach ($tasks as $T) { + $T['isopen'] = ($T['flags'] & TaskModel::ISOPEN != 0); //XXX: + $total += 1; + $tag=$T['staff_id']?'assigned':'openticket'; + $flag=null; + if($T['lock__staff_id'] && $T['lock__staff_id'] != $thisstaff->getId()) + $flag='locked'; + elseif($T['isoverdue']) + $flag='overdue'; + + $assignee = ''; + $dept = Dept::getLocalById($T['dept_id'], 'name', $T['dept__name']); + $assinee =''; + if ($T['staff_id']) { + $staff = new PersonsName($T['staff__firstname'].' '.$T['staff__lastname']); + $assignee = sprintf('<span class="Icon staffAssigned">%s</span>', + Format::truncate((string) $staff, 40)); + } elseif($T['team_id']) { + $assignee = sprintf('<span class="Icon teamAssigned">%s</span>', + Format::truncate(Team::getLocalById($T['team_id'], 'name', $T['team__name']),40)); + } + + $threadcount=$T['thread_count']; + $number = $T['number']; + if ($T['isopen']) + $number = sprintf('<b>%s</b>', $number); + + $title = Format::truncate($title_field->display($title_field->to_php($T['cdata__title'])), 40); + ?> + <tr id="<?php echo $T['id']; ?>"> + <?php + if ($thisstaff->canManageTickets()) { + $sel = false; + if ($ids && in_array($T['id'], $ids)) + $sel = true; + ?> + <td align="center" class="nohover"> + <input class="ckb" type="checkbox" name="tids[]" + value="<?php echo $T['id']; ?>" <?php echo $sel?'checked="checked"':''; ?>> + </td> + <?php } ?> + <td nowrap> + <a class="preview" + href="tasks.php?id=<?php echo $T['id']; ?>" + data-preview="#tasks/<?php echo $T['id']; ?>/preview" + ><?php echo $number; ?></a></td> + <td align="center" nowrap><?php echo + Format::datetime($T[$date_col ?: 'created']); ?></td> + <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?> + href="tasks.php?id=<?php echo $T['id']; ?>"><?php + echo $title; ?></a> + <?php + if ($threadcount>1) + echo "<small>($threadcount)</small> ".'<i + class="icon-fixed-width icon-comments-alt"></i> '; + if ($T['collab_count']) + echo '<i class="icon-fixed-width icon-group faded"></i> '; + if ($T['attachment_count']) + echo '<i class="icon-fixed-width icon-paperclip"></i> '; + ?> + </td> + <td nowrap> <?php echo Format::truncate($dept, 40); ?></td> + <td nowrap> <?php echo $assignee; ?></td> + </tr> + <?php + } //end of foreach + if (!$total) + $ferror=__('There are no tickets matching your criteria.'); + ?> + </tbody> + <tfoot> + <tr> + <td colspan="6"> + <?php if($total && $thisstaff->canManageTickets()){ ?> + <?php echo __('Select');?>: + <a id="selectAll" href="#ckb"><?php echo __('All');?></a> + <a id="selectNone" href="#ckb"><?php echo __('None');?></a> + <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> + <?php }else{ + echo '<i>'; + echo $ferror?Format::htmlchars($ferror):__('Query returned 0 results.'); + echo '</i>'; + } ?> + </td> + </tr> + </tfoot> + </table> + <?php + if ($total>0) { //if we actually had any tickets returned. + echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' '; + echo sprintf('<a class="export-csv no-pjax" href="?%s">%s</a>', + Http::build_query(array( + 'a' => 'export', 'h' => $hash, + 'status' => $_REQUEST['status'])), + __('Export')); + echo ' <i class="help-tip icon-question-sign" href="#export"></i></div>'; + } ?> + </form> +</div> + +<div style="display:none;" class="dialog" id="confirm-action"> + <h3><?php echo __('Please Confirm');?></h3> + <a class="close" href=""><i class="icon-remove-circle"></i></a> + <hr/> + <p class="confirm-action" style="display:none;" id="mark_overdue-confirm"> + <?php echo __('Are you sure want to flag the selected tickets as <font color="red"><b>overdue</b></font>?');?> + </p> + <div><?php echo __('Please confirm to continue.');?></div> + <hr style="margin-top:1em"/> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="button" value="<?php echo __('No, Cancel');?>" class="close"> + </span> + <span class="buttons pull-right"> + <input type="button" value="<?php echo __('Yes, Do it!');?>" class="confirm"> + </span> + </p> + <div class="clear"></div> +</div> +<script type="text/javascript"> +$(function() { + $(document).off('.tasks'); + $(document).on('click.tasks', 'a.tasks-action', function(e) { + e.preventDefault(); + var count = checkbox_checker($('form#tasks'), 1); + if (count) { + var url = 'ajax.php/' + +$(this).attr('href').substr(1) + +'?count='+count + +'&_uid='+new Date().getTime(); + $.dialog(url, [201], function (xhr) { + $.pjax.reload('#pjax-container'); + }); + } + return false; + }); + $(document).off('.task-action'); + $(document).on('click.task-action', 'a.task-action', function(e) { + e.preventDefault(); + var url = 'ajax.php/' + +$(this).attr('href').substr(1) + +'?_uid='+new Date().getTime(); + var $options = $(this).data('dialog'); + var $redirect = $(this).data('redirect'); + $.dialog(url, [201], function (xhr) { + if ($redirect) + window.location.href = $redirect; + else + $.pjax.reload('#pjax-container'); + }, $options); + + return false; + }); +}); +</script> diff --git a/include/staff/templates/dynamic-form-simple.tmpl.php b/include/staff/templates/dynamic-form-simple.tmpl.php index cd53f0cd8950126c08f27e18f11f09eb7f92ce0e..8ec0cb2726a6c9b0b33c4d90c5a069938549e920 100644 --- a/include/staff/templates/dynamic-form-simple.tmpl.php +++ b/include/staff/templates/dynamic-form-simple.tmpl.php @@ -1,33 +1,43 @@ -<?php - echo $form->getMedia(); - foreach ($form->getFields() as $name=>$f) { ?> - <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id; - ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>> - <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>"> - <label for="<?php echo $f->getWidget()->name; ?>"> - <?php if ($f->get('label')) { ?> - <?php echo Format::htmlchars($f->get('label')); ?>: - <?php } ?> - <?php if ($f->get('required')) { ?> - <span class="error">*</span> - <?php } ?> - </label> - <?php - if ($f->get('hint')) { ?> - <br/><em style="color:gray;display:inline-block"><?php - echo Format::viewableImages($f->get('hint')); ?></em> - <?php - } ?> - </div><div> - <?php - $f->render(); - ?> - </div> +<div class="form-simple"> + <?php + echo $form->getMedia(); + foreach ($form->getFields() as $name=>$f) { ?> + <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id; + ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>> + <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>"> + <label for="<?php echo $f->getWidget()->name; ?>"> + <?php if ($f->get('label')) { ?> + <?php echo Format::htmlchars($f->get('label')); ?>: + <?php } ?> + <?php if ($f->get('required')) { ?> + <span class="error">*</span> + <?php } ?> + </label> + <?php + if ($f->get('hint')) { ?> + <em style="color:gray;display:block"><?php + echo Format::viewableImages($f->get('hint')); ?></em> + <?php + } ?> + </div><div> + <?php + $f->render($options); + ?> + </div> + <?php + if ($f->errors()) { ?> + <div id="field<?php echo $f->getWidget()->id; ?>_error"> <?php foreach ($f->errors() as $e) { ?> <div class="error"><?php echo $e; ?></div> - <?php } ?> + <?php + } ?> </div> <?php - } -?> + } ?> + </div> + <?php + } + $form->emitJavascript($options); + ?> +</div> diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php index 7fac7eb1209eb21ad978e27c937c02c24d165e1f..ef18c8bd01c5a988f063d80caea823da0f857ca1 100644 --- a/include/staff/templates/dynamic-form.tmpl.php +++ b/include/staff/templates/dynamic-form.tmpl.php @@ -65,7 +65,7 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?> <span class="error">*</span> <?php } - if (($a = $field->getAnswer()) && $a->isDeleted()) { + if ($field->isStorable() && ($a = $field->getAnswer()) && $a->isDeleted()) { ?><a class="action-button float-right danger overlay" title="Delete this data" href="#delete-answer" onclick="javascript:if (confirm('<?php echo __('You sure?'); ?>')) diff --git a/include/staff/templates/sub-navigation.tmpl.php b/include/staff/templates/sub-navigation.tmpl.php index 0cb5e6ed4737ebdfb67e08f80c716b665ddf0d6e..6e40d33c8274b9fd29bf85973c3dc968d17118c7 100644 --- a/include/staff/templates/sub-navigation.tmpl.php +++ b/include/staff/templates/sub-navigation.tmpl.php @@ -17,7 +17,13 @@ if(($subnav=$nav->getSubMenu()) && is_array($subnav)){ if (!($id=$item['id'])) $id="subnav$k"; - echo sprintf('<li><a class="%s" href="%s" title="%s" id="%s">%s</a></li>', - $class, $item['href'], $item['title'], $id, $item['desc']); + //Extra attributes + $attr = ''; + if ($item['attr']) + foreach ($item['attr'] as $name => $value) + $attr.= sprintf("%s='%s' ", $name, $value); + + echo sprintf('<li><a class="%s" href="%s" title="%s" id="%s" %s>%s</a></li>', + $class, $item['href'], $item['title'], $id, $attr, $item['desc']); } } diff --git a/include/staff/templates/task-assign.tmpl.php b/include/staff/templates/task-assign.tmpl.php index 0c5dd7a5eac411e9f78ab99e0096a0dd671187a8..7019fa49a2b1d12fdb03039921f0f65b7f9d367c 100644 --- a/include/staff/templates/task-assign.tmpl.php +++ b/include/staff/templates/task-assign.tmpl.php @@ -1,14 +1,13 @@ <?php global $cfg; -if (!$info['title']) - $info['title'] = sprintf(__('%s Tasks #%s'), - $task->isAssigned() ? __('Reassign') : __('Assign'), - $task->getNumber() - ); +$form = $form ?: AssignmentForm::instantiate($info); +if (!$info[':title']) + $info[':title'] = sprintf(__('%s Selected Tasks'), + __('Assign')); ?> -<h3><?php echo $info['title']; ?></h3> +<h3><?php echo $info[':title']; ?></h3> <b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> <div class="clear"></div> <hr/> @@ -25,17 +24,19 @@ if ($info['error']) { } -$action = $info['action'] ?: ('#tasks/'.$task->getId().'/assign'); +$action = $info[':action'] ?: ('#tasks/mass/assign'); ?> -<div id="ticket-status" style="display:block; margin:5px;"> -<form method="post" name="transfer" id="transfer" +<div style="display:block; margin:5px;"> +<form class="mass-action" method="post" + name="assign" + id="<?php echo $form->getId(); ?>" action="<?php echo $action; ?>"> <table width="100%"> <?php - if ($info['extra']) { + if ($info[':extra']) { ?> <tbody> - <tr><td colspan="2"><strong><?php echo $info['extra']; + <tr><td colspan="2"><strong><?php echo $info[':extra']; ?></strong></td> </tr> </tbody> <?php @@ -43,40 +44,12 @@ $action = $info['action'] ?: ('#tasks/'.$task->getId().'/assign'); ?> <tbody> <tr><td colspan=2> - <span> - <strong><?php echo __('Agent') ?>: </strong> - <select name="staff_id"> - <?php - foreach (Staff::getAvailableStaffMembers() as $id => $name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, - ($info['staff_id'] == $id) - ? 'selected="selected"' : '', - $name - ); - } - ?> - </select> - <font class="error">* <?php echo - $errors['dept_id']; ?></font> - </span> + <?php + $options = array('template' => 'simple', 'form_id' => 'assign'); + $form->render($options); + ?> </td> </tr> </tbody> - <tbody> - <tr> - <td colspan="2"> - <?php - $placeholder = $info['placeholder'] ?: __('Optional reason for the assignment'); - ?> - <textarea name="comments" id="comments" - cols="50" rows="3" wrap="soft" style="width:100%" - class="<?php if ($cfg->isHtmlThreadEnabled()) echo 'richtext'; - ?> no-bar" - placeholder="<?php echo $placeholder; ?>"><?php - echo $info['comments']; ?></textarea> - </td> - </tr> - </tbody> </table> <hr> <p class="full-width"> diff --git a/include/staff/templates/task-delete.tmpl.php b/include/staff/templates/task-delete.tmpl.php index 6014bd88aa6be37d1027d01cbb083ba6b4d03b1b..9a9191e4cc5134849fee8d60a5b4e0aa5d82c21a 100644 --- a/include/staff/templates/task-delete.tmpl.php +++ b/include/staff/templates/task-delete.tmpl.php @@ -1,14 +1,11 @@ <?php global $cfg; - -if (!$info['title']) - $info['title'] = sprintf(__('%s Tasks #%s'), +if (!$info[':title']) + $info[':title'] = sprintf(__('%s %s'), __('Delete'), - $task->getNumber() - ); - + _N('selected task', 'selected tasks', $count)); ?> -<h3><?php echo $info['title']; ?></h3> +<h3><?php echo $info[':title']; ?></h3> <b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> <div class="clear"></div> <hr/> @@ -25,17 +22,18 @@ if ($info['error']) { } -$action = $info['action'] ?: ('#tasks/'.$task->getId().'/delete'); +$action = $info[':action'] ?: ('#tasks/mass/delete'); ?> <div id="ticket-status" style="display:block; margin:5px;"> <form method="post" name="delete" id="delete" - action="<?php echo $action; ?>"> + action="<?php echo $action; ?>" + class="mass-action"> <table width="100%"> <?php - if ($info['extra']) { + if ($info[':extra']) { ?> <tbody> - <tr><td colspan="2"><strong><?php echo $info['extra']; + <tr><td colspan="2"><strong><?php echo $info[':extra']; ?></strong></td> </tr> </tbody> <?php @@ -45,7 +43,7 @@ $action = $info['action'] ?: ('#tasks/'.$task->getId().'/delete'); <tr> <td colspan="2"> <?php - $placeholder = $info['placeholder'] ?: __('Optional reason for the deletion'); + $placeholder = $info[':placeholder'] ?: __('Optional reason for the deletion'); ?> <textarea name="comments" id="comments" cols="50" rows="3" wrap="soft" style="width:100%" diff --git a/include/staff/templates/task-status.tmpl.php b/include/staff/templates/task-status.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b711e6f91c71f2e8b46a6d353ca5ef2e859d5e39 --- /dev/null +++ b/include/staff/templates/task-status.tmpl.php @@ -0,0 +1,101 @@ +<?php +global $cfg; + +if (!$info[':title']) + $info[':title'] = __('Change Tasks Status'); + +?> +<h3><?php echo $info[':title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<?php +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['warn']) { + echo sprintf('<p id="msg_warning">%s</p>', $info['warn']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} elseif ($info['notice']) { + echo sprintf('<p id="msg_info"><i class="icon-info-sign"></i> %s</p>', + $info['notice']); +} + +$action = $info[':action'] ?: ('#tasks/mass/'. $action); +?> +<div style="display:block; margin:5px;"> + <form method="post" name="status" id="status" + action="<?php echo $action; ?>" + class="mass-action"> + <table width="100%"> + <?php + if ($info[':extra']) { + ?> + <tbody> + <tr><td colspan="2"><strong><?php echo $info[':extra']; + ?></strong></td> </tr> + </tbody> + <?php + } + ?> + <tbody> + <tr> + <td colspan=2> + <span> + <strong><?php echo __('Status') ?>: </strong> + <select name="status"> + <?php + $statuses = array( + 'open' => __('Open'), + 'closed' => __('Closed')); + + if (!$info['status']) + echo '<option value=""> '. __('Select One') + .' </option>'; + foreach ($statuses as $k => $status) { + echo sprintf('<option value="%s" %s>%s</option>', + $k, + ($info['status'] == $k) + ? 'selected="selected"' : '', + $status + ); + } + ?> + </select> + <font class="error">* <?php echo + $errors['status']; ?></font> + </span> + </td> + </tr> + </tbody> + <tbody> + <tr> + <td colspan="2"> + <?php + $placeholder = $info[':placeholder'] ?: __('Optional reason for status change (internal note)'); + ?> + <textarea name="comments" id="comments" + cols="50" rows="3" wrap="soft" style="width:100%" + class="<?php if ($cfg->isHtmlThreadEnabled()) echo 'richtext'; + ?> no-bar" + placeholder="<?php echo $placeholder; ?>"><?php + echo $info['comments']; ?></textarea> + </td> + </tr> + </tbody> + </table> + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>"> + <input type="button" name="cancel" class="close" + value="<?php echo __('Cancel'); ?>"> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php + echo $verb ?: __('Submit'); ?>"> + </span> + </p> + </form> +</div> +<div class="clear"></div> diff --git a/include/staff/templates/task-transfer.tmpl.php b/include/staff/templates/task-transfer.tmpl.php index 8a872f43845ec328c92554f32125e10682173b89..a45311f7f991b4fba4e0522272401edde44c376b 100644 --- a/include/staff/templates/task-transfer.tmpl.php +++ b/include/staff/templates/task-transfer.tmpl.php @@ -1,12 +1,13 @@ <?php global $cfg; -if (!$info['title']) - $info['title'] = sprintf(__('%s Tasks #%s'), - __('Tranfer'), $task->getNumber()); +$form = $form ?: TransferForm::instantiate($info); +if (!$info[':title']) + $info[':title'] = sprintf(__('%s Selected Tasks'), + __('Tranfer')); ?> -<h3><?php echo $info['title']; ?></h3> +<h3><?php echo $info[':title']; ?></h3> <b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> <div class="clear"></div> <hr/> @@ -23,17 +24,18 @@ if ($info['error']) { } -$action = $info['action'] ?: ('#tasks/'.$task->getId().'/transfer'); +$action = $info[':action'] ?: ('#tasks/mass/transfer'); ?> <div style="display:block; margin:5px;"> <form method="post" name="transfer" id="transfer" + class="mass-action" action="<?php echo $action; ?>"> <table width="100%"> <?php - if ($info['extra']) { + if ($info[':extra']) { ?> <tbody> - <tr><td colspan="2"><strong><?php echo $info['extra']; + <tr><td colspan="2"><strong><?php echo $info[':extra']; ?></strong></td> </tr> </tbody> <?php @@ -41,40 +43,12 @@ $action = $info['action'] ?: ('#tasks/'.$task->getId().'/transfer'); ?> <tbody> <tr><td colspan=2> - <span> - <strong><?php echo __('Department') ?>: </strong> - <select name="dept_id"> - <?php - foreach (Dept::getDepartments() as $id => $name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, - ($info['dept_id'] == $id) - ? 'selected="selected"' : '', - $name - ); - } - ?> - </select> - <font class="error">* <?php echo - $errors['dept_id']; ?></font> - </span> + <?php + $options = array('template' => 'simple', 'form_id' => 'transfer'); + $form->render($options); + ?> </td> </tr> </tbody> - <tbody> - <tr> - <td colspan="2"> - <?php - $placeholder = $info['placeholder'] ?: __('Optional reason for the transfer'); - ?> - <textarea name="comments" id="comments" - cols="50" rows="3" wrap="soft" style="width:100%" - class="<?php if ($cfg->isHtmlThreadEnabled()) echo 'richtext'; - ?> no-bar" - placeholder="<?php echo $placeholder; ?>"><?php - echo $info['comments']; ?></textarea> - </td> - </tr> - </tbody> </table> <hr> <p class="full-width"> diff --git a/include/staff/templates/task-view.tmpl.php b/include/staff/templates/task-view.tmpl.php index a9a4c72170780e61cd2edd7ada25a4ab30a6ab3b..7cd928b8a15e05921fe7e612fffd2e477fde94a2 100644 --- a/include/staff/templates/task-view.tmpl.php +++ b/include/staff/templates/task-view.tmpl.php @@ -1,50 +1,60 @@ <?php if (!defined('OSTSCPINC') - || !$thisstaff - || !$task - || !($role = $thisstaff->getRole($task->getId()))) + || !$thisstaff || !$task + || !($role = $thisstaff->getRole($task->getDeptId()))) die('Invalid path'); - $actions = array(); +$actions += array( + 'print' => array( + 'href' => sprintf('tasks.php?id=%d&a=print', $task->getId()), + 'class' => 'none', + 'icon' => 'icon-print', + 'label' => __('Print') + )); + if ($role->hasPerm(Task::PERM_EDIT)) { $actions += array( 'edit' => array( + 'href' => sprintf('#tasks/%d/edit', $task->getId()), 'icon' => 'icon-edit', 'dialog' => '{"size":"large"}', - 'action' => __('Edit') + 'label' => __('Edit') )); } if ($role->hasPerm(Task::PERM_ASSIGN)) { $actions += array( 'assign' => array( + 'href' => sprintf('#tasks/%d/assign', $task->getId()), 'icon' => 'icon-user', - 'action' => $task->isAssigned() ? __('Reassign') : __('Assign') + 'label' => $task->isAssigned() ? __('Reassign') : __('Assign') )); } if ($role->hasPerm(Task::PERM_TRANSFER)) { $actions += array( 'transfer' => array( + 'href' => sprintf('#tasks/%d/transfer', $task->getId()), 'icon' => 'icon-share', - 'action' => __('Transfer') + 'label' => __('Transfer') )); } + if ($role->hasPerm(Task::PERM_DELETE)) { $actions += array( 'delete' => array( + 'href' => sprintf('#tasks/%d/delete', $task->getId()), 'icon' => 'icon-trash', - 'action' => __('Delete') + 'label' => __('Delete') )); } - $info=($_POST && $errors)?Format::input($_POST):array(); -$id = $task->getId(); //Ticket ID. +$id = $task->getId(); if ($task->isOverdue()) $warn.=' <span class="Icon overdueTicket">'.__('Marked overdue!').'</span>'; @@ -52,214 +62,198 @@ if ($task->isOverdue()) <table width="940" cellpadding="2" cellspacing="0" border="0"> <tr> <td width="20%" class="has_bottom_border"> - <h3><a href="#tasks/<?php echo $task->getId(); ?>/view" - id="reload-task"><i class="icon-refresh"></i> <?php + <h2><a + id="reload-task" + href="tasks.php?id=<?php echo $task->getId(); ?>" + <?php + if ($ticket) { + echo ' class="preview" '; + echo sprintf('data-preview="#tasks/%d/preview" ', $task->getId()); + echo sprintf('href="#tasks/%d" ', $task->getId()); + } else { ?> + href="tasks.php?id=<?php echo $task->getId(); ?>" + <?php + } ?> + ><i class="icon-refresh"></i> <?php echo sprintf(__('Task #%s'), $task->getNumber()); ?></a> - </h3> + </h2> </td> <td width="auto" class="flush-right has_bottom_border"> - <?php - if ($actions) { ?> + <?php + if ($ticket) { ?> <span class="action-button" - data-dropdown="#action-dropdown-taskoptions"> + data-dropdown="#action-dropdown-task-options"> <i class="icon-caret-down pull-right"></i> <a class="task-action" - href="#taskoptions"><i + href="#task-options"><i class="icon-reorder"></i> <?php - echo __('Task Options'); ?></a> + echo __('Options'); ?></a> </span> - <div id="action-dropdown-taskoptions" + <div id="action-dropdown-task-options" class="action-dropdown anchor-right"> <ul> <?php foreach ($actions as $a => $action) { ?> <li> - <a class="no-pjax task-action" + <a class="no-pjax <?php + echo $action['class'] ?: 'task-action'; ?>" <?php if ($action['dialog']) echo sprintf("data-dialog='%s'", $action['dialog']); + if ($action['redirect']) + echo sprintf("data-redirect='%s'", $action['redirect']); ?> - href="<?php - echo sprintf('#tasks/%d/%s', $task->getId(), $a); ?>" + href="<?php echo $action['href']; ?>" ><i class="<?php echo $action['icon'] ?: 'icon-tag'; ?>"></i> <?php - echo $action['action']; ?></a> + echo $action['label']; ?></a> </li> <?php } ?> </ul> </div> <?php - } ?> - </td> - </tr> -</table> -<table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> - <tr> - <td width="50%"> - <table border="0" cellspacing="" cellpadding="4" width="100%"> - <tr> - <th width="100"><?php echo __('Status');?>:</th> - <td><?php echo $task->getStatus(); ?></td> - </tr> - <tr> - <th><?php echo __('Department');?>:</th> - <td><?php echo Format::htmlchars($task->dept->getName()); ?></td> - </tr> - <tr> - <th><?php echo __('Create Date');?>:</th> - <td><?php echo Format::datetime($task->getCreateDate()); ?></td> - </tr> - </table> - </td> - <td width="50%" style="vertical-align:top"> - <table cellspacing="0" cellpadding="4" width="100%" border="0"> - <?php - if ($task->isOpen()) { ?> - <tr> - <th width="100"><?php echo __('Assigned To');?>:</th> - <td> - <?php - if ($assigned=$task->getAssigned()) - echo Format::htmlchars($assigned); - else - echo '<span class="faded">— '.__('Unassigned').' —</span>'; - ?> - </td> - </tr> - <?php - } else { ?> - <tr> - <th width="100"><?php echo __('Closed By');?>:</th> - <td> - <?php - if (0 && ($staff = $task->getStaff())) - echo Format::htmlchars($staff->getName()); - else - echo '<span class="faded">— '.__('Unknown').' —</span>'; - ?> - </td> - </tr> - <?php - } ?> - <tr> - <th><?php echo __('SLA Plan');?>:</th> - <td><?php echo $sla?Format::htmlchars($sla->getName()):'<span class="faded">— '.__('None').' —</span>'; ?></td> - </tr> - <?php - if($task->isOpen()){ ?> - <tr> - <th><?php echo __('Due Date');?>:</th> - <td><?php echo $task->duedate ? - Format::datetime($task->duedate) : '<span - class="faded">— '.__('None').' —</span>'; ?></td> - </tr> - <?php - }else { ?> - <tr> - <th><?php echo __('Close Date');?>:</th> - <td><?php echo 0 ? - Format::datetime($task->getCloseDate()) : ''; ?></td> - </tr> - <?php + } else { + foreach ($actions as $action) {?> + <a class="action-button <?php + echo $action['class'] ?: 'task-action'; ?>" + <?php + if ($action['dialog']) + echo sprintf("data-dialog='%s'", $action['dialog']); + ?> + href="<?php echo $action['href']; ?>"><i + class="<?php + echo $action['icon'] ?: 'icon-tag'; ?>"></i> <?php + echo $action['label']; + ?></a> + <?php } - ?> - </table> + } ?> </td> </tr> </table> -<br> -<br> -<table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> + <?php -$idx = 0; -foreach (DynamicFormEntry::forObject($task->getId(), - ObjectModel::OBJECT_TYPE_TASK) as $form) { - $answers = array_filter($form->getAnswers(), function ($a) { - return $a->getField()->isStorable(); - }); - if (count($answers) == 0) - continue; - ?> +if (!$ticket) { ?> + <table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> <tr> - <td colspan="2"> - <table cellspacing="0" cellpadding="4" width="100%" border="0"> - <?php foreach($answers as $a) { - if (!($v = $a->display())) continue; ?> - <tr> - <th width="100"><?php - echo $a->getField()->get('label'); - ?>:</th> - <td><?php - echo $v; - ?></td> - </tr> - <?php - } ?> - </table> - </td> + <td width="50%"> + <table border="0" cellspacing="" cellpadding="4" width="100%"> + <tr> + <th width="100"><?php echo __('Status');?>:</th> + <td><?php echo $task->getStatus(); ?></td> + </tr> + <tr> + <th><?php echo __('Department');?>:</th> + <td><?php echo Format::htmlchars($task->dept->getName()); ?></td> + </tr> + <?php + if ($task->isOpen()) { ?> + <tr> + <th width="100"><?php echo __('Assigned To');?>:</th> + <td> + <?php + if ($assigned=$task->getAssigned()) + echo Format::htmlchars($assigned); + else + echo '<span class="faded">— '.__('Unassigned').' —</span>'; + ?> + </td> + </tr> + <?php + } else { ?> + <tr> + <th width="100"><?php echo __('Closed By');?>:</th> + <td> + <?php + if (0 && ($staff = $task->getStaff())) + echo Format::htmlchars($staff->getName()); + else + echo '<span class="faded">— '.__('Unknown').' —</span>'; + ?> + </td> + </tr> + <?php + } ?> + </table> + </td> + <td width="50%" style="vertical-align:top"> + <table cellspacing="0" cellpadding="4" width="100%" border="0"> + <tr> + <th><?php echo __('SLA Plan');?>:</th> + <td><?php echo $sla?Format::htmlchars($sla->getName()):'<span class="faded">— '.__('None').' —</span>'; ?></td> + </tr> + <tr> + <th><?php echo __('Create Date');?>:</th> + <td><?php echo Format::datetime($task->getCreateDate()); ?></td> + </tr> + <?php + if($task->isOpen()){ ?> + <tr> + <th><?php echo __('Due Date');?>:</th> + <td><?php echo $task->duedate ? + Format::datetime($task->duedate) : '<span + class="faded">— '.__('None').' —</span>'; ?></td> + </tr> + <?php + }else { ?> + <tr> + <th><?php echo __('Close Date');?>:</th> + <td><?php echo 0 ? + Format::datetime($task->getCloseDate()) : ''; ?></td> + </tr> + <?php + } + ?> + </table> + </td> </tr> + </table> + <br> + <br> + <table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> <?php - $idx++; + $idx = 0; + foreach (DynamicFormEntry::forObject($task->getId(), + ObjectModel::OBJECT_TYPE_TASK) as $form) { + $answers = $form->getAnswers()->exclude(Q::any(array( + 'field__flags__hasbit' => DynamicFormField::FLAG_EXT_STORED + ))); + if (!$answers || count($answers) == 0) + continue; + + ?> + <tr> + <td colspan="2"> + <table cellspacing="0" cellpadding="4" width="100%" border="0"> + <?php foreach($answers as $a) { + if (!($v = $a->display())) continue; ?> + <tr> + <th width="100"><?php + echo $a->getField()->get('label'); + ?>:</th> + <td><?php + echo $v; + ?></td> + </tr> + <?php + } ?> + </table> + </td> + </tr> + <?php + $idx++; + } ?> + </table> +<?php } ?> -</table> <div class="clear"></div> <div id="task_thread_container"> - <div id="task_thread_content"> + <div id="task_thread_content" data-thread-id="<?php echo + $task->getThread()->getId(); ?>" class="tab_content"> <?php - $threadTypes=array('M'=>'message','R'=>'response', 'N'=>'note'); - /* -------- Messages & Responses & Notes (if inline)-------------*/ - $types = array('M', 'R', 'N'); - if(($thread=$task->getThreadEntries($types))) { - foreach($thread as $entry) { ?> - <table class="thread-entry <?php echo $threadTypes[$entry->type]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> - <tr> - <th colspan="4" width="100%"> - <div> - <span class="pull-left"> - <span style="display:inline-block"><?php - echo Format::datetime($entry->created);?></span> - <span style="display:inline-block;padding:0 1em" class="faded title"><?php - echo Format::truncate($entry->title, 100); ?></span> - </span> - <span class="pull-right" style="white-space:no-wrap;display:inline-block"> - <span style="vertical-align:middle;" class="textra"></span> - <span style="vertical-align:middle;" - class="tmeta faded title"><?php - echo Format::htmlchars($entry->getName()); ?></span> - </span> - </div> - </th> - </tr> - <tr><td colspan="4" class="thread-body" id="thread-id-<?php - echo $entry->getId(); ?>"><div><?php - echo $entry->getBody()->toHtml(); ?></div></td></tr> - <?php - $urls = null; - if($entry->has_attachments - && ($urls = $tentry->getAttachmentUrls()) - && ($links = $tentry->getAttachmentsLinks())) {?> - <tr> - <td class="info" colspan="4"><?php echo $links; ?></td> - </tr> <?php - } - if ($urls) { ?> - <script type="text/javascript"> - $('#thread-id-<?php echo $entry->getId(); ?>') - .data('urls', <?php - echo JsonDataEncoder::encode($urls); ?>) - .data('id', <?php echo $entry['id']; ?>); - </script> -<?php - } ?> - </table> - <?php - if ($entry->type == 'M') - $msgId = $entry->getId(); - } - } else { - echo '<p>'.__('Error fetching thread - get technical help.').'</p>'; - }?> + $task->getThread()->render(array('M', 'R', 'N')); + ?> </div> </div> <div class="clear" style="padding-bottom:10px;"></div> @@ -269,11 +263,18 @@ foreach (DynamicFormEntry::forObject($task->getId(), <div id="msg_notice"><?php echo $msg; ?></div> <?php }elseif($warn) { ?> <div id="msg_warning"><?php echo $warn; ?></div> -<?php } ?> +<?php } + +if ($ticket) + $action = sprintf('#tickets/%d/tasks/%d', + $ticket->getId(), $task->getId()); +else + $action = 'tasks.php?id='.$task->getId(); +?> <div id="response_options"> <ul class="tabs"></ul> - <form id="task_note" - action="#tasks/<?php echo $task->getId(); ?>" + <form id="<?php echo $ticket? 'ticket_task_note': 'task_note'; ?>" + action="<?php echo $action; ?>" name="task_note" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> @@ -301,8 +302,8 @@ foreach (DynamicFormEntry::forObject($task->getId(), ?></textarea> <div class="attachments"> <?php - if ($task_note_form) - print $task_note_form->getField('attachments')->render(); + if ($note_form) + print $note_form->getField('attachments')->render(); ?> </div> </td> @@ -333,6 +334,7 @@ foreach (DynamicFormEntry::forObject($task->getId(), </p> </form> </div> + <script type="text/javascript"> $(function() { $(document).on('click', 'li.active a#ticket_tasks', function(e) { @@ -341,8 +343,27 @@ $(function() { $('div#tasks_content').show(); return false; }); + + $(document).off('.task-action'); + $(document).on('click.task-action', 'a.task-action', function(e) { + e.preventDefault(); + var url = 'ajax.php/' + +$(this).attr('href').substr(1) + +'?_uid='+new Date().getTime(); + var $options = $(this).data('dialog'); + var $redirect = $(this).data('redirect'); + $.dialog(url, [201], function (xhr) { + if ($redirect) + window.location.href = $redirect; + else + $.pjax.reload('#pjax-container'); + }, $options); + + return false; + }); + $(document).off('.tf'); - $(document).on('submit.tf', 'form#task_note', function(e) { + $(document).on('submit.tf', 'form#ticket_task_note', function(e) { e.preventDefault(); var $form = $(this); var $container = $('div#task_content'); @@ -360,6 +381,6 @@ $(function() { }) .done(function() { }) .fail(function() { }); - }); + }); }); </script> diff --git a/include/staff/templates/task.tmpl.php b/include/staff/templates/task.tmpl.php index bf1355bc0ac77b94c672f322eee7e1389674ccf4..d92da014f9f53e7367f67daecbfe3f41b517c45b 100644 --- a/include/staff/templates/task.tmpl.php +++ b/include/staff/templates/task.tmpl.php @@ -3,6 +3,10 @@ if (!$info['title']) $info['title'] = __('New Task'); +$namespace = 'task.add'; +if ($ticket) + $namespace = sprintf('ticket.%d.task', $ticket->getId()); + ?> <div id="task-form"> <h3><?php echo $info['title']; ?></h3> @@ -24,9 +28,7 @@ if ($info['error']) { $form = $form ?: TaskForm::getInstance(); $form->render(true, __('Create New Task'), - array( - 'draft-namespace' => sprintf('ticket.%d.task', - $ticket->getId())) + array('draft-namespace' => $namespace) ); ?> <tr><th colspan=2><em><?php diff --git a/include/staff/templates/tasks-actions.tmpl.php b/include/staff/templates/tasks-actions.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..306cd6ea9ab29c390f4dd01c3bca570890b7a2fe --- /dev/null +++ b/include/staff/templates/tasks-actions.tmpl.php @@ -0,0 +1,139 @@ +<?php +// Tasks' mass actions based on logged in agent + +$actions = array(); + +if ($agent->hasPerm(Task::PERM_CLOSE)) { + + if (isset($options['status'])) { + $status = $options['status']; + ?> + <span + class="action-button" + data-dropdown="#action-dropdown-tasks-status"> + <i class="icon-caret-down pull-right"></i> + <a class="tasks-status-action" + href="#statuses"><i + class="icon-flag"></i> <?php + echo __('Change Status'); ?></a> + </span> + <div id="action-dropdown-tasks-status" + class="action-dropdown anchor-right"> + <ul> + <?php + if (!$status || !strcasecmp($status, 'closed')) { ?> + <li> + <a class="no-pjax tasks-action" + href="#tasks/mass/reopen"><i + class="icon-fixed-width icon-undo"></i> <?php + echo __('Reopen Tasks');?> </a> + </li> + <?php + } + if (!$status || !strcasecmp($status, 'open')) { + ?> + <li> + <a class="no-pjax tasks-action" + href="#tasks/mass/close"><i + class="icon-fixed-width icon-ok-circle"></i> <?php + echo __('Close Tasks');?> </a> + </li> + <?php + } ?> + </ul> + </div> +<?php + } else { + + $actions += array( + 'reopen' => array( + 'icon' => 'icon-undo', + 'action' => __('Reopen Tasks') + )); + + $actions += array( + 'close' => array( + 'icon' => 'icon-ok-circle', + 'action' => __('Close Tasks') + )); + } +} + +if ($agent->hasPerm(Task::PERM_ASSIGN)) { + $actions += array( + 'assign' => array( + 'icon' => 'icon-user', + 'action' => __('Assign Tasks') + )); +} + +if ($agent->hasPerm(Task::PERM_TRANSFER)) { + $actions += array( + 'transfer' => array( + 'icon' => 'icon-share', + 'action' => __('Transfer Tasks') + )); +} + +if ($agent->hasPerm(Task::PERM_DELETE)) { + $actions += array( + 'delete' => array( + 'icon' => 'icon-trash', + 'action' => __('Delete Tasks') + )); +} +if ($actions) { + $more = $options['morelabel'] ?: __('More'); + ?> + <span + class="action-button" + data-dropdown="#action-dropdown-moreoptions"> + <i class="icon-caret-down pull-right"></i> + <a class="tasks-action" + href="#moreoptions"><i + class="icon-reorder"></i> <?php + echo $more; ?></a> + </span> + <div id="action-dropdown-moreoptions" + class="action-dropdown anchor-right"> + <ul> + <?php foreach ($actions as $a => $action) { ?> + <li> + <a class="no-pjax tasks-action" + <?php + if ($action['dialog']) + echo sprintf("data-dialog='%s'", $action['dialog']); + if ($action['redirect']) + echo sprintf("data-redirect='%s'", $action['redirect']); + ?> + href="<?php + echo sprintf('#tasks/mass/%s', $a); ?>" + ><i class="icon-fixed-width <?php + echo $action['icon'] ?: 'icon-tag'; ?>"></i> <?php + echo $action['action']; ?></a> + </li> + <?php + } ?> + </ul> + </div> + <?php + } ?> +<script type="text/javascript"> +$(function() { + $(document).off('.tasks'); + $(document).on('click.tasks', 'a.tasks-action', function(e) { + e.preventDefault(); + var count = checkbox_checker($('form#tasks'), 1); + if (count) { + var url = 'ajax.php/' + +$(this).attr('href').substr(1) + +'?count='+count + +'&_uid='+new Date().getTime(); + $.dialog(url, [201], function (xhr) { + $.pjax.reload('#pjax-container'); + }); + } + return false; + }); +}); +</script> diff --git a/include/staff/templates/thread-entries.tmpl.php b/include/staff/templates/thread-entries.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..821c1c50ca4416860e4350507fa730b744d6a2e9 --- /dev/null +++ b/include/staff/templates/thread-entries.tmpl.php @@ -0,0 +1,83 @@ +<?php +$entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note'); +if ($entries) { + foreach ($entries as $entry) { ?> + <table class="thread-entry <?php echo $entryTypes[$entry->type]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> + <tr> + <th colspan="4" width="100%"> + <div> + <span class="pull-left"> + <span style="display:inline-block"><?php + echo Format::datetime($entry->created);?></span> + <span style="display:inline-block;padding:0 1em;max-width: 500px" class="faded title truncate"><?php + echo $entry->title; ?></span> + </span> + <div class="pull-right"> +<?php if ($entry->hasActions()) { + $actions = $entry->getActions(); ?> + <span class="action-button pull-right" data-dropdown="#entry-action-more-<?php echo $entry->getId(); ?>"> + <i class="icon-caret-down"></i> + <span ><i class="icon-cog"></i></span> + </span> + <div id="entry-action-more-<?php echo $entry->getId(); ?>" class="action-dropdown anchor-right"> + <ul class="title"> +<?php foreach ($actions as $group => $list) { + foreach ($list as $id => $action) { ?> + <li> + <a class="no-pjax" href="#" onclick="javascript: + <?php echo str_replace('"', '\\"', $action->getJsStub()); ?>; return false;"> + <i class="<?php echo $action->getIcon(); ?>"></i> <?php + echo $action->getName(); + ?></a></li> +<?php } + } ?> + </ul> + </div> +<?php } ?> + <span style="vertical-align:middle"> + <span style="vertical-align:middle;" class="textra"></span> + <span style="vertical-align:middle;" + class="tmeta faded title"><?php + echo Format::htmlchars($entry->getName()); ?></span> + </span> + </div> + </th> + </tr> + <tr><td colspan="4" class="thread-body" id="thread-id-<?php + echo $entry->getId(); ?>"><div><?php + echo $entry->getBody()->toHtml(); ?></div></td></tr> + <?php + $urls = null; + if ($entry->has_attachments + && ($urls = $entry->getAttachmentUrls())) { ?> + <tr> + <td class="info" colspan="4"><?php + foreach ($entry->attachments as $A) { + if ($A->inline) continue; + $size = ''; + if ($A->file->size) + $size = sprintf('<em>(%s)</em>', + Format::file_size($A->file->size)); +?> + <a class="Icon file no-pjax" href="<?php echo $A->file->getDownloadUrl(); + ?>" target="_blank"><?php echo Format::htmlchars($A->file->name); + ?></a><?php echo $size;?> +<?php } ?> + </td> + </tr> <?php + } + if ($urls) { ?> + <script type="text/javascript"> + $('#thread-id-<?php echo $entry->getId(); ?>') + .data('urls', <?php + echo JsonDataEncoder::encode($urls); ?>) + .data('id', <?php echo $entry->getId(); ?>); + </script> +<?php + } ?> + </table> + <?php + } +} else { + echo '<p><em>'.__('No entries have been posted to this thread.').'</em></p>'; +}?> diff --git a/include/staff/ticket-tasks.inc.php b/include/staff/ticket-tasks.inc.php index 8b8f3fb9d7c13f601ad6c101b4d2f7a32dd0bb43..802ea24989c6da9017493a42e418291722395c4e 100644 --- a/include/staff/ticket-tasks.inc.php +++ b/include/staff/ticket-tasks.inc.php @@ -4,9 +4,12 @@ global $thisstaff; $role = $thisstaff->getRole($ticket->getDeptId()); $tasks = Task::objects() - ->select_related('dept', 'staff') + ->select_related('dept', 'staff', 'team') ->order_by('-created'); +$tasks->filter(array( + 'object_id' => $ticket->getId(), + 'object_type' => 'T')); $count = $tasks->count(); $pageNav = new Pagenate($count,1, 100000); //TODO: support ajax based pages @@ -14,7 +17,7 @@ $showing = $pageNav->showing().' '._N('task', 'tasks', $count); ?> <div id="tasks_content" style="display:block;"> -<div style="width:700px; float:left;"> +<div class="pull-left"> <?php if ($count) { echo '<strong>'.$showing.'</strong>'; @@ -24,24 +27,28 @@ $showing = $pageNav->showing().' '._N('task', 'tasks', $count); } ?> </div> -<div style="float:right;text-align:right;padding-right:5px;"> +<div class="pull-right"> <?php if ($role && $role->hasPerm(Task::PERM_CREATE)) { ?> <a - class="Icon newTicket task-action" + class="action-button ticket-task-action" data-url="tickets.php?id=<?php echo $ticket->getId(); ?>#tasks" data-dialog='{"size":"large"}' href="#tickets/<?php - echo $ticket->getId(); ?>/add-task"> <?php + echo $ticket->getId(); ?>/add-task"> + <i class="icon-plus-sign"></i> <?php print __('Add New Task'); ?></a> <?php - } ?> + } + Task::getAgentActions($thisstaff, array('morelabel' => __('Options'))); + ?> </div> <br/> <div> <?php if ($count) { ?> -<form action="#tickets/<?php echo $ticket->getId(); ?>/tasks" method="POST" name='tasks' style="padding-top:10px;"> +<form action="#tickets/<?php echo $ticket->getId(); ?>/tasks" method="POST" + name='tasks' id="tasks" style="padding-top:7px;"> <?php csrf_token(); ?> <input type="hidden" name="a" value="mass_process" > <input type="hidden" name="do" id="action" value="" > @@ -49,8 +56,7 @@ if ($count) { ?> <thead> <tr> <?php - //TODO: support mass actions. - if (0) {?> + if (1) {?> <th width="8px"> </th> <?php } ?> @@ -76,21 +82,20 @@ if ($count) { ?> $title = Format::htmlchars(Format::truncate($task->getTitle(),40)); $threadcount = $task->getThread() ? $task->getThread()->getNumEntries() : 0; + + $viewhref = sprintf('#tickets/%d/tasks/%d/view', + $ticket->getId(), $id); + ?> <tr id="<?php echo $id; ?>"> - <?php - //Implement mass action....if need be. - if (0) { ?> <td align="center" class="nohover"> <input class="ckb" type="checkbox" name="tids[]" value="<?php echo $id; ?>" <?php echo $sel?'checked="checked"':''; ?>> </td> - <?php - } ?> <td align="center" nowrap> <a class="Icon no-pjax preview" title="<?php echo __('Preview Task'); ?>" - href="#tasks/<?php echo $id; ?>/view" + href="<?php echo $viewhref; ?>" data-preview="#tasks/<?php echo $id; ?>/preview" ><?php echo $task->getNumber(); ?></a></td> <td align="center" nowrap><?php echo @@ -98,7 +103,7 @@ if ($count) { ?> <td><?php echo $status; ?></td> <td><a <?php if ($flag) { ?> class="no-pjax" title="<?php echo ucfirst($flag); ?> Task" <?php } ?> - href="#tasks/<?php echo $id; ?>/view"><?php + href="<?php echo $viewhref; ?>"><?php echo $title; ?></a> <?php if ($threadcount>1) @@ -137,8 +142,9 @@ $(function() { }).show(); return false; }); - $(document).off('.task-action'); - $(document).on('click.task-action', 'a.task-action', function(e) { + // Ticket Tasks + $(document).off('.ticket-task-action'); + $(document).on('click.ticket-task-action', 'a.ticket-task-action', function(e) { e.preventDefault(); var url = 'ajax.php/' +$(this).attr('href').substr(1) diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index a0e843a0c47e46144ccc66caaf8911ddd758519f..9bc13c7aa0105d686938be9e9dba8dcfe44093c6 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -396,102 +396,8 @@ $tcount = $ticket->getThreadEntries($types)->count(); <div id="ticket_tabs_container"> <div id="ticket_thread" data-thread-id="<?php echo $ticket->getThread()->getId(); ?>" class="tab_content"> <?php - $threadTypes=array('M'=>'message','R'=>'response', 'N'=>'note'); - /* -------- Messages & Responses & Notes (if inline)-------------*/ - $types = array('M', 'R', 'N'); - if(($thread=$ticket->getThreadEntries($types))) { - foreach($thread as $entry) { ?> - <table class="thread-entry <?php echo $threadTypes[$entry->type]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> - <tr> - <th colspan="4" width="100%"> - <div> - <span class="pull-left"> - <span style="display:inline-block"><?php - echo Format::datetime($entry->created);?></span> - <span style="display:inline-block;padding:0 1em;max-width: 500px" class="faded title truncate"><?php - echo $entry->title; ?></span> - </span> - <div class="pull-right"> -<?php if ($entry->hasActions()) { - $actions = $entry->getActions(); ?> - <span class="action-button pull-right" data-dropdown="#entry-action-more-<?php echo $entry->getId(); ?>"> - <i class="icon-caret-down"></i> - <span ><i class="icon-cog"></i></span> - </span> - <div id="entry-action-more-<?php echo $entry->getId(); ?>" class="action-dropdown anchor-right"> - <ul class="title"> -<?php foreach ($actions as $group => $list) { - foreach ($list as $id => $action) { ?> - <li> - <a class="no-pjax <?php - if (!$action->isEnabled()) - echo 'disabled'; - ?>" href="#" onclick="javascript: - if ($(this).hasClass('disabled')) return false; - <?php echo str_replace('"', '\\"', $action->getJsStub()); ?>; return false;"> - <i class="icon-fixed-width <?php echo $action->getIcon(); ?>"></i> <?php - echo $action->getName(); - ?></a></li> -<?php } - } ?> - </ul> - </div> -<?php } ?> - <span style="vertical-align:middle"> - <span style="vertical-align:middle;" class="textra"> - <?php if ($entry->flags & ThreadEntry::FLAG_EDITED) { ?> - <span class="label label-bare" title="<?php - echo sprintf(__('Edited on %s by %s'), Format::datetime($entry->updated), 'You'); - ?>"><?php echo __('Edited'); ?></span> - <?php } ?> - </span> - <span style="vertical-align:middle;" - class="tmeta faded title"><?php - echo Format::htmlchars($entry->getName()); ?></span> - </span> - </div> - </th> - </tr> - <tr><td colspan="4" class="thread-body" id="thread-id-<?php - echo $entry->getId(); ?>"><div><?php - echo $entry->getBody()->toHtml(); ?></div></td></tr> - <?php - $urls = null; - if ($entry->has_attachments - && ($urls = $entry->getAttachmentUrls())) { ?> - <tr> - <td class="info" colspan="4"><?php - foreach ($entry->attachments as $A) { - if ($A->inline) continue; - $size = ''; - if ($A->file->size) - $size = sprintf('<em>(%s)</em>', - Format::file_size($A->file->size)); -?> - <a class="Icon file no-pjax" href="<?php echo $A->file->getDownloadUrl(); - ?>" target="_blank"><?php echo Format::htmlchars($A->file->name); - ?></a><?php echo $size;?> -<?php } ?> - </td> - </tr> <?php - } - if ($urls) { ?> - <script type="text/javascript"> - $('#thread-id-<?php echo $entry->getId(); ?>') - .data('urls', <?php - echo JsonDataEncoder::encode($urls); ?>) - .data('id', <?php echo $entry->getId(); ?>); - </script> -<?php - } ?> - </table> - <?php - if ($entry->type == 'M') - $msgId = $entry->getId(); - } - } else { - echo '<p><em>'.__('No entries have been posted to this ticket.').'</em></p>'; - }?> + $ticket->getThread()->render(array('M', 'R', 'N')); + ?> <div class="clear" style="padding-bottom:10px;"></div> <?php if($errors['err']) { ?> <div id="msg_error"><?php echo $errors['err']; ?></div> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 8f6a86ce857528594c9e6ee2c607d2ecdcb5485c..bc10b928410dc25aeace52771f34910f7e5c5a35 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -390,11 +390,10 @@ $_SESSION[':Q:tickets'] = $orig_tickets; <tbody> <?php // Setup Subject field for display - $subject_field = TicketForm::objects()->one()->getField('subject'); + $subject_field = TicketForm::getInstance()->getField('subject'); $class = "row1"; $total=0; $ids=($errors && $_POST['tids'] && is_array($_POST['tids']))?$_POST['tids']:null; - $subject_field = TicketForm::objects()->one()->getField('subject'); foreach ($tickets as $T) { $total += 1; $tag=$T['staff_id']?'assigned':'openticket'; diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js index de2a898253b7910d6c8f583c3023717df1c0e640..23b1ee76a4aaed7305df84ed4f974294dbe53139 100644 --- a/js/redactor-osticket.js +++ b/js/redactor-osticket.js @@ -343,6 +343,10 @@ $(function() { $(document).on('pjax:start', cleanupRedactorElements); }); +$(document).on('focusout.redactor', 'div.redactor_richtext', function (e) { + $(this).siblings('textarea').trigger('change'); +}); + $(document).ajaxError(function(event, request, settings) { if (settings.url.indexOf('ajax.php/draft') != -1 && settings.type.toUpperCase() == 'POST') { diff --git a/scp/ajax.php b/scp/ajax.php index f34bcfb930f912ea70e6b4592324924ec62fd4a7..f6e2f36de5eb6a966eca31a4980cae32ea8406d9 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -159,6 +159,8 @@ $dispatcher = patterns('', url_post('^status/(?P<state>\w+)$', 'setSelectedTicketsStatus'), url_get('^(?P<tid>\d+)/tasks$', 'tasks'), url('^(?P<tid>\d+)/add-task$', 'addTask'), + url_get('^(?P<tid>\d+)/tasks/(?P<id>\d+)/view$', 'task'), + url_post('^(?P<tid>\d+)/tasks/(?P<id>\d+)$', 'task'), url_get('^lookup', 'lookup'), url('^search', patterns('ajax.search.php:SearchAjaxAPI', url_get('^$', 'getAdvancedSearchDialog'), @@ -182,7 +184,10 @@ $dispatcher = patterns('', url_get('^(?P<tid>\d+)/delete', 'delete'), url_post('^(?P<tid>\d+)/delete$', 'delete'), url_get('^(?P<tid>\d+)/view$', 'task'), - url_post('^(?P<tid>\d+)$', 'task') + url_post('^(?P<tid>\d+)$', 'task'), + url('^add$', 'add'), + url_get('^mass/(?P<action>[\w.]+)', 'massProcess'), + url_post('^mass/(?P<action>[\w.]+)', 'massProcess') )), url('^/collaborators/', patterns('ajax.tickets.php:TicketsAjaxAPI', url_get('^(?P<cid>\d+)/view$', 'viewCollaborator'), diff --git a/scp/canned.php b/scp/canned.php index d35ec00c6b58aa207b6cb68d1f6240f64d79764a..34edc39a069b8a2524de622f555b52b28d127d15 100644 --- a/scp/canned.php +++ b/scp/canned.php @@ -30,7 +30,7 @@ $canned=null; if($_REQUEST['id'] && !($canned=Canned::lookup($_REQUEST['id']))) $errors['err']=sprintf(__('%s: Unknown or invalid ID.'), __('canned response')); -$canned_form = new Form(array( +$canned_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'configuration'=>array('extensions'=>false, 'size'=>$cfg->getMaxFileSize()) diff --git a/scp/css/scp.css b/scp/css/scp.css index 279fb557b963772535531845d7744cf91e912bcb..0f7077ae1be1c33108f2ef77d6fd6f20283a7721 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2177,6 +2177,11 @@ td.indented { #dynamic-actions > tr > td { padding: 5px; } + .no-margin { margin: 0 !important; } + +.form-simple select, .form-simple input, .form-simple textarea { + margin-left: 0; +} diff --git a/scp/faq.php b/scp/faq.php index 03c5f160118ba06081f1e53b33afc867a84d9b10..09b0d6bcb04a0b1cb0c9cfca40786b4c7b26625d 100644 --- a/scp/faq.php +++ b/scp/faq.php @@ -46,7 +46,7 @@ if ($langs = $cfg->getSecondaryLanguages()) { } } -$faq_form = new Form($form_fields, $_POST); +$faq_form = new SimpleForm($form_fields, $_POST); if ($_POST) { $errors=array(); diff --git a/scp/js/scp.js b/scp/js/scp.js index aed3618a2be9c6bb4a99301f5e38303cafca4821..d3026828db81d44cd77c82bb6bfbe3623d59c058 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -326,7 +326,9 @@ var scp_prep = function() { $('.dialog').delegate('input.close, a.close', 'click', function(e) { e.preventDefault(); - $(this).parents('div.dialog') + var $dialog = $(this).parents('div.dialog'); + $dialog.off('blur.redactor'); + $dialog .hide() .removeAttr('style'); $.toggleOverlay(false); diff --git a/scp/settings.php b/scp/settings.php index 2fef525750d4ac1db73ccbdf6e576129f5ca5435..a0d0a8c2a8ddba53f215a076a1d1acdb29dfd8e8 100644 --- a/scp/settings.php +++ b/scp/settings.php @@ -21,6 +21,8 @@ $settingOptions=array( array(__('System Settings'), 'settings.system'), 'tickets' => array(__('Ticket Settings and Options'), 'settings.ticket'), + 'tasks' => + array(__('Task Settings and Options'), 'settings.tasks'), 'pages' => array(__('Site Pages'), 'settings.pages'), 'access' => diff --git a/scp/tasks.php b/scp/tasks.php new file mode 100644 index 0000000000000000000000000000000000000000..ab0996e8822a3370f33b8b8e8d64cb62ba297d9c --- /dev/null +++ b/scp/tasks.php @@ -0,0 +1,201 @@ +<?php +/************************************************************************* + tasks.php + + Copyright (c) 2006-2013 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: +**********************************************************************/ + +require('staff.inc.php'); +require_once(INCLUDE_DIR.'class.task.php'); + +$page = ''; +$task = null; //clean start. +if ($_REQUEST['id']) { + if (!($task=Task::lookup($_REQUEST['id']))) + $errors['err'] = sprintf(__('%s: Unknown or invalid ID.'), __('task')); + elseif (!$task->checkStaffPerm($thisstaff)) { + $errors['err'] = __('Access denied. Contact admin if you believe this is in error'); + $task = null; + } +} + +// Configure form for file uploads +$note_form = new SimpleForm(array( + 'attachments' => new FileUploadField(array('id'=>'attach', + 'name'=>'attach:note', + 'configuration' => array('extensions'=>''))) +)); + +//At this stage we know the access status. we can process the post. +if($_POST && !$errors): + + if ($task) { + //More coffee please. + $errors=array(); + $role = $thisstaff->getRole($task->getDeptId()); + switch(strtolower($_POST['a'])): + case 'postnote': /* Post Internal Note */ + $vars = $_POST; + $attachments = $note_form->getField('attachments')->getClean(); + $vars['cannedattachments'] = array_merge( + $vars['cannedattachments'] ?: array(), $attachments); + + $wasOpen = ($task->isOpen()); + if(($note=$task->postNote($vars, $errors, $thisstaff))) { + + $msg=__('Internal note posted successfully'); + // Clear attachment list + $note_form->setSource(array()); + $note_form->getField('attachments')->reset(); + + if($wasOpen && $task->isClosed()) + $task = null; //Going back to main listing. + else + // Ticket is still open -- clear draft for the note + Draft::deleteForNamespace('task.note.'.$task->getId(), + $thisstaff->getId()); + + } else { + + if(!$errors['err']) + $errors['err'] = __('Unable to post internal note - missing or invalid data.'); + + $errors['postnote'] = __('Unable to post the note. Correct the error(s) below and try again!'); + } + break; + default: + $errors['err']=__('Unknown action'); + endswitch; + } + if(!$errors) + $thisstaff->resetStats(); //We'll need to reflect any changes just made! +endif; + +/*... Quick stats ...*/ +$stats= $thisstaff->getTasksStats(); + +// Clear advanced search upon request +if (isset($_GET['clear_filter'])) + unset($_SESSION['advsearch']); + +//Navigation +$nav->setTabActive('tasks'); +$open_name = _P('queue-name', + /* This is the name of the open tasks queue */ + 'Open'); + +$nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']).')', + 'title'=>__('Open Tasks'), + 'href'=>'tasks.php?status=open', + 'iconclass'=>'Ticket'), + ((!$_REQUEST['status'] && !isset($_SESSION['advsearch'])) || $_REQUEST['status']=='open')); + +if ($stats['assigned']) { + + $nav->addSubMenu(array('desc'=>__('My Tasks').' ('.number_format($stats['assigned']).')', + 'title'=>__('Assigned Tasks'), + 'href'=>'tasks.php?status=assigned', + 'iconclass'=>'assignedTickets'), + ($_REQUEST['status']=='assigned')); +} + +if ($stats['overdue']) { + $nav->addSubMenu(array('desc'=>__('Overdue').' ('.number_format($stats['overdue']).')', + 'title'=>__('Stale Tasks'), + 'href'=>'tasks.php?status=overdue', + 'iconclass'=>'overdueTickets'), + ($_REQUEST['status']=='overdue')); + + if(!$sysnotice && $stats['overdue']>10) + $sysnotice=sprintf(__('%d overdue tasks!'), $stats['overdue']); +} + +if ($stats['closed']) { + $nav->addSubMenu(array('desc' => __('Completed').' ('.number_format($stats['closed']).')', + 'title'=>__('Completed Tasks'), + 'href'=>'tasks.php?status=closed', + 'iconclass'=>'closedTickets'), + ($_REQUEST['status']=='closed')); +} + +if (isset($_SESSION['advsearch'])) { + // XXX: De-duplicate and simplify this code + $search = SavedSearch::create(); + $form = $search->getFormFromSession('advsearch'); + $form->loadState($_SESSION['advsearch']); + $tasks = Task::objects(); + $tasks = $search->mangleQuerySet($tasks, $form); + $count = $tasks->count(); + $nav->addSubMenu(array('desc' => __('Search').' ('.number_format($count).')', + 'title'=>__('Advanced Task Search'), + 'href'=>'tasks.php?status=search', + 'iconclass'=>'Ticket'), + (!$_REQUEST['status'] || $_REQUEST['status']=='search')); +} + +if ($thisstaff->hasPerm(TaskModel::PERM_CREATE)) { + $nav->addSubMenu(array('desc'=>__('New Task'), + 'title'=> __('Open a New Task'), + 'href'=>'#tasks/add', + 'iconclass'=>'newTicket task-action', + 'id' => 'new-task', + 'attr' => array( + 'data-dialog' => '{"size":"large"}' + ) + ), + ($_REQUEST['a']=='open')); +} + + +$ost->addExtraHeader('<script type="text/javascript" src="js/ticket.js"></script>'); +$ost->addExtraHeader('<meta name="tip-namespace" content="tasks.queue" />', + "$('#content').data('tipNamespace', 'tasks.queue');"); + +if($task) { + $ost->setPageTitle(sprintf(__('Task #%s'),$task->getNumber())); + $nav->setActiveSubMenu(-1); + $inc = 'task-view.inc.php'; + if ($_REQUEST['a']=='edit' + && $task->checkStaffPerm($thisstaff, TaskModel::PERM_EDIT)) { + $inc = 'task-edit.inc.php'; + if (!$forms) $forms=DynamicFormEntry::forObject($task->getId(), 'A'); + // Auto add new fields to the entries + foreach ($forms as $f) $f->addMissingFields(); + } elseif($_REQUEST['a'] == 'print' && !$task->pdfExport($_REQUEST['psize'])) + $errors['err'] = __('Internal error: Unable to export the task to PDF for print.'); +} else { + $inc = 'tasks.inc.php'; + if ($_REQUEST['a']=='open' && + $thisstaff->hasPerm(Task::PERM_CREATE)) + $inc = 'task-open.inc.php'; + elseif($_REQUEST['a'] == 'export') { + $ts = strftime('%Y%m%d'); + if (!($query=$_SESSION[':Q:tasks'])) + $errors['err'] = __('Query token not found'); + elseif (!Export::saveTasks($query, "tasks-$ts.csv", 'csv')) + $errors['err'] = __('Internal error: Unable to dump query results'); + } + + //Clear active submenu on search with no status + if($_REQUEST['a']=='search' && !$_REQUEST['status']) + $nav->setActiveSubMenu(-1); + + //set refresh rate if the user has it configured + if(!$_POST && !$_REQUEST['a'] && ($min=$thisstaff->getRefreshRate())) { + $js = "clearTimeout(window.task_refresh); + window.task_refresh = setTimeout($.refreshTaskView," + .($min*60000).");"; + $ost->addExtraHeader('<script type="text/javascript">'.$js.'</script>', + $js); + } +} + +require_once(STAFFINC_DIR.'header.inc.php'); +require_once(STAFFINC_DIR.$inc); +require_once(STAFFINC_DIR.'footer.inc.php'); diff --git a/scp/tickets.php b/scp/tickets.php index 4d15d30dee7ce88608d9ab9991436433321fd126..c4a9591e25049e1a9c7292d089ce5a8c2a695ee4 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -40,12 +40,12 @@ if ($_REQUEST['uid']) $user = User::lookup($_REQUEST['uid']); // Configure form for file uploads -$response_form = new Form(array( +$response_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:response', 'configuration' => array('extensions'=>''))) )); -$note_form = new Form(array( +$note_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:note', 'configuration' => array('extensions'=>''))) diff --git a/setup/doc/forms.md b/setup/doc/forms.md index c3545096c5e6daaee3ff394eb4b490f9a5a84daa..f1520cd3712cb8093fbb83787027bc15db3b051c 100644 --- a/setup/doc/forms.md +++ b/setup/doc/forms.md @@ -16,7 +16,7 @@ constructor. The simplest way to create forms is to instanciate the Form instance directly: - $form = new Form(array( + $form = new SimpleForm(array( 'email' => new TextboxField(array('label'=>'Email Address')), ); @@ -34,7 +34,7 @@ the cleaned values from the form fields based on the data from the request. To create a class that defines the fields statically, one might write a trampoline constructor: - class UserForm extends Form { + class UserForm extends SimpleForm { function __construct() { $args = func_get_args(); $fields = array(