diff --git a/README.md b/README.md index ea4cdd5d..a53bd5d5 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Mods for HESK is a set of modifications for [HESK](https://www.hesk.com) v2.7.x,
  • Custom service message icons
  • Permission templates
  • Request users location in tickets
  • +
  • Category managers
  • Show number of merged tickets in ticket search view
  • Enable / disable staff members
  • More-restricted settings page access
  • diff --git a/admin/admin_reply_ticket.php b/admin/admin_reply_ticket.php index aa373e96..96ab9f9f 100644 --- a/admin/admin_reply_ticket.php +++ b/admin/admin_reply_ticket.php @@ -39,7 +39,9 @@ hesk_dbConnect(); hesk_isLoggedIn(); /* Check permissions for this feature */ -hesk_checkPermission('can_reply_tickets'); +if (!isset($_REQUEST['isManager']) || !$_REQUEST['isManager']) { + hesk_checkPermission('can_reply_tickets'); +} /* A security check */ # hesk_token_check('POST'); @@ -279,7 +281,7 @@ if ($time_worked == '00:00:00') { $sql .= ",`time_worked` = ADDTIME(`time_worked`,'" . hesk_dbEscape($time_worked) . "') "; } -if (!empty($_POST['assign_self']) && (hesk_checkPermission('can_assign_self', 0))) { +if (!empty($_POST['assign_self']) && (hesk_checkPermission('can_assign_self', 0) || (isset($_REQUEST['isManager']) && $_REQUEST['isManager']))) { $revision = sprintf($hesklang['thist2'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); $sql .= " , `owner`=" . intval($_SESSION['id']) . ", `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') "; } diff --git a/admin/admin_ticket.php b/admin/admin_ticket.php index 4e23cb80..9743f626 100644 --- a/admin/admin_ticket.php +++ b/admin/admin_ticket.php @@ -107,14 +107,33 @@ if ($ticket['lastreplier']) { } /* Get category name and ID */ -$result = hesk_dbQuery("SELECT `id`, `name` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` WHERE `id`='" . intval($ticket['category']) . "' LIMIT 1"); +$result = hesk_dbQuery("SELECT `id`, `name`, `manager` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` WHERE `id`='" . intval($ticket['category']) . "' LIMIT 1"); /* If this category has been deleted use the default category with ID 1 */ if (hesk_dbNumRows($result) != 1) { - $result = hesk_dbQuery("SELECT `id`, `name` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` WHERE `id`='1' LIMIT 1"); + $result = hesk_dbQuery("SELECT `id`, `name`, `manager` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` WHERE `id`='1' LIMIT 1"); } $category = hesk_dbFetchAssoc($result); +$managerRS = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'users` WHERE `id` = ' . intval($_SESSION['id'])); +$managerRow = hesk_dbFetchAssoc($managerRS); +$isManager = $managerRow['id'] == $category['manager']; +if ($isManager) { + $can_del_notes = + $can_reply = + $can_delete = + $can_edit = + $can_archive = + $can_assign_self = + $can_view_unassigned = + $can_change_own_cat = + $can_change_cat = + $can_ban_emails = + $can_unban_emails = + $can_ban_ips = + $can_unban_ips = + $can_resolve = true; +} /* Is this user allowed to view tickets inside this category? */ hesk_okCategory($category['id']); @@ -971,6 +990,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); '; + if ($isManager) { + echo ''; + } echo ' @@ -994,12 +1016,15 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); '; + if ($isManager) { + echo ''; + } echo ' '; echo '

    ' . $hesklang['owner'] . '

    '; - if (hesk_checkPermission('can_assign_others', 0)) { + if (hesk_checkPermission('can_assign_others', 0) || $isManager) { echo '
    @@ -1242,14 +1267,15 @@ require_once(HESK_PATH . 'inc/footer.inc.php'); function hesk_getAdminButtons($category_id) { - global $hesk_settings, $hesklang, $modsForHesk_settings, $ticket, $reply, $trackingID, $can_edit, $can_archive, $can_delete, $can_resolve; + global $hesk_settings, $hesklang, $modsForHesk_settings, $ticket, $reply, $trackingID, $can_edit, $can_archive, $can_delete, $can_resolve, $isManager; $options = ''; /* Edit post */ if ($can_edit) { $tmp = $reply ? '&reply=' . $reply['id'] : ''; - $options .= ' ' . $hesklang['edit'] . ' '; + $mgr = $isManager ? '&isManager=true' : ''; + $options .= ' ' . $hesklang['edit'] . ' '; } @@ -1295,12 +1321,13 @@ function hesk_getAdminButtons($category_id) $isTicketClosed = $isTicketClosedRow['IsClosed']; $isClosable = $isTicketClosedRow['Closable'] == 'yes' || $isTicketClosedRow['Closable'] == 'sonly'; + $mgr = $isManager ? '&isManager=1' : ''; if ($isTicketClosed == 0 && $isClosable && $can_resolve) // Ticket is still open { - $options .= ' + $options .= ' ' . $hesklang['close_action'] . ' '; } elseif ($isTicketClosed == 1) { - $options .= ' + $options .= ' ' . $hesklang['open_action'] . ' '; } @@ -1486,7 +1513,7 @@ function hesk_getAdminButtons($category_id) function hesk_getAdminButtonsInTicket($reply = 0, $white = 1) { - global $hesk_settings, $hesklang, $ticket, $reply, $trackingID, $can_edit, $can_archive, $can_delete; + global $hesk_settings, $hesklang, $ticket, $reply, $trackingID, $can_edit, $can_archive, $can_delete, $isManager; $options = $reply ? '' : '
    '; @@ -1509,7 +1536,8 @@ function hesk_getAdminButtonsInTicket($reply = 0, $white = 1) /* Edit post */ if ($can_edit) { $tmp = $reply ? '&reply=' . $reply['id'] : ''; - $options .= ' ' . $hesklang['edtt'] . ' '; + $mgr = $isManager ? '&isManager=true' : ''; + $options .= ' ' . $hesklang['edtt'] . ' '; } @@ -1823,7 +1851,7 @@ function hesk_printTicketReplies() function hesk_printReplyForm() { - global $hesklang, $hesk_settings, $ticket, $admins, $can_options, $can_resolve, $options, $can_assign_self, $modsForHesk_settings; + global $hesklang, $hesk_settings, $ticket, $admins, $can_options, $can_resolve, $options, $can_assign_self, $modsForHesk_settings, $isManager; // Force assigning a ticket before allowing to reply? if ($hesk_settings['require_owner'] && ! $ticket['owner']) @@ -2045,6 +2073,9 @@ function hesk_printReplyForm()
    + + +
    diff --git a/admin/change_status.php b/admin/change_status.php index d1d6551a..42d002f1 100644 --- a/admin/change_status.php +++ b/admin/change_status.php @@ -26,8 +26,10 @@ hesk_isLoggedIn(); $modsForHesk_settings = mfh_getSettings(); /* Check permissions for this feature */ -hesk_checkPermission('can_view_tickets'); -hesk_checkPermission('can_reply_tickets'); +if (!isset($_REQUEST['isManager']) || !$_REQUEST['isManager']) { + hesk_checkPermission('can_view_tickets'); + hesk_checkPermission('can_reply_tickets'); +} /* A security check */ hesk_token_check(); diff --git a/admin/edit_post.php b/admin/edit_post.php index 7dcdda44..f4312a2a 100644 --- a/admin/edit_post.php +++ b/admin/edit_post.php @@ -32,8 +32,11 @@ hesk_dbConnect(); hesk_isLoggedIn(); /* Check permissions for this feature */ -hesk_checkPermission('can_view_tickets'); -hesk_checkPermission('can_edit_tickets'); +if (!isset($_REQUEST['isManager']) || !$_REQUEST['isManager']) { + hesk_checkPermission('can_view_tickets'); + hesk_checkPermission('can_edit_tickets'); +} + $modsForHesk_settings = mfh_getSettings(); /* Ticket ID */ @@ -59,7 +62,10 @@ if (defined('HESK_DEMO')) { } /* Is this user allowed to view tickets inside this category? */ -hesk_okCategory($ticket['category']); +if (!isset($_REQUEST['isManager']) || !$_REQUEST['isManager']) { + hesk_okCategory($ticket['category']); +} + if (hesk_isREQUEST('reply')) { $tmpvar['id'] = intval(hesk_REQUEST('reply')) or die($hesklang['id_not_valid']); @@ -626,6 +632,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); ?> + + + diff --git a/admin/manage_categories.php b/admin/manage_categories.php index 6ddce2d4..5b6cf082 100644 --- a/admin/manage_categories.php +++ b/admin/manage_categories.php @@ -128,6 +128,7 @@ $res = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) + @@ -144,6 +145,19 @@ $res = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) +var users = [];'; +$users = array(); +while ($row = hesk_dbFetchAssoc($usersRs)) { + $users[] = $row; + echo "users[" . $row['id'] . "] = { + id: ".$row['id'].", + name: '".$row['name']."' + }\n"; +} +echo ''; +?> +
    + +
    + +
    +
    @@ -391,6 +418,9 @@ $res = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix'])
    + + + @@ -446,6 +476,7 @@ echo mfh_get_hidden_fields_for_language(array( 'disabled_title_case', 'geco', 'cpric', + 'no_manager', )); require_once(HESK_PATH . 'inc/footer.inc.php'); diff --git a/admin/manage_permission_templates.php b/admin/manage_permission_groups.php similarity index 71% rename from admin/manage_permission_templates.php rename to admin/manage_permission_groups.php index dc697006..3fd7eddc 100644 --- a/admin/manage_permission_templates.php +++ b/admin/manage_permission_groups.php @@ -39,10 +39,6 @@ if ($action = hesk_REQUEST('a')) { create(); } elseif ($action == 'delete') { deleteTemplate(); - } elseif ($action == 'addadmin') { - toggleAdmin(true); - } elseif ($action == 'deladmin') { - toggleAdmin(false); } } @@ -51,34 +47,20 @@ require_once(HESK_PATH . 'inc/headerAdmin.inc.php'); /* Print main manage users page */ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); -?> - - -
    @@ -87,9 +69,9 @@ while ($row = hesk_dbFetchAssoc($res)) {

    - + + title="">

    - + @@ -115,28 +97,11 @@ while ($row = hesk_dbFetchAssoc($res)) { - + 0) { + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` SET `manager` = 0 WHERE `id` IN (" . implode(',', $revokeCats) . ")"); + } + } + + hesk_dbQuery( "UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` SET `user`='" . hesk_dbEscape($myuser['user']) . "', @@ -651,6 +677,11 @@ function update_user() `default_calendar_view`=" . intval($myuser['default_calendar_view']) . " WHERE `id`='" . intval($myuser['id']) . "'"); + // If they are now inactive, remove any manager rights + if (!$myuser['active']) { + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` SET `manager` = 0 WHERE `manager` = " . intval($myuser['id'])); + } + unset($_SESSION['save_userdata']); unset($_SESSION['userdata']); @@ -814,6 +845,10 @@ function remove() hesk_process_messages($hesklang['cant_del_own'], './manage_users.php'); } + // Revoke manager rights + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` SET `manager` = 0 WHERE `manager` = " . intval($myuser)); + + /* Un-assign all tickets for this user */ $res = hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `owner`=0 WHERE `owner`='" . intval($myuser) . "'"); @@ -877,6 +912,9 @@ function toggle_active() $active = 0; $tmp = $hesklang['user_deactivated']; + // Revoke any manager rights + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` SET `manager` = 0 WHERE `manager` = " . intval($myuser)); + $notificationSql = ", `autoassign` = '0', `notify_new_unassigned` = '0', `notify_new_my` = '0', `notify_reply_unassigned` = '0', `notify_reply_my` = '0', `notify_assigned` = '0', `notify_pm` = '0', `notify_note` = '0', `notify_note_unassigned` = '0', `notify_overdue_unassigned` = '0'"; } diff --git a/api/BusinessLogic/Categories/Category.php b/api/BusinessLogic/Categories/Category.php index 7209823f..597c3410 100644 --- a/api/BusinessLogic/Categories/Category.php +++ b/api/BusinessLogic/Categories/Category.php @@ -51,6 +51,11 @@ class Category extends \BaseClass { */ public $priority; + /** + * @var int|null The manager for the Categories, if applicable + */ + public $manager; + /** * @var bool Indication if the user has access to the Categories */ diff --git a/api/BusinessLogic/Security/UserToTicketChecker.php b/api/BusinessLogic/Security/UserToTicketChecker.php index 13d88226..a49ecc55 100644 --- a/api/BusinessLogic/Security/UserToTicketChecker.php +++ b/api/BusinessLogic/Security/UserToTicketChecker.php @@ -31,6 +31,12 @@ class UserToTicketChecker extends \BaseClass { return false; } + $categoryManagerId = $this->userGateway->getManagerForCategory($ticket->categoryId, $heskSettings); + + if ($user->id === $categoryManagerId) { + return true; + } + $extraPermissions[] = UserPrivilege::CAN_VIEW_TICKETS; foreach ($extraPermissions as $permission) { diff --git a/api/Controllers/Categories/CategoryController.php b/api/Controllers/Categories/CategoryController.php index c89d1a1a..9d69df9f 100644 --- a/api/Controllers/Categories/CategoryController.php +++ b/api/Controllers/Categories/CategoryController.php @@ -63,6 +63,7 @@ class CategoryController extends \BaseClass { $category->description = Helpers::safeArrayGet($json, 'description'); $category->displayBorder = Helpers::safeArrayGet($json, 'displayBorder'); $category->foregroundColor = Helpers::safeArrayGet($json, 'foregroundColor'); + $category->manager = Helpers::safeArrayGet($json, 'manager'); $category->name = Helpers::safeArrayGet($json, 'name'); $category->priority = Helpers::safeArrayGet($json, 'priority'); $category->type = Helpers::safeArrayGet($json, 'type'); diff --git a/api/DataAccess/Categories/CategoryGateway.php b/api/DataAccess/Categories/CategoryGateway.php index 1a4c0d1c..2191373d 100644 --- a/api/DataAccess/Categories/CategoryGateway.php +++ b/api/DataAccess/Categories/CategoryGateway.php @@ -41,6 +41,7 @@ class CategoryGateway extends CommonDao { $category->foregroundColor = $row['foreground_color']; $category->displayBorder = $row['display_border_outline'] === '1'; $category->priority = intval($row['priority']); + $category->manager = intval($row['manager']) == 0 ? NULL : intval($row['manager']); $category->description = $row['mfh_description']; $category->numberOfTickets = intval($row['number_of_tickets']); $results[] = $category; @@ -63,11 +64,11 @@ class CategoryGateway extends CommonDao { $newOrder = hesk_dbFetchAssoc($newOrderRs); $sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "categories` - (`name`, `cat_order`, `autoassign`, `type`, `priority`, `background_color`, `usage`, + (`name`, `cat_order`, `autoassign`, `type`, `priority`, `manager`, `background_color`, `usage`, `foreground_color`, `display_border_outline`, `mfh_description`) VALUES ('" . hesk_dbEscape($category->name) . "', " . intval($newOrder['cat_order']) . ", '" . ($category->autoAssign ? 1 : 0) . "', '" . intval($category->type) . "', - '" . intval($category->priority) . "', + '" . intval($category->priority) . "', " . ($category->manager === null ? 0 : intval($category->manager)) . ", '" . hesk_dbEscape($category->backgroundColor) . "', " . intval($category->usage) . ", '" . hesk_dbEscape($category->foregroundColor) . "', '" . ($category->displayBorder ? 1 : 0) . "', '" . hesk_dbEscape($category->description) . "')"; @@ -94,6 +95,7 @@ class CategoryGateway extends CommonDao { `autoassign` = '" . ($category->autoAssign ? 1 : 0) . "', `type` = '" . intval($category->type) . "', `priority` = '" . intval($category->priority) . "', + `manager` = " . ($category->manager === null ? 0 : intval($category->manager)) . ", `background_color` = '" . hesk_dbEscape($category->backgroundColor) . "', `usage` = " . intval($category->usage) . ", `foreground_color` = '" . hesk_dbEscape($category->foregroundColor) . "', diff --git a/api/DataAccess/Security/UserGateway.php b/api/DataAccess/Security/UserGateway.php index 3ed4191b..e836f18a 100644 --- a/api/DataAccess/Security/UserGateway.php +++ b/api/DataAccess/Security/UserGateway.php @@ -99,4 +99,25 @@ class UserGateway extends CommonDao { return $users; } + + function getManagerForCategory($categoryId, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` + WHERE `id` = ( + SELECT `manager` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "categories` + WHERE `id` = " . intval($categoryId) . ")"); + + if (hesk_dbNumRows($rs) === 0) { + $this->close(); + return null; + } + + $user = UserContext::fromDataRow(hesk_dbFetchAssoc($rs)); + + $this->close(); + + return $user; + } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php b/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php index cf6d1719..27d5b939 100644 --- a/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php +++ b/api/Tests/BusinessLogic/Security/UserToTicketCheckerTest.php @@ -92,4 +92,23 @@ class UserToTicketCheckerTest extends TestCase { //-- Assert self::assertThat($result, self::isFalse()); } + + function testItReturnsTrueWhenTheUserDoesNotHaveEditPermissionsButIsTheCategoryManager() { + //-- Arrange + $user = new UserContext(); + $user->admin = false; + $user->categories = array(1); + $user->permissions = array(UserPrivilege::CAN_VIEW_TICKETS, 'something else'); + $user->id = 1; + $this->userGateway->method('getManagerForCategory')->willReturn(1); + + $ticket = new Ticket(); + $ticket->categoryId = 1; + + //-- Act + $result = $this->userToTicketChecker->isTicketAccessibleToUser($user, $ticket, $this->heskSettings, array(UserPrivilege::CAN_EDIT_TICKETS)); + + //-- Assert + self::assertThat($result, self::isTrue()); + } } diff --git a/inc/common.inc.php b/inc/common.inc.php index ec0ed464..94f487dc 100644 --- a/inc/common.inc.php +++ b/inc/common.inc.php @@ -2026,6 +2026,7 @@ function hesk_getFeatureArray() 'can_service_msg', /* User can manage service messages shown in customer interface */ 'can_email_tpl', /* User can manage email templates */ 'can_man_ticket_statuses', /* User can manage ticket statuses */ + 'can_set_manager', /* User can set category managers */ 'can_man_permission_tpl', /* User can manage permission templates */ 'can_man_settings', /* User can manage helpdesk settings */ 'can_change_notification_settings', /* User can change notification settings */ diff --git a/inc/profile_functions.inc.php b/inc/profile_functions.inc.php index 71b2ab45..52bb241a 100644 --- a/inc/profile_functions.inc.php +++ b/inc/profile_functions.inc.php @@ -162,9 +162,10 @@ function hesk_profile_tab($session_array = 'new', $is_profile_page = true, $acti if (!$is_profile_page) { ?>
    +
    + class="col-md-3 control-label">
    ' . htmlspecialchars($hesklang['custom']) . ''; echo ''; - outputCheckboxJavascript(); ?>
    + + + +
    - - - - - - - - - - - "> + - + @@ -172,12 +137,10 @@ function createEditModal($template, $features, $categories) { global $hesklang; - $showNotice = true; $disabled = 'checked="checked" disabled'; $enabledFeatures = array(); $enabledCategories = array(); if ($template['heskprivileges'] != 'ALL') { - $showNotice = false; $disabled = ''; $enabledFeatures = explode(',', $template['heskprivileges']); $enabledCategories = explode(',', $template['categories']); @@ -187,30 +150,23 @@ function createEditModal($template, $features, $categories) aria-labelledby="myLargeModalLabel" aria-hidden="true">