diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 643d8b4613ad80ee89da610c33843bca7b3d6610..05cf5354c426092df2d32adc814e83b31aac228a 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -652,6 +652,66 @@ function refer($tid, $target=null) { } + function release($tid) { + global $thisstaff; + + if (!($ticket=Ticket::lookup($tid))) + Http::response(404, __('No such ticket')); + + if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_RELEASE) && !$thisstaff->isManager()) + Http::response(403, __('Permission denied')); + + if (!$ticket->isAssigned()) + $errors['err'] = __('Ticket is not assigned!'); + + + $errors = array(); + $info = array(':title' => sprintf(__('Ticket #%s: %s'), + $ticket->getNumber(), + __('Release Confirmation'))); + + $form = ReleaseForm::instantiate($_POST); + $hasData = ($_POST['sid'] || $_POST['tid']); + + $staff = $ticket->getStaff(); + $team = $ticket->getTeam(); + if ($_POST) { + if ($hasData && $ticket->release($_POST, $errors)) { + $data = array(); + + if ($staff && !$ticket->getStaff()) + $data['staff'] = array($staff->getId(), (string) $staff->getName()->getOriginal()); + if ($team && !$ticket->getTeam()) + $data['team'] = $team->getId(); + $ticket->logEvent('released', $data); + + $comments = $form->getComments(); + if ($comments) { + $title = __('Assignment Released'); + $_errors = array(); + + $ticket->postNote( + array('note' => $comments, 'title' => $title), + $_errors, $thisstaff, false); + } + + $_SESSION['::sysmsgs']['msg'] = __('Ticket assignment released successfully'); + Http::response(201, $ticket->getId()); + } + + if (!$hasData) + $errors['err'] = __('Please check an assignee to release assignment'); + + $form->addErrors($errors); + $info['error'] = $errors['err'] ?: __('Unable to release ticket assignment'); + } + + if($errors && $errors['err']) + $info['error'] = $errors['err'] ?: __('Unable to release ticket'); + + include STAFFINC_DIR . 'templates/release.tmpl.php'; + } + function massProcess($action, $w=null) { global $thisstaff, $cfg; diff --git a/include/class.forms.php b/include/class.forms.php index e13f5f15d41a0c28bebb7d7e8a1108a247d4d241..6f28d71f98dbf476523733f75c87bcaf3b5251fd 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -4928,6 +4928,49 @@ class ClaimForm extends AssignmentForm { } +class ReleaseForm extends Form { + static $id = 'unassign'; + + function getFields() { + if ($this->fields) + return $this->fields; + + $fields = array( + 'comments' => new TextareaField(array( + 'id' => 1, 'label'=> '', 'required'=>false, 'default'=>'', + 'configuration' => array( + 'html' => true, + 'size' => 'small', + 'placeholder' => __('Optional reason for releasing assignment'), + ), + ) + ), + ); + + + $this->setFields($fields); + + return $this->fields; + } + + function getField($name) { + if (($fields = $this->getFields()) + && isset($fields[$name])) + return $fields[$name]; + } + + function isValid($include=false) { + if (!parent::isValid($include)) + return false; + + return !$this->errors(); + } + + function getComments() { + return $this->getField('comments')->getClean(); + } +} + class ReferralForm extends Form { static $id = 'refer'; diff --git a/include/class.thread.php b/include/class.thread.php index 3393043e60e91aed5130ae70bf863e26c44c56e3..7cca14af8f1fdc952e917156fc9a4ad6a81e6f0a 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -1871,6 +1871,7 @@ class ThreadEvent extends VerySimpleModel { // Valid events for database storage const ASSIGNED = 'assigned'; + const RELEASED = 'released'; const CLOSED = 'closed'; const CREATED = 'created'; const COLLAB = 'collab'; @@ -1906,6 +1907,7 @@ class ThreadEvent extends VerySimpleModel { function getIcon() { $icons = array( 'assigned' => 'hand-right', + 'released' => 'unlock', 'collab' => 'group', 'created' => 'magic', 'overdue' => 'time', @@ -2092,7 +2094,7 @@ class ThreadEvents extends InstrumentedList { * $object - Object to log activity for * $state - State name of the activity (one of 'created', 'edited', * 'deleted', 'closed', 'reopened', 'error', 'collab', 'resent', - * 'assigned', 'transferred') + * 'assigned', 'released', 'transferred') * $data - (array?) Details about the state change * $user - (string|User|Staff) user triggering the state change * $annul - (state) a corresponding state change that is annulled by @@ -2175,6 +2177,30 @@ class AssignmentEvent extends ThreadEvent { } } +class ReleaseEvent extends ThreadEvent { + static $icon = 'unlock'; + static $state = 'released'; + + function getDescription($mode=self::MODE_STAFF) { + $data = $this->getData(); + switch (true) { + case isset($data['staff'], $data['team']): + $desc = __('Ticket released from <strong>{<Team>data.team}</strong> and <strong>{<Staff>data.staff}</strong> by <b>{somebody}</b> {timestamp}'); + break; + case isset($data['staff']): + $desc = __('Ticket released from <strong>{<Staff>data.staff}</strong> by <b>{somebody}</b> {timestamp}'); + break; + case isset($data['team']): + $desc = __('Ticket released from <strong>{<Team>data.team}</strong> by <b>{somebody}</b> {timestamp}'); + break; + default: + $desc = __('<b>{somebody}</b> released ticket assignment {timestamp}'); + break; + } + return $this->template($desc); + } +} + class ReferralEvent extends ThreadEvent { static $icon = 'exchange'; static $state = 'referred'; diff --git a/include/class.ticket.php b/include/class.ticket.php index 5078b566e1f8c04944cf524ef379ef9b14b37c16..160c5630ee5bd2daff7822abe0a7f325623bc988 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -98,6 +98,7 @@ implements RestrictedAccess, Threadable, Searchable { const PERM_CREATE = 'ticket.create'; const PERM_EDIT = 'ticket.edit'; const PERM_ASSIGN = 'ticket.assign'; + const PERM_RELEASE = 'ticket.release'; const PERM_TRANSFER = 'ticket.transfer'; const PERM_REPLY = 'ticket.reply'; const PERM_CLOSE = 'ticket.close'; @@ -119,6 +120,11 @@ implements RestrictedAccess, Threadable, Searchable { /* @trans */ 'Assign', 'desc' => /* @trans */ 'Ability to assign tickets to agents or teams'), + self::PERM_RELEASE => array( + 'title' => + /* @trans */ 'Release', + 'desc' => + /* @trans */ 'Ability to release ticket assignment'), self::PERM_TRANSFER => array( 'title' => /* @trans */ 'Transfer', @@ -2470,8 +2476,15 @@ implements RestrictedAccess, Threadable, Searchable { return true; } - function release() { - return $this->unassign(); + function release($info=array(), &$errors) { + if ($info['sid'] && $info['tid']) + return $this->unassign(); + elseif ($info['sid'] && $this->setStaffId(0)) + return true; + elseif ($info['tid'] && $this->setTeamId(0)) + return true; + + return false; } function refer(ReferralForm $form, &$errors, $alert=true) { diff --git a/include/i18n/en_US/role.yaml b/include/i18n/en_US/role.yaml index ca76b2650513d4ffcf0d5bb48fa57cb07b203757..9688cae1f82a35011256f89b18a4211b84178313 100644 --- a/include/i18n/en_US/role.yaml +++ b/include/i18n/en_US/role.yaml @@ -20,6 +20,7 @@ ticket.create, ticket.edit, ticket.assign, + ticket.release, ticket.transfer, ticket.reply, ticket.close, @@ -44,6 +45,7 @@ ticket.create, ticket.edit, ticket.assign, + ticket.release, ticket.transfer, ticket.reply, ticket.close, diff --git a/include/staff/templates/release.tmpl.php b/include/staff/templates/release.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..a60fe50a52dd01efc87c27419590a4991da552ba --- /dev/null +++ b/include/staff/templates/release.tmpl.php @@ -0,0 +1,89 @@ +<?php +global $cfg; + +$assignees = array(); +if (($staff = $ticket->getStaff())) + $assignees[] = $staff; +if (($team = $ticket->getTeam())) + $assignees[] = $team; + +$form = ReleaseForm::instantiate($_POST); +?> +<h3 class="drag-handle"><?php echo $info[':title'] ?: __('Release Confirmation'); ?></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']); +} +?> +<form class="mass-action" method="post" + action="#tickets/<?php echo $ticket->getId(); ?>/release" + name="release"> + <input type='hidden' name='do' value='release'> + <table width="100%"> + <tbody> + <?php if ($staff && $team) { ?> + <tr><td> + <p> + <?php echo __('Please check assignee(s) to release assignment.'); ?> + </p> + </td></tr> + <?php } ?> + <?php if(count($assignees) > 1) { ?> + <?php foreach($assignees as $assignee) { ?> + <tr><td> + <label class="inline checkbox"> + <?php echo sprintf( + ($isStaff = $assignee instanceof Staff) + ? '<input type="checkbox" name="sid[]" id="s%d" value="%d">' + : '<input type="checkbox" name="tid[]" id="t%d" value="%d">', + $assignee->getId(), + $assignee->getId()); ?> + </label> + <?php echo '<i class="icon-'.(($isStaff) ? 'user' : 'group').'"></i>'; ?> + <?php echo $assignee->getName(); ?> + </td></tr> + <?php } ?> + <?php } else { ?> + <tr><td> + <input type="hidden" name="<?php echo (($staff)?'s':'t').'id[]'; ?>" value="()"> + <p> + <?php echo __('Please confirm to continue.'); ?> + </p> + <p> + <?php echo sprintf( + __('Are you sure you want to <b>unassign</b> ticket from <b>%s</b>?'), + ($staff) ?: $team); ?> + </p> + </td></tr> + <?php } ?> + <tr><td> + <p> + <?php print $form->getField('comments')->render(); ?> + </p> + </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 __('Release'); ?>"> + </span> + </p> +</form> +<div class="clear"></div> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 21d5646e92a065605f26eefb86e1c9055ab4c2ff..33272a222b17afd06db4b06198263b36ad9ba144 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -20,6 +20,8 @@ if (!$lock && $cfg->getTicketLockMode() == Lock::MODE_ON_VIEW) $lock = $ticket->acquireLock($thisstaff->getId()); $mylock = ($lock && $lock->getStaffId() == $thisstaff->getId()) ? $lock : null; $id = $ticket->getId(); //Ticket ID. +$isManager = $dept->isManager($thisstaff); //Check if Agent is Manager +$canRelease = ($isManager || $role->hasPerm(Ticket::PERM_RELEASE)); //Check if Agent can release tickets //Useful warnings and errors the user might want to know! if ($ticket->isClosed() && !$ticket->isReopenable()) @@ -146,14 +148,14 @@ if($ticket->isOverdue()) <?php } - if($ticket->isOpen() && ($dept && $dept->isManager($thisstaff))) { - - if($ticket->isAssigned()) { ?> - <li><a class="confirm-action" id="ticket-release" href="#release"><i class="icon-user"></i> <?php - echo __('Release (unassign) Ticket'); ?></a></li> - <?php - } - + if ($ticket->isAssigned() && $canRelease) { ?> + <li><a href="#tickets/<?php echo $ticket->getId(); + ?>/release" class="ticket-action" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" > + <i class="icon-unlock"></i> <?php echo __('Release (unassign) Ticket'); ?></a></li> + <?php + } + if($ticket->isOpen() && $isManager) { if(!$ticket->isOverdue()) { ?> <li><a class="confirm-action" id="ticket-overdue" href="#overdue"><i class="icon-bell"></i> <?php echo __('Mark as Overdue'); ?></a></li> diff --git a/scp/ajax.php b/scp/ajax.php index 7dc4a56a2a9c690add163d05fed948045ad7cd2a..dfdb7499e7ae093c1a55e20f8937772d6447002e 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -167,6 +167,7 @@ $dispatcher = patterns('', url('^(?P<tid>\d+)/field/(?P<fid>\d+)/edit$', 'editField'), url('^(?P<tid>\d+)/field/(?P<field>\w+)/edit$', 'editField'), url('^(?P<tid>\d+)/assign(?:/(?P<to>\w+))?$', 'assign'), + url('^(?P<tid>\d+)/release$', 'release'), url('^(?P<tid>\d+)/refer(?:/(?P<to>\w+))?$', 'refer'), url('^(?P<tid>\d+)/referrals$', 'referrals'), url('^(?P<tid>\d+)/claim$', 'claim'), diff --git a/scp/tickets.php b/scp/tickets.php index 1e1a085ab48afbf63f32af2db1329b4ba037fdf3..ca0770817b558436d31691a035011e47abcfb3f4 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -282,19 +282,6 @@ if($_POST && !$errors): break; case 'process': switch(strtolower($_POST['do'])): - case 'release': - if(!$ticket->isAssigned() || !($assigned=$ticket->getAssigned())) { - $errors['err'] = __('Ticket is not assigned!'); - } elseif($ticket->release()) { - $msg=sprintf(__( - /* 1$ is the current assignee, 2$ is the agent removing the assignment */ - 'Ticket released (unassigned) from %1$s by %2$s'), - $assigned, $thisstaff->getName()); - $ticket->logActivity(__('Ticket unassigned'),$msg); - } else { - $errors['err'] = sprintf('%s %s', __('Problems releasing the ticket.'), __('Please try again!')); - } - break; case 'claim': if(!$role->hasPerm(Ticket::PERM_EDIT)) { $errors['err'] = __('Permission Denied. You are not allowed to assign/claim tickets.');