diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 6d726f5a189631861aa7986970284563bc52e573..fbb8425bdaab9f65da2b833974ab2f8f62e10bd8 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -405,31 +405,44 @@ class TicketsAjaxAPI extends AjaxController { } - function assign($tid, $to=null) { + function assign($tid, $target=null) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); - if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN)) + if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) + || !($form = $ticket->getAssignmentForm($_POST, + array('target' => $target)))) Http::response(403, __('Permission Denied')); $errors = array(); $info = array( ':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), - $ticket->isAssigned() ? __('Reassign') : __('Assign')), + sprintf('%s %s', + $ticket->isAssigned() ? + __('Reassign') : __('Assign'), + !strcasecmp($target, 'agents') ? + __('to an Agent') : __('to a Team') + )), ':action' => sprintf('#tickets/%d/assign%s', $ticket->getId(), - ($to ? "/$to": '')), + ($target ? "/$target": '')), ); + if ($ticket->isAssigned()) { - $info['notice'] = sprintf(__('%s is currently assigned to %s'), - __('Ticket'), - $ticket->getAssigned()); + if ($ticket->getStaffId() == $thisstaff->getId()) + $assigned = __('you'); + else + $assigned = $ticket->getAssigned(); + + $info['notice'] = sprintf(__('%s is currently assigned to <b>%s</b>'), + __('This ticket'), + Format::htmlchars($assigned) + ); } - $form = $ticket->getAssignmentForm($_POST); if ($_POST && $form->isValid()) { if ($ticket->assign($form, $errors)) { $_SESSION['::sysmsgs']['msg'] = sprintf( @@ -449,9 +462,69 @@ class TicketsAjaxAPI extends AjaxController { include STAFFINC_DIR . 'templates/assign.tmpl.php'; } - function massProcess($action) { + function claim($tid) { + global $thisstaff; + if (!($ticket=Ticket::lookup($tid))) + Http::response(404, __('No such ticket')); + + // Check for premissions and such + if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) + || !$ticket->isOpen() // Claim only open + || $ticket->getStaff() // cannot claim assigned ticket + || !($form = $ticket->getClaimForm($_POST))) + Http::response(403, __('Permission Denied')); + + $errors = array(); + $info = array( + ':title' => sprintf(__('Ticket #%s: %s'), + $ticket->getNumber(), + __('Claim')), + ':action' => sprintf('#tickets/%d/claim', + $ticket->getId()), + + ); + + if ($ticket->isAssigned()) { + if ($ticket->getStaffId() == $thisstaff->getId()) + $assigned = __('you'); + else + $assigneed = $ticket->getAssigned(); + + $info['error'] = sprintf(__('%s is currently assigned to <b>%s</b>'), + __('This ticket'), + $assigned); + } else { + $info['warn'] = sprintf(__('Are you sure you want to claim %s?'), + __('this ticket')); + } + + if ($_POST && $form->isValid()) { + if ($ticket->claim($form, $errors)) { + $_SESSION['::sysmsgs']['msg'] = sprintf( + __('%s successfully'), + sprintf( + __('%s assigned to %s'), + __('Ticket'), + __('you')) + ); + Http::response(201, $ticket->getId()); + } + + $form->addErrors($errors); + $info['error'] = $errors['err'] ?: __('Unable to claim ticket'); + } + + $verb = sprintf('%s, %s', __('Yes'), __('Claim')); + + include STAFFINC_DIR . 'templates/assign.tmpl.php'; + + } + + function massProcess($action, $w=null) { + global $thisstaff, $cfg; + $actions = array( 'transfer' => array( 'verbed' => __('transferred'), @@ -459,6 +532,9 @@ class TicketsAjaxAPI extends AjaxController { 'assign' => array( 'verbed' => __('assigned'), ), + 'claim' => array( + 'verbed' => __('assigned'), + ), 'delete' => array( 'verbed' => __('deleted'), ), @@ -486,12 +562,102 @@ class TicketsAjaxAPI extends AjaxController { $count = $_REQUEST['count']; } switch ($action) { + case 'claim': + $w = 'me'; case 'assign': $inc = 'assign.tmpl.php'; - $info[':action'] = '#tickets/mass/assign'; + $info[':action'] = "#tickets/mass/assign/$w"; $info[':title'] = sprintf('Assign %s', _N('selected ticket', 'selected tickets', $count)); + $form = AssignmentForm::instantiate($_POST); + + $assignCB = function($t, $f, $e) { + return $t->assign($f, $e); + }; + + $assignees = null; + switch ($w) { + case 'agents': + $depts = array(); + $tids = $_POST['tids'] ?: array_filter(explode(',', $_REQUEST['tids'])); + if ($tids) { + $tickets = TicketModel::objects() + ->distinct('dept_id') + ->filter(array('ticket_id__in' => $tids)); + + $depts = $tickets->values_flat('dept_id'); + } + $members = Staff::objects() + ->distinct('staff_id') + ->filter(array( + 'onvacation' => 0, + 'isactive' => 1, + ) + ); + + if ($depts) { + $members->filter(Q::any( array( + 'dept_id__in' => $depts, + Q::all(array( + 'dept_access__dept__id__in' => $depts, + Q::not(array('dept_access__dept__flags__hasbit' + => Dept::FLAG_ASSIGN_MEMBERS_ONLY)) + )) + ))); + } + + switch ($cfg->getAgentNameFormat()) { + case 'last': + case 'lastfirst': + case 'legal': + $members->order_by('lastname', 'firstname'); + break; + + default: + $members->order_by('firstname', 'lastname'); + } + + $prompt = __('Select an Agent'); + $assignees = array(); + foreach ($members as $member) + $assignees['s'.$member->getId()] = $member->getName(); + + if (!$assignees) + $info['warn'] = __('No agents available for assignment'); + break; + case 'teams': + $assignees = array(); + $prompt = __('Select a Team'); + foreach (Team::getActiveTeams() as $id => $name) + $assignees['t'.$id] = $name; + + if (!$assignees) + $info['warn'] = __('No teams available for assignment'); + break; + case 'me': + $info[':action'] = '#tickets/mass/claim'; + $info[':title'] = sprintf('Claim %s', + _N('selected ticket', 'selected tickets', $count)); + $info['warn'] = sprintf(__('Are you sure you want to claim %s?'), + _N('selected ticket', 'selected tickets', $count)); + $verb = sprintf('%s, %s', __('Yes'), __('Claim')); + $id = sprintf('s%s', $thisstaff->getId()); + $assignees = array($id => $thisstaff->getName()); + $vars = $_POST ?: array('assignee' => array($id)); + $form = ClaimForm::instantiate($vars); + $assignCB = function($t, $f, $e) { + return $t->claim($f, $e); + }; + break; + } + + if ($assignees != null) + $form->setAssignees($assignees); + + if ($prompt && ($f=$form->getField('assignee'))) + $f->configure('prompt', $prompt); + if ($_POST && $form->isValid()) { foreach ($_POST['tids'] as $tid) { if (($t=Ticket::lookup($tid)) @@ -499,7 +665,7 @@ class TicketsAjaxAPI extends AjaxController { // access and assign the task. && $t->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) // Do the assignment - && $t->assign($form, $e) + && $assignCB($t, $form, $e) ) $i++; } diff --git a/include/class.dept.php b/include/class.dept.php index 0f74f4cf8e0f02f1fcff3491ba4455ab7a0e0b7c..5e916a8167cafdf8e8a7c8e8e4bbe273bd5d2bfc 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -205,6 +205,17 @@ implements TemplateVariable { return $this->getMembers(array('available'=>1)); } + // Get members eligible members only + function getAssignees() { + + $members = clone $this->getAvailableMembers(); + // If restricted then filter to primary members ONLY! + if ($this->assignMembersOnly()) + $members->filter(array('dept_id' => $this->getId())); + + return $members; + } + function getMembersForAlerts() { if ($this->isGroupMembershipEnabled() == self::ALERTS_DISABLED) { // Disabled for this department diff --git a/include/class.forms.php b/include/class.forms.php index 7e092ad9a65c028d17f0e1da2abac5785074d14b..bacd0dd799683209bcb890780cf29d923b7cd919 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -2233,10 +2233,14 @@ class AssigneeField extends ChoiceField { return true; } + function setChoices($choices) { + $this->_choices = $choices; + } + function getChoices() { global $cfg; - if (!$this->_choices) { + if (!isset($this->_choices)) { $config = $this->getConfiguration(); $choices = array( __('Agents') => new ArrayObject(), @@ -2257,7 +2261,7 @@ class AssigneeField extends ChoiceField { next($choices); $T = current($choices); - if (($teams = Team::getTeams())) + if (($teams = Team::getActiveTeams())) foreach ($teams as $id => $name) $T['t'.$id] = $name; @@ -3239,7 +3243,9 @@ class ChoicesWidget extends Widget { } function emitComplexChoices($choices, $values=array(), $have_def=false, $def_key=null) { - foreach ($choices as $label => $group) { ?> + foreach ($choices as $label => $group) { + if (!count($group)) continue; + ?> <optgroup label="<?php echo $label; ?>"><?php foreach ($group as $key => $name) { if (!$have_def && $key == $def_key) @@ -3810,6 +3816,10 @@ class VisibilityConstraint { } function emitJavascript($field) { + + if (!$this->constraint->constraints) + return; + $func = 'recheck'; $form = $field->getForm(); ?> @@ -3851,6 +3861,12 @@ class VisibilityConstraint { * Determines if the field was visible when the form was submitted */ function isVisible($field) { + + // Assume initial visibility if constraint is not provided. + if (!$this->constraint->constraints) + return $this->initial == self::VISIBLE; + + return $this->compileQPhp($this->constraint, $field); } @@ -3942,14 +3958,8 @@ class AssignmentForm extends Form { static $id = 'assign'; var $_assignee = null; - var $_dept = null; + var $_assignees = 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() { @@ -3967,7 +3977,6 @@ class AssignmentForm extends Form { 'criteria' => array( 'available' => true, ), - 'dept' => $this->_dept ?: null, ), ) ), @@ -3985,26 +3994,41 @@ class AssignmentForm extends Form { ), ); + + if (isset($this->_assignees)) + $fields['assignee']->setChoices($this->_assignees); + + $this->setFields($fields); return $this->fields; } + function getField($name) { + + if (($fields = $this->getFields()) + && isset($fields[$name])) + return $fields[$name]; + } + function isValid() { - if (!parent::isValid()) + if (!parent::isValid() || !($f=$this->getField('assignee'))) return false; // Do additional assignment validation if (!($assignee = $this->getAssignee())) { - $this->getField('assignee')->addError( - __('Unknown assignee')); + $f->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') - ); + $f->addError(__('Agent is unavailable for assignment')); + } elseif ($assignee instanceof Team) { + // Make sure the team is active and has members + if (!$assignee->isActive()) + $f->addError(__('Team is disabled')); + elseif (!$assignee->getNumMembers()) + $f->addError(__('Team does not have members')); } return !$this->errors(); @@ -4025,6 +4049,15 @@ class AssignmentForm extends Form { include $inc; } + function setAssignees($assignees) { + $this->_assignees = $assignees; + $this->_fields = array(); + } + + function getAssignees() { + return $this->_assignees; + } + function getAssignee() { if (!isset($this->_assignee)) @@ -4033,11 +4066,45 @@ class AssignmentForm extends Form { return $this->_assignee; } - function assigneeCriteria() { - $dept = $this->id; - return function () use($dept) { - return array('dept_id' =>$dept); - }; + function getComments() { + return $this->getField('comments')->getClean(); + } +} + +class ClaimForm extends AssignmentForm { + + var $_fields; + + function setFields($fields) { + $this->_fields = $fields; + parent::setFields($fields); + } + + function getFields() { + + if ($this->_fields) + return $this->_fields; + + $fields = parent::getFields(); + + // Disable && hide assignee field selection + if (isset($fields['assignee'])) { + $visibility = new VisibilityConstraint( + new Q(array()), VisibilityConstraint::HIDDEN); + + $fields['assignee']->set('visibility', $visibility); + } + + // Change coments placeholder to reflect claim + if (isset($fields['comments'])) { + $fields['comments']->configure('placeholder', + __('Optional reason for the claim')); + } + + + $this->setFields($fields); + + return $this->fields; } } diff --git a/include/class.ticket.php b/include/class.ticket.php index 3fe355de2a1e8f807e59d3d447e5513626c526c3..b8e6b55828ccc48744fd19a67688b57a40c64d5c 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -852,12 +852,58 @@ implements RestrictedAccess, Threadable { function getAssignmentForm($source=null, $options=array()) { + $prompt = $assignee = ''; + // Possible assignees + $assignees = array(); + switch (strtolower($options['target'])) { + case 'agents': + $dept = $this->getDept(); + foreach ($dept->getAssignees() as $member) + $assignees['s'.$member->getId()] = $member; + + if (!$source && $this->isOpen() && $this->staff) + $assignee = sprintf('s%d', $this->staff->getId()); + $prompt = __('Select an Agent'); + break; + case 'teams': + if (($teams = Team::getActiveTeams())) + foreach ($teams as $id => $name) + $assignees['t'.$id] = $name; + + if (!$source && $this->isOpen() && $this->team) + $assignee = sprintf('s%d', $this->team->getId()); + $prompt = __('Select a Team'); + break; + } + + // Default to current assignee if source is not set if (!$source) - $source = array('assignee' => array($this->getAssigneeId())); + $source = array('assignee' => array($assignee)); + + $form = AssignmentForm::instantiate($source, $options); + + if ($assignees) + $form->setAssignees($assignees); + + if ($prompt && ($f=$form->getField('assignee'))) + $f->configure('prompt', $prompt); + + + return $form; + } - $options += array('dept' => $this->getDept()); + function getClaimForm($source=null, $options=array()) { + global $thisstaff; + + $id = sprintf('s%d', $thisstaff->getId()); + if(!$source) + $source = array('assignee' => array($id)); + + $form = ClaimForm::instantiate($source, $options); + $form->setAssignees(array($id => $thisstaff->getName())); + + return $form; - return AssignmentForm::instantiate($source, $options); } function getTransferForm($source=null) { @@ -2025,17 +2071,25 @@ implements RestrictedAccess, Threadable { return true; } - function claim() { + function claim(ClaimForm $form, &$errors) { global $thisstaff; - if (!$thisstaff || !$this->isOpen() || $this->isAssigned()) - return false; - $dept = $this->getDept(); - if ($dept->assignMembersOnly() && !$dept->isMember($thisstaff)) + $assignee = $form->getAssignee(); + if (!($assignee instanceof Staff) + || !$thisstaff + || $thisstaff->getId() != $assignee->getId()) { + $errors['err'] = __('Unknown assignee'); + } elseif (!$assignee->isAvailable()) { + $errors['err'] = __('Agent is unavailable for assignment'); + } elseif ($dept->assignMembersOnly() && !$dept->isMember($assignee)) { + $errors['err'] = __('Permission denied'); + } + + if ($errors) return false; - return $this->assignToStaff($thisstaff->getId(), null, false); + return $this->assignToStaff($assignee, $form->getComments(), false); } function assignToStaff($staff, $note, $alert=true) { @@ -2085,6 +2139,7 @@ implements RestrictedAccess, Threadable { $evd = array(); $assignee = $form->getAssignee(); if ($assignee instanceof Staff) { + $dept = $this->getDept(); if ($this->getStaffId() == $assignee->getId()) { $errors['assignee'] = sprintf(__('%s already assigned to %s'), __('Ticket'), @@ -2092,6 +2147,8 @@ implements RestrictedAccess, Threadable { ); } elseif(!$assignee->isAvailable()) { $errors['assignee'] = __('Agent is unavailable for assignment'); + } elseif ($dept->assignMembersOnly() && !$dept->isMember($assignee)) { + $errors['err'] = __('Permission denied'); } else { $this->staff_id = $assignee->getId(); if ($thisstaff && $thisstaff->getId() == $assignee->getId()) @@ -2118,9 +2175,7 @@ implements RestrictedAccess, Threadable { $this->logEvent('assigned', $evd); - $this->onAssign($assignee, - $form->getField('comments')->getClean(), - $alert); + $this->onAssign($assignee, $form->getComments(), $alert); return true; } @@ -3303,7 +3358,7 @@ implements RestrictedAccess, Threadable { // Assign ticket to staff or team (new ticket by staff) if ($vars['assignId']) { - $asnform = new AssignmentForm(array('assignee' => $vars['assignId'])); + $asnform = $ticket->AssignmentForm(array('assignee' => $vars['assignId'])); $ticket->assign($asnform, $vars['note']); } else { diff --git a/include/staff/templates/status-options.tmpl.php b/include/staff/templates/status-options.tmpl.php index 1f9918f26ca44c70df7cd72e2af925d598cc84fe..3b493f37927b9cbf608f0a698fa06a8ca5c6e469 100644 --- a/include/staff/templates/status-options.tmpl.php +++ b/include/staff/templates/status-options.tmpl.php @@ -34,12 +34,11 @@ if (!$nextStatuses) <span class="action-button" - data-dropdown="#action-dropdown-statuses"> + data-dropdown="#action-dropdown-statuses" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Change Status'); ?>"> <i class="icon-caret-down pull-right"></i> <a class="tickets-action" href="#statuses"><i - class="icon-flag"></i> <?php - echo __('Change Status'); ?></a> + class="icon-flag"></i></a> </span> <div id="action-dropdown-statuses" class="action-dropdown anchor-right"> diff --git a/include/staff/templates/tickets-actions.tmpl.php b/include/staff/templates/tickets-actions.tmpl.php index cdc741db90d6adcc8eee43e76b137bc783d264ef..0535a5af2832a7da0354b6fa53dc88373ff6f6ca 100644 --- a/include/staff/templates/tickets-actions.tmpl.php +++ b/include/staff/templates/tickets-actions.tmpl.php @@ -1,83 +1,77 @@ <?php // Tickets mass actions based on logged in agent +// Status change if ($agent->canManageTickets()) echo TicketStatus::status_options(); -$actions = array(); -if ($agent->hasPerm(Ticket::PERM_ASSIGN, false)) { - $actions += array( - 'assign' => array( - 'icon' => 'icon-user', - 'action' => __('Assign') - )); + +// Mass Claim/Assignment +if ($agent->hasPerm(Ticket::PERM_ASSIGN, false)) {?> +<span + class="action-button" data-placement="bottom" + data-dropdown="#action-dropdown-assign" data-toggle="tooltip" title=" <?php + echo __('Assign'); ?>"> + <i class="icon-caret-down pull-right"></i> + <a class="tickets-action" id="tickets-assign" + href="#tickets/mass/assign"><i class="icon-user"></i></a> +</span> +<div id="action-dropdown-assign" class="action-dropdown anchor-right"> + <ul> + <li><a class="no-pjax tickets-action" + href="#tickets/mass/claim"><i + class="icon-chevron-sign-down"></i> <?php echo __('Claim'); ?></a> + <li><a class="no-pjax tickets-action" + href="#tickets/mass/assign/agents"><i + class="icon-user"></i> <?php echo __('Agent'); ?></a> + <li><a class="no-pjax tickets-action" + href="#tickets/mass/assign/teams"><i + class="icon-group"></i> <?php echo __('Team'); ?></a> + </ul> +</div> +<?php } -if ($agent->hasPerm(Ticket::PERM_TRANSFER, false)) { - $actions += array( - 'transfer' => array( - 'icon' => 'icon-share', - 'action' => __('Transfer') - )); +// Mass Transfer +if ($agent->hasPerm(Ticket::PERM_TRANSFER, false)) {?> +<span class="action-button"> + <a class="tickets-action" id="tickets-transfer" data-placement="bottom" + data-toggle="tooltip" title="<?php echo __('Transfer'); ?>" + href="#tickets/mass/transfer"><i class="icon-share"></i></a> +</span> +<?php } -if ($agent->hasPerm(Ticket::PERM_DELETE, false)) { - $actions += array( - 'delete' => array( - 'class' => 'danger', - 'icon' => 'icon-trash', - 'action' => __('Delete') - )); + +// Mass Delete +if ($agent->hasPerm(Ticket::PERM_DELETE, false)) {?> +<span class="red button action-button"> + <a class="tickets-action" id="tickets-delete" data-placement="bottom" + data-toggle="tooltip" title="<?php echo __('Delete'); ?>" + href="#tickets/mass/delete"><i class="icon-trash"></i></a> +</span> +<?php } -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="tickets-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 <?php - if ($action['class']) - echo sprintf("class='%s'", $action['class']); ?> > - <a class="no-pjax tickets-action" - <?php - if ($action['dialog']) - echo sprintf("data-dialog-config='%s'", $action['dialog']); - if ($action['redirect']) - echo sprintf("data-redirect='%s'", $action['redirect']); - ?> - href="<?php - echo sprintf('#tickets/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('.tickets-actions'); - $(document).on('click.tickets-actions', 'a.tickets-action', function(e) { + + $(document).off('.tickets'); + $(document).on('click.tickets', 'a.tickets-action', function(e) { e.preventDefault(); - var count = checkbox_checker($('form#tickets'), 1); + var $form = $('form#tickets'); + var count = checkbox_checker($form, 1); if (count) { + var tids = $('.ckb:checked', $form).map(function() { + return this.value; + }).get(); var url = 'ajax.php/' +$(this).attr('href').substr(1) +'?count='+count + +'&tids='+tids.join(',') +'&_uid='+new Date().getTime(); + console.log(tids); $.dialog(url, [201], function (xhr) { $.pjax.reload('#pjax-container'); }); diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 156b2c323556a57f4de5771baae88a64eb123fc0..29a2d1fc1e490e094a7bfe27dd9fb4559ff0dc3d 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -60,46 +60,68 @@ if($ticket->isOverdue()) if ($thisstaff->hasPerm(Email::PERM_BANLIST) || $role->hasPerm(TicketModel::PERM_EDIT) || ($dept && $dept->isManager($thisstaff))) { ?> - <span class="action-button pull-right" data-dropdown="#action-dropdown-more"> + <span class="action-button pull-right" data-placement="bottom" data-dropdown="#action-dropdown-more" data-toggle="tooltip" title="<?php echo __('More');?>"> <i class="icon-caret-down pull-right"></i> - <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + <span ><i class="icon-cog"></i></span> </span> <?php } - // Status change options - echo TicketStatus::status_options(); if ($role->hasPerm(TicketModel::PERM_EDIT)) { ?> - <a class="action-button pull-right" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=edit"><i class="icon-edit"></i> <?php - echo __('Edit'); ?></a> + <span class="action-button pull-right"><a data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Edit'); ?>" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=edit"><i class="icon-edit"></i></a></span> <?php } ?> + <span class="action-button pull-right" data-placement="bottom" data-dropdown="#action-dropdown-print" data-toggle="tooltip" title="<?php echo __('Print'); ?>"> + <i class="icon-caret-down pull-right"></i> + <a id="ticket-print" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=print"><i class="icon-print"></i></a> + </span> + <div id="action-dropdown-print" class="action-dropdown anchor-right"> + <ul> + <li><a class="no-pjax" target="_blank" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=print¬es=0"><i + class="icon-file-alt"></i> <?php echo __('Ticket Thread'); ?></a> + <li><a class="no-pjax" target="_blank" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=print¬es=1"><i + class="icon-file-text-alt"></i> <?php echo __('Thread + Internal Notes'); ?></a> + </ul> + </div> <?php // Transfer if ($role->hasPerm(TicketModel::PERM_TRANSFER)) {?> - <a class="ticket-action action-button pull-right" id="ticket-transfer" + <span class="action-button pull-right"> + <a class="ticket-action" id="ticket-transfer" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Transfer'); ?>" data-redirect="tickets.php" - href="#tickets/<?php echo $ticket->getId(); ?>/transfer"><i class="icon-share"></i> <?php - echo __('Transfer'); ?></a> + href="#tickets/<?php echo $ticket->getId(); ?>/transfer"><i class="icon-share"></i></a> + </span> <?php } ?> <?php // Assign - if ($role->hasPerm(TicketModel::PERM_ASSIGN)) {?> - <span class="action-button pull-right" data-dropdown="#action-dropdown-assign"> + if ($ticket->isOpen() && $role->hasPerm(TicketModel::PERM_ASSIGN)) {?> + <span class="action-button pull-right" + data-dropdown="#action-dropdown-assign" + data-placement="bottom" + data-toggle="tooltip" + title=" <?php echo $ticket->isAssigned() ? __('Assign') : __('Reassign'); ?>" + > <i class="icon-caret-down pull-right"></i> <a class="ticket-action" id="ticket-assign" data-redirect="tickets.php" - href="#tickets/<?php echo $ticket->getId(); ?>/assign"><i class="icon-user"></i> <?php - echo $ticket->isAssigned() ? __('Assign') : __('Reassign'); ?></a> + href="#tickets/<?php echo $ticket->getId(); ?>/assign"><i class="icon-user"></i></a> </span> <div id="action-dropdown-assign" class="action-dropdown anchor-right"> <ul> + <?php + // Agent can claim team assigned ticket + if (!$ticket->getStaff() + && (!$dept->assignMembersOnly() + || $dept->isMember($thisstaff)) + ) { ?> <li><a class="no-pjax ticket-action" data-redirect="tickets.php" - href="#tickets/<?php echo $ticket->getId(); ?>/assign/<?php echo $thisstaff->getId(); ?>"><i + href="#tickets/<?php echo $ticket->getId(); ?>/claim"><i class="icon-chevron-sign-down"></i> <?php echo __('Claim'); ?></a> + <?php + } ?> <li><a class="no-pjax ticket-action" data-redirect="tickets.php" href="#tickets/<?php echo $ticket->getId(); ?>/assign/agents"><i @@ -112,19 +134,6 @@ if($ticket->isOverdue()) </div> <?php } ?> - <span class="action-button pull-right" data-dropdown="#action-dropdown-print"> - <i class="icon-caret-down pull-right"></i> - <a id="ticket-print" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=print"><i class="icon-print"></i> <?php - echo __('Print'); ?></a> - </span> - <div id="action-dropdown-print" class="action-dropdown anchor-right"> - <ul> - <li><a class="no-pjax" target="_blank" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=print¬es=0"><i - class="icon-file-alt"></i> <?php echo __('Ticket Thread'); ?></a> - <li><a class="no-pjax" target="_blank" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=print¬es=1"><i - class="icon-file-text-alt"></i> <?php echo __('Thread + Internal Notes'); ?></a> - </ul> - </div> <div id="action-dropdown-more" class="action-dropdown anchor-right"> <ul> <?php @@ -191,7 +200,20 @@ if($ticket->isOverdue()) ?> </ul> </div> - </div> + <?php + if ($role->hasPerm(TicketModel::PERM_REPLY)) { ?> + <a href="#post-reply" class="post-response action-button" + data-placement="bottom" data-toggle="tooltip" + title="<?php echo __('Post Reply'); ?>"><i class="icon-mail-reply"></i></a> + <?php + } ?> + <a href="#post-note" id="post-note" class="post-response action-button" + data-placement="bottom" data-toggle="tooltip" + title="<?php echo __('Post Internal Note'); ?>"><i class="icon-file-text"></i></a> + <?php // Status change options + echo TicketStatus::status_options(); + ?> + </div> <div class="flush-left"> <h2><a href="tickets.php?id=<?php echo $ticket->getId(); ?>" title="<?php echo __('Reload'); ?>"><i class="icon-refresh"></i> @@ -456,7 +478,8 @@ echo $v; $tcount = $ticket->getThreadEntries($types)->count(); ?> <ul class="tabs clean threads" id="ticket_tabs" > - <li class="active"><a href="#ticket_thread"><?php echo sprintf(__('Ticket Thread (%d)'), $tcount); ?></a></li> + <li class="active"><a id="ticket-thread-tab" href="#ticket_thread"><?php + echo sprintf(__('Ticket Thread (%d)'), $tcount); ?></a></li> <li><a id="ticket-tasks-tab" href="#tasks" data-url="<?php echo sprintf('#tickets/%d/tasks', $ticket->getId()); ?>"><?php @@ -468,6 +491,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); <div id="ticket_tabs_container"> <div id="ticket_thread" class="tab_content"> + <?php // Render ticket thread $ticket->getThread()->render( @@ -478,23 +502,31 @@ $tcount = $ticket->getThreadEntries($types)->count(); ); ?> <div class="clear"></div> -<?php if($errors['err']) { ?> - <div id="msg_error"><?php echo $errors['err']; ?></div> -<?php }elseif($msg) { ?> +<?php +if ($errors['err'] && isset($_POST['a'])) { + // Reflect errors back to the tab. + $errors[$_POST['a']] = $errors['err']; +} elseif($msg) { ?> <div id="msg_notice"><?php echo $msg; ?></div> -<?php }elseif($warn) { ?> +<?php +} elseif($warn) { ?> <div id="msg_warning"><?php echo $warn; ?></div> -<?php } ?> +<?php +} ?> <div class="sticky bar stop actions" id="response_options" > - <ul class="tabs"> + <ul class="tabs" id="response-tabs"> <?php if ($role->hasPerm(TicketModel::PERM_REPLY)) { ?> - <li class="active"><a href="#reply"><?php echo __('Post Reply');?></a></li> + <li class="active <?php + echo isset($errors['reply']) ? 'error' : ''; ?>"><a + href="#reply" id="post-reply-tab"><?php echo __('Post Reply');?></a></li> <?php } ?> - <li><a href="#note"><?php echo __('Post Internal Note');?></a></li> + <li><a href="#note" <?php + echo isset($errors['postnote']) ? 'class="error"' : ''; ?> + id="post-note-tab"><?php echo __('Post Internal Note');?></a></li> </ul> <?php if ($role->hasPerm(TicketModel::PERM_REPLY)) { ?> @@ -508,8 +540,12 @@ $tcount = $ticket->getThreadEntries($types)->count(); <input type="hidden" name="msgId" value="<?php echo $msgId; ?>"> <input type="hidden" name="a" value="reply"> <input type="hidden" name="lockCode" value="<?php echo $mylock ? $mylock->getCode() : ''; ?>"> - <span class="error"></span> <table style="width:100%" border="0" cellspacing="0" cellpadding="3"> + <?php + if ($errors['reply']) {?> + <tr><td width="120"> </td><td class="error"><?php echo $errors['reply']; ?> </td></tr> + <?php + }?> <tbody id="to_sec"> <tr> <td width="120"> @@ -896,5 +932,30 @@ $(function() { } }); }); + + // Post Reply or Note action buttons. + $('a.post-response').click(function (e) { + var $r = $('ul.tabs > li > a'+$(this).attr('href')+'-tab'); + if ($r.length) { + // Make sure ticket thread tab is visiable. + var $t = $('ul#ticket_tabs > li > a#ticket-thread-tab'); + if ($t.length && !$t.hasClass('active')) + $t.trigger('click'); + // Make the target response tab active. + if (!$r.hasClass('active')) + $r.trigger('click'); + + // Scroll to the response section. + var $stop = $(document).height(); + var $s = $('div#response_options'); + if ($s.length) + $stop = $s.offset().top-125 + + $('html, body').animate({scrollTop: $stop}, 'fast'); + } + + return false; + }); + }); </script> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 690f6c2066fefa03c4b2e78b7e39a6919b08f610..d015d1cfad8bbf536fb74291209f3eb01d34a602 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -586,21 +586,6 @@ return false;"> </div> <script type="text/javascript"> $(function() { - $(document).off('.tickets'); - $(document).on('click.tickets', 'a.tickets-action', function(e) { - e.preventDefault(); - var count = checkbox_checker($('form#tickets'), 1); - if (count) { - var url = 'ajax.php/' - +$(this).attr('href').substr(1) - +'?count='+count - +'&_uid='+new Date().getTime(); - $.dialog(url, [201], function (xhr) { - $.pjax({url: 'tickets.php', container: '#pjax-container'}); - }); - } - return false; - }); $('[data-toggle=tooltip]').tooltip(); }); </script> diff --git a/scp/ajax.php b/scp/ajax.php index 78682a36315846be8c87295a22d80d1ef521def6..8fcb6146f2f9e6d97bdc2567ac6a22c6259f2e6a 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -161,10 +161,10 @@ $dispatcher = patterns('', 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_get('^mass/(?P<action>[\w.]+)', 'massProcess'), - url_post('^mass/(?P<action>[\w.]+)', 'massProcess'), + url('^mass/(?P<action>\w+)(?:/(?P<what>\w+))?', 'massProcess'), url('^(?P<tid>\d+)/transfer$', 'transfer'), url('^(?P<tid>\d+)/assign(?:/(?P<to>\w+))?$', 'assign'), + url('^(?P<tid>\d+)/claim$', 'claim'), url('^search', patterns('ajax.search.php:SearchAjaxAPI', url_get('^$', 'getAdvancedSearchDialog'), url_post('^$', 'doSearch'), diff --git a/scp/css/dropdown.css b/scp/css/dropdown.css index c0b90c83a9fc3b080df495c16e74c18380b88770..1106463645a48208b3526afe37ee08423e7db3ff 100644 --- a/scp/css/dropdown.css +++ b/scp/css/dropdown.css @@ -5,7 +5,7 @@ .action-dropdown { position: absolute; - z-index: 9999999; + z-index: 9999998; display: none; margin-top: 8px; } diff --git a/scp/css/scp.css b/scp/css/scp.css index 5b3141cb176ffa6c468c89c6a4bea4a58ef8a2fd..73c0e0029ce6eee6905006512fd2b9abf8ca2ba9 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -904,6 +904,12 @@ h2 .reload { border-bottom:none; margin:0; } +/***** top page ticket response buttons *****/ +a#post-note:hover { + background-color:#fff9e2; + color:#555!IMPORTANT; +} + .thread-entry { margin-bottom: 15px; z-index: 0; diff --git a/scp/css/tooltip.css b/scp/css/tooltip.css index ae54e90fc6b05e52e73828a6a4fa8492244023bc..daecf02bccdecc1d1477b834066b8771723610b9 100644 --- a/scp/css/tooltip.css +++ b/scp/css/tooltip.css @@ -1,6 +1,6 @@ .tooltip { position: absolute; - z-index: 1070; + z-index: 9999999; display: block; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; diff --git a/scp/js/scp.js b/scp/js/scp.js index 94b3b699371c1208ddb25932dbeeef6bdfb25c5e..28162de01b9a026a35a573f8089a35658c98ebda 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -464,6 +464,10 @@ var scp_prep = function() { $('[data-toggle="tooltip"]').tooltip() + $('[data-toggle="tooltip"]').on('click', function() { + $(this).tooltip('hide'); + }); + $('.attached.input input[autofocus]').parent().addClass('focus') $('.attached.input input') .on('focus', function() { $(this).parent().addClass('focus'); })