Skip to content

Commit

Permalink
feature: Release Role Permission
Browse files Browse the repository at this point in the history
This feature adds a new Role permission called Release. This permission
(if Enabled) allows an Agent to release ticket assignment. This permission
is configurable for each and every Role in the helpdesk. This also keeps
current functionality where Department Managers do not need the Role
Permission in order to release tickets. In addition to the permission,
this feature adds a new Release modal giving the option to choose who to
release assignment from (Agent/Team/Both). This also adds a comment box to
the release modal to optionally enter a reason for releasing assignment.
Lastly, this adds a Release Thread Event so an event is logged showing who
released who’s assignment.
  • Loading branch information
JediKev committed Jul 11, 2018
1 parent 3cbc57e commit d354e09
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 24 deletions.
60 changes: 60 additions & 0 deletions include/ajax.tickets.php
Expand Up @@ -652,6 +652,66 @@ function claim($tid) {


} }


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) { function massProcess($action, $w=null) {
global $thisstaff, $cfg; global $thisstaff, $cfg;


Expand Down
43 changes: 43 additions & 0 deletions include/class.forms.php
Expand Up @@ -4928,6 +4928,49 @@ function getFields() {


} }


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 { class ReferralForm extends Form {


static $id = 'refer'; static $id = 'refer';
Expand Down
28 changes: 27 additions & 1 deletion include/class.thread.php
Expand Up @@ -1871,6 +1871,7 @@ class ThreadEvent extends VerySimpleModel {


// Valid events for database storage // Valid events for database storage
const ASSIGNED = 'assigned'; const ASSIGNED = 'assigned';
const RELEASED = 'released';
const CLOSED = 'closed'; const CLOSED = 'closed';
const CREATED = 'created'; const CREATED = 'created';
const COLLAB = 'collab'; const COLLAB = 'collab';
Expand Down Expand Up @@ -1906,6 +1907,7 @@ function getUserName() {
function getIcon() { function getIcon() {
$icons = array( $icons = array(
'assigned' => 'hand-right', 'assigned' => 'hand-right',
'released' => 'unlock',
'collab' => 'group', 'collab' => 'group',
'created' => 'magic', 'created' => 'magic',
'overdue' => 'time', 'overdue' => 'time',
Expand Down Expand Up @@ -2092,7 +2094,7 @@ function annul($event) {
* $object - Object to log activity for * $object - Object to log activity for
* $state - State name of the activity (one of 'created', 'edited', * $state - State name of the activity (one of 'created', 'edited',
* 'deleted', 'closed', 'reopened', 'error', 'collab', 'resent', * 'deleted', 'closed', 'reopened', 'error', 'collab', 'resent',
* 'assigned', 'transferred') * 'assigned', 'released', 'transferred')
* $data - (array?) Details about the state change * $data - (array?) Details about the state change
* $user - (string|User|Staff) user triggering the state change * $user - (string|User|Staff) user triggering the state change
* $annul - (state) a corresponding state change that is annulled by * $annul - (state) a corresponding state change that is annulled by
Expand Down Expand Up @@ -2175,6 +2177,30 @@ function getDescription($mode=self::MODE_STAFF) {
} }
} }


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 { class ReferralEvent extends ThreadEvent {
static $icon = 'exchange'; static $icon = 'exchange';
static $state = 'referred'; static $state = 'referred';
Expand Down
17 changes: 15 additions & 2 deletions include/class.ticket.php
Expand Up @@ -98,6 +98,7 @@ class Ticket extends VerySimpleModel
const PERM_CREATE = 'ticket.create'; const PERM_CREATE = 'ticket.create';
const PERM_EDIT = 'ticket.edit'; const PERM_EDIT = 'ticket.edit';
const PERM_ASSIGN = 'ticket.assign'; const PERM_ASSIGN = 'ticket.assign';
const PERM_RELEASE = 'ticket.release';
const PERM_TRANSFER = 'ticket.transfer'; const PERM_TRANSFER = 'ticket.transfer';
const PERM_REPLY = 'ticket.reply'; const PERM_REPLY = 'ticket.reply';
const PERM_CLOSE = 'ticket.close'; const PERM_CLOSE = 'ticket.close';
Expand All @@ -119,6 +120,11 @@ class Ticket extends VerySimpleModel
/* @trans */ 'Assign', /* @trans */ 'Assign',
'desc' => 'desc' =>
/* @trans */ 'Ability to assign tickets to agents or teams'), /* @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( self::PERM_TRANSFER => array(
'title' => 'title' =>
/* @trans */ 'Transfer', /* @trans */ 'Transfer',
Expand Down Expand Up @@ -2470,8 +2476,15 @@ function unassign() {
return true; return true;
} }


function release() { function release($info=array(), &$errors) {
return $this->unassign(); 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) { function refer(ReferralForm $form, &$errors, $alert=true) {
Expand Down
2 changes: 2 additions & 0 deletions include/i18n/en_US/role.yaml
Expand Up @@ -20,6 +20,7 @@
ticket.create, ticket.create,
ticket.edit, ticket.edit,
ticket.assign, ticket.assign,
ticket.release,
ticket.transfer, ticket.transfer,
ticket.reply, ticket.reply,
ticket.close, ticket.close,
Expand All @@ -44,6 +45,7 @@
ticket.create, ticket.create,
ticket.edit, ticket.edit,
ticket.assign, ticket.assign,
ticket.release,
ticket.transfer, ticket.transfer,
ticket.reply, ticket.reply,
ticket.close, ticket.close,
Expand Down
89 changes: 89 additions & 0 deletions 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>
18 changes: 10 additions & 8 deletions include/staff/ticket-view.inc.php
Expand Up @@ -20,6 +20,8 @@
$lock = $ticket->acquireLock($thisstaff->getId()); $lock = $ticket->acquireLock($thisstaff->getId());
$mylock = ($lock && $lock->getStaffId() == $thisstaff->getId()) ? $lock : null; $mylock = ($lock && $lock->getStaffId() == $thisstaff->getId()) ? $lock : null;
$id = $ticket->getId(); //Ticket ID. $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! //Useful warnings and errors the user might want to know!
if ($ticket->isClosed() && !$ticket->isReopenable()) if ($ticket->isClosed() && !$ticket->isReopenable())
Expand Down Expand Up @@ -146,14 +148,14 @@ class="icon-group"></i> <?php echo __('Team'); ?></a>
<?php <?php
} }


if($ticket->isOpen() && ($dept && $dept->isManager($thisstaff))) { if ($ticket->isAssigned() && $canRelease) { ?>

<li><a href="#tickets/<?php echo $ticket->getId();
if($ticket->isAssigned()) { ?> ?>/release" class="ticket-action"
<li><a class="confirm-action" id="ticket-release" href="#release"><i class="icon-user"></i> <?php data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" >
echo __('Release (unassign) Ticket'); ?></a></li> <i class="icon-unlock"></i> <?php echo __('Release (unassign) Ticket'); ?></a></li>
<?php <?php
} }

if($ticket->isOpen() && $isManager) {
if(!$ticket->isOverdue()) { ?> if(!$ticket->isOverdue()) { ?>
<li><a class="confirm-action" id="ticket-overdue" href="#overdue"><i class="icon-bell"></i> <?php <li><a class="confirm-action" id="ticket-overdue" href="#overdue"><i class="icon-bell"></i> <?php
echo __('Mark as Overdue'); ?></a></li> echo __('Mark as Overdue'); ?></a></li>
Expand Down
1 change: 1 addition & 0 deletions scp/ajax.php
Expand Up @@ -167,6 +167,7 @@ function staffLoginPage($msg='Unauthorized') {
url('^(?P<tid>\d+)/field/(?P<fid>\d+)/edit$', 'editField'), url('^(?P<tid>\d+)/field/(?P<fid>\d+)/edit$', 'editField'),
url('^(?P<tid>\d+)/field/(?P<field>\w+)/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+)/assign(?:/(?P<to>\w+))?$', 'assign'),
url('^(?P<tid>\d+)/release$', 'release'),
url('^(?P<tid>\d+)/refer(?:/(?P<to>\w+))?$', 'refer'), url('^(?P<tid>\d+)/refer(?:/(?P<to>\w+))?$', 'refer'),
url('^(?P<tid>\d+)/referrals$', 'referrals'), url('^(?P<tid>\d+)/referrals$', 'referrals'),
url('^(?P<tid>\d+)/claim$', 'claim'), url('^(?P<tid>\d+)/claim$', 'claim'),
Expand Down
13 changes: 0 additions & 13 deletions scp/tickets.php
Expand Up @@ -282,19 +282,6 @@
break; break;
case 'process': case 'process':
switch(strtolower($_POST['do'])): 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': case 'claim':
if(!$role->hasPerm(Ticket::PERM_EDIT)) { if(!$role->hasPerm(Ticket::PERM_EDIT)) {
$errors['err'] = __('Permission Denied. You are not allowed to assign/claim tickets.'); $errors['err'] = __('Permission Denied. You are not allowed to assign/claim tickets.');
Expand Down

0 comments on commit d354e09

Please sign in to comment.