diff --git a/.gitignore b/.gitignore index 70d3d8c3..44ac5da8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ admin/archive.php admin/custom_statuses.php admin/email_templates.php admin/generate_spam_question.php -admin/priority.php admin/test_connection.php attachments/index.htm cache/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05e2a432..d24cfd47 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,32 +1,82 @@ -image: tetraweb/php - stages: + - validate - test - - deploy + - package before_script: - - apt-get update - - apt-get install zip unzip - - cd api - - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - - php composer-setup.php - - php -r "unlink('composer-setup.php');" - - php composer.phar update - -test: + - bash ci/docker_install.sh > /dev/null + +validate:7.1: + image: php:7.1 + stage: validate + script: + - bash ci/php_lint.sh ./ + +validate:7.0: + image: php:7.0 + stage: validate + script: + - bash ci/php_lint.sh ./ + +validate:5.6: + image: php:5.6 + stage: validate + script: + - bash ci/php_lint.sh ./ + +validate:5.5: + image: php:5.5 + stage: validate + script: + - bash ci/php_lint.sh ./ + +validate:5.4: + image: php:5.4 + stage: validate + script: + - bash ci/php_lint.sh ./ + +validate:5.3: + image: php:5.3 + stage: validate + script: + - bash ci/php_lint.sh ./ + +test:7.1: + image: php:7.1 + stage: test + script: + - cd api + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php -r "unlink('composer-setup.php');" + - php composer.phar update + - php composer.phar install + - cd Tests + - phpunit + +test:7.0: + image: php:7.0 stage: test script: - - composer install + - cd api + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php -r "unlink('composer-setup.php');" + - php composer.phar update + - php composer.phar install - cd Tests - phpunit -deploy: +package: + image: tetraweb/php when: manual - stage: deploy + stage: package script: + - cd api - composer install --no-dev - cd ../ci - - bash build_zip.sh + - bash build_release.sh artifacts: paths: - - release.zip \ No newline at end of file + - release/ diff --git a/admin/admin_reply_ticket.php b/admin/admin_reply_ticket.php index 96ab9f9f..29a0d5c8 100644 --- a/admin/admin_reply_ticket.php +++ b/admin/admin_reply_ticket.php @@ -189,15 +189,20 @@ if ($hesk_settings['attachments']['use'] && !empty($attachments)) { // Add reply $html = $modsForHesk_settings['rich_text_for_tickets']; if ($submit_as_customer) { - hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` (`replyto`,`name`,`message`,`dt`,`attachments`,`html`) VALUES ('" . intval($replyto) . "','" . hesk_dbEscape(addslashes($ticket['name'])) . "','" . hesk_dbEscape($message . "

{$hesklang['creb']} {$_SESSION['name']}") . "',NOW(),'" . hesk_dbEscape($myattachments) . "', '" . $html . "')"); + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` (`replyto`,`name`,`message`,`dt`,`attachments`,`html`) VALUES ('" . intval($replyto) . "','" . hesk_dbEscape(addslashes($ticket['name'])) . "','" . hesk_dbEscape($message . "

{$hesklang['creb']} {$_SESSION['name']}") . "','" . hesk_dbEscape(hesk_date()) . "','" . hesk_dbEscape($myattachments) . "', '" . $html . "')"); } else { - hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` (`replyto`,`name`,`message`,`dt`,`attachments`,`staffid`,`html`) VALUES ('" . intval($replyto) . "','" . hesk_dbEscape(addslashes($_SESSION['name'])) . "','" . hesk_dbEscape($message) . "',NOW(),'" . hesk_dbEscape($myattachments) . "','" . intval($_SESSION['id']) . "', '" . $html . "')"); + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` (`replyto`,`name`,`message`,`dt`,`attachments`,`staffid`,`html`) VALUES ('" . intval($replyto) . "','" . hesk_dbEscape(addslashes($_SESSION['name'])) . "','" . hesk_dbEscape($message) . "','" . hesk_dbEscape(hesk_date()) . "','" . hesk_dbEscape($myattachments) . "','" . intval($_SESSION['id']) . "', '" . $html . "')"); } /* Track ticket status changes for history */ $revision = ''; /* Change the status of priority? */ +$audit_priority = null; +$audit_closed = null; +$audit_status = null; +$audit_customer_status = null; +$audit_assigned_self = null; if (!empty($_POST['set_priority'])) { $priority = intval(hesk_POST('priority')); if ($priority < 0 || $priority > 3) { @@ -211,9 +216,17 @@ if (!empty($_POST['set_priority'])) { 3 => $hesklang['low'] ); - $revision = sprintf($hesklang['thist8'], hesk_date(), $options[$priority], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $plain_options = array( + 0 => 'critical', + 1 => 'high', + 2 => 'medium', + 3 => 'low' + ); + + $priority_sql = ",`priority`='$priority' "; - $priority_sql = ",`priority`='$priority', `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') "; + $audit_priority = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $plain_options[$priority]); } else { $priority_sql = ""; } @@ -238,8 +251,11 @@ if ($ticket['locked']) { $newStatus = hesk_dbFetchAssoc($newStatusRs); if ($newStatus['IsClosed'] && hesk_checkPermission('can_resolve', 0)) { - $revision = sprintf($hesklang['thist3'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - $sql_status = " , `closedat`=NOW(), `closedby`=" . intval($_SESSION['id']) . ", `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') "; + $audit_closed = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_status = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => mfh_getDisplayTextForStatusId($new_status) + ); + $sql_status = " , `closedat`=NOW(), `closedby`=" . intval($_SESSION['id']) . " "; // Lock the ticket if customers are not allowed to reopen tickets if ($hesk_settings['custopen'] != 1) { @@ -247,8 +263,8 @@ if ($ticket['locked']) { } } else { // Ticket isn't being closed, just add the history to the sql query (or tried to close but doesn't have permission) - $revision = sprintf($hesklang['thist9'], hesk_date(), $hesklang[$newStatus['Key']], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - $sql_status = " , `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') "; + $audit_status = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => mfh_getDisplayTextForStatusId($new_status)); } } } // -> Submit as Customer reply @@ -259,8 +275,8 @@ elseif ($submit_as_customer) { $new_status = $customerReplyStatus['ID']; if ($ticket['status'] != $new_status) { - $revision = sprintf($hesklang['thist9'], hesk_date(), $hesklang['wait_reply'], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - $sql_status = " , `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') "; + $audit_customer_status = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => mfh_getDisplayTextForStatusId($new_status)); } } // -> Default: submit as "Replied by staff" else { @@ -282,8 +298,8 @@ if ($time_worked == '00:00:00') { } 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) . "') "; + $audit_assigned_self = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $sql .= " , `owner`=" . intval($_SESSION['id']) . " "; } $sql .= " $priority_sql "; @@ -306,6 +322,29 @@ unset($sql); /* Update number of replies in the users table */ hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` SET `replies`=`replies`+1 WHERE `id`='" . intval($_SESSION['id']) . "'"); +//-- Insert necessary audit trail records +if ($audit_priority != null) { + mfh_insert_audit_trail_record($replyto, 'TICKET', 'audit_priority', hesk_date(), $audit_priority); +} + +if ($audit_closed != null) { + mfh_insert_audit_trail_record($replyto, 'TICKET', 'audit_closed', hesk_date(), $audit_closed); +} + +if ($audit_status != null) { + mfh_insert_audit_trail_record($replyto, 'TICKET', 'audit_status', hesk_date(), $audit_status); +} + +if ($audit_customer_status != null) { + mfh_insert_audit_trail_record($replyto, 'TICKET', 'audit_status', hesk_date(), + $audit_customer_status); +} + +if ($audit_assigned_self != null) { + mfh_insert_audit_trail_record($replyto, 'TICKET', 'audit_assigned_self', hesk_date(), $audit_assigned_self); +} + + // --> Prepare reply message // 1. Generate the array with ticket info that can be used in emails diff --git a/admin/admin_settings.php b/admin/admin_settings.php index eb56671c..8f9a5115 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -426,16 +426,16 @@ $modsForHesk_settings = mfh_getSettings(); - - $v) { $tmpvar['trackid'] = hesk_createID(); // Log who submitted ticket -$tmpvar['history'] = sprintf($hesklang['thist7'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); $tmpvar['openedby'] = $_SESSION['id']; // Owner $tmpvar['owner'] = 0; +$autoassign_owner = null; if (hesk_checkPermission('can_assign_others', 0)) { $tmpvar['owner'] = intval(hesk_POST('owner')); @@ -192,7 +192,6 @@ if (hesk_checkPermission('can_assign_others', 0)) { $autoassign_owner = hesk_autoAssignTicket($tmpvar['category']); if ($autoassign_owner) { $tmpvar['owner'] = intval($autoassign_owner['id']); - $tmpvar['history'] .= sprintf($hesklang['thist10'], hesk_date(), $autoassign_owner['name'] . ' (' . $autoassign_owner['user'] . ')'); } else { $tmpvar['owner'] = 0; } @@ -315,6 +314,14 @@ $tmpvar['screen_resolution_width'] = "NULL"; // Insert ticket to database $ticket = hesk_newTicket($tmpvar); +mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_created', hesk_date(), + array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')')); + +if ($autoassign_owner) { + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_autoassigned', hesk_date(), + array(0 => $autoassign_owner['name'] . ' (' . $autoassign_owner['user'] . ')')); +} + // Notify the customer about the ticket? if ($notify && $email_available) { hesk_notifyCustomer($modsForHesk_settings); diff --git a/admin/admin_ticket.php b/admin/admin_ticket.php index 899d37e6..d17ee27b 100644 --- a/admin/admin_ticket.php +++ b/admin/admin_ticket.php @@ -97,6 +97,37 @@ if (!$ticket['owner'] && !$can_view_unassigned) { hesk_error($hesklang['ycovtay']); } +// Get audit information +$audit_sort = $hesk_settings['new_top'] ? "ASC" : "DESC"; +$auditRes = hesk_dbQuery("SELECT `audit`.`id`, `audit`.`language_key`, `audit`.`date`, + `values`.`replacement_index`, `values`.`replacement_value` + FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail` AS `audit` + LEFT JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail_to_replacement_values` AS `values` + ON `audit`.`id` = `values`.`audit_trail_id` + WHERE `entity_type` = 'TICKET' AND `entity_id` = " . intval($ticket['id']) . " + ORDER BY `audit`.`date` {$audit_sort}"); +$audit_records = array(); +$current_audit_record = null; +while ($row = hesk_dbFetchAssoc($auditRes)) { + if ($current_audit_record == null || $current_audit_record['id'] != $row['id']) { + if ($current_audit_record != null) { + $audit_records[] = $current_audit_record; + } + $current_audit_record['id'] = $row['id']; + $current_audit_record['language_key'] = $row['language_key']; + $current_audit_record['date'] = $row['date']; + $current_audit_record['replacement_values'] = array(); + } + + if ($row['replacement_index'] != null) { + $current_audit_record['replacement_values'][intval($row['replacement_index'])] = $row['replacement_value']; + } +} + +if ($current_audit_record != null) { + $audit_records[] = $current_audit_record; +} + /* Set last replier name */ if ($ticket['lastreplier']) { if (empty($ticket['repliername'])) { @@ -120,19 +151,19 @@ $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; + $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? */ @@ -439,8 +470,10 @@ if ($hesk_settings['time_worked'] && ($can_reply || $can_edit) && isset($_POST[' $time_worked = hesk_getTime($h . ':' . $m . ':' . $s); /* Update database */ - $revision = sprintf($hesklang['thist14'], hesk_date(), $time_worked, $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `time_worked`='" . hesk_dbEscape($time_worked) . "', `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `time_worked`='" . hesk_dbEscape($time_worked) . "' WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_time_worked', hesk_date(), + array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $time_worked)); /* Show ticket */ hesk_process_messages($hesklang['twu'], 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . mt_rand(10000, 99999), 'SUCCESS'); @@ -476,13 +509,26 @@ if (($can_reply || $can_edit) && isset($_POST['childTrackingId'])) { } hesk_dbQuery('UPDATE `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'tickets` SET `parent` = ' . intval($ticket['id']) . ' WHERE `trackid` = \'' . hesk_dbEscape(hesk_POST('childTrackingId')) . '\''); + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_linked_ticket', hesk_date(), + array( + 0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => hesk_POST('childTrackingId') + )); hesk_process_messages(sprintf($hesklang['link_added'], $_POST['childTrackingId']), 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . mt_rand(10000, 99999), 'SUCCESS'); } /* Delete child action */ if (($can_reply || $can_edit) && isset($_GET['deleteChild'])) { //-- Delete the relationship + $innerTrackingRs = hesk_dbQuery("SELECT `trackid` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `id` = " . hesk_dbEscape($_GET['deleteChild'])); + $innerTrackingId = hesk_dbFetchAssoc($innerTrackingRs); + hesk_dbQuery('UPDATE `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'tickets` SET `parent` = NULL WHERE `ID` = ' . hesk_dbEscape($_GET['deleteChild'])); + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_unlinked_ticket', hesk_date(), + array( + 0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $innerTrackingId['trackid'] + )); hesk_process_messages($hesklang['ticket_no_longer_linked'], 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . mt_rand(10000, 99999), 'SUCCESS'); } elseif (($can_reply || $can_edit) && isset($_GET['deleteParent'])) { @@ -528,7 +574,6 @@ if (isset($_GET['delatt']) && hesk_token_check()) { hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "attachments` WHERE `att_id`='" . intval($att_id) . "'"); /* Update ticket or reply in the database */ - $revision = sprintf($hesklang['thist12'], hesk_date(), $att['real_name'], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); if ($reply) { hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` SET `attachments`=REPLACE(`attachments`,'" . hesk_dbEscape($att_id . '#' . $att['real_name'] . '#' . $att['saved_name']) . ",','') WHERE `id`='" . intval($reply) . "'"); hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `id`='" . intval($ticket['id']) . "'"); @@ -539,6 +584,9 @@ if (isset($_GET['delatt']) && hesk_token_check()) { hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `attachments`=REPLACE(`attachments`,'" . hesk_dbEscape($att_id . '#' . $att['real_name'] . '#' . $att['saved_name']) . ",','') WHERE `id`='" . intval($ticket['id']) . "'"); hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `attachments`=REPLACE(`attachments`,'" . hesk_dbEscape($att_id . '#' . $att['real_name']) . ",',''), `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `id`='" . intval($ticket['id']) . "'"); } + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_attachment_deleted', hesk_date(), + array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $att['real_name'])); hesk_process_messages($hesklang['kb_att_rem'], 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . mt_rand(10000, 99999), 'SUCCESS'); } @@ -948,25 +996,42 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); ); $options = array(); for ($i = 0; $i < 4; $i++) { + if ($ticket['priority'] == $i) { + if ($i === 0) { + $cssClass = 'critical-priority'; + } elseif ($i === 1) { + $cssClass = 'high-priority'; + } elseif ($i === 2) { + $cssClass = 'medium-priority'; + } else { + $cssClass = 'low-priority'; + } + } + $selected = $ticket['priority'] == $i ? 'selected' : ''; - array_push($options, ''); - } + $content = " {$priorityLanguages[$i]}"; + + if ($i === 0) { + $content = sprintf($content, 'long-arrow-up', 'critical'); + } elseif ($i === 1) { + $content = sprintf($content, 'angle-double-up', 'orange'); + } elseif ($i === 2) { + $content = sprintf($content, 'angle-double-down', 'green'); + } else { + $content = sprintf($content, 'long-arrow-down', 'blue'); + } - echo '
'; - } elseif ($ticket['priority'] == 1) { - echo 'high-priority">'; - } else { - echo 'med-low-priority">'; + array_push($options, ''); } + echo '
'; + echo '

' . $hesklang['priority'] . '

'; echo '
- '; echo implode('', $options); echo ' @@ -987,13 +1052,13 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); $results = mfh_getAllStatuses(); foreach ($results as $row) { $selected = $ticket['status'] == $row['ID'] ? 'selected' : ''; - $status_options[$row['ID']] = ''; + $status_options[$row['ID']] = ''; } echo ' - ' . implode('', $status_options) . ' @@ -1011,7 +1076,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); echo ' - '; $selectedForUnassign = 'selected'; foreach ($admins as $k => $v) { $selected = ''; @@ -1046,7 +1111,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); - ' . $categories_options . ' @@ -1588,7 +1653,7 @@ function print_form() } // End print_form() function mfh_print_message() { - global $ticket, $hesklang, $hesk_settings, $can_ban_emails, $can_ban_ips, $trackingID, $modsForHesk_settings; + global $ticket, $hesklang, $hesk_settings, $can_ban_emails, $can_ban_ips, $can_unban_emails, $can_unban_ips, $trackingID, $modsForHesk_settings; ?>
  • @@ -1749,7 +1814,44 @@ function mfh_print_message() { function hesk_printTicketReplies() { - global $hesklang, $hesk_settings, $result, $reply; + global $hesklang, $hesk_settings, $result, $reply, $audit_records; + + // Sort replies and audit messages. They'll be in the proper order already + $combined_records = array(); + foreach ($audit_records as $audit_record) { + $audit_record['SORT_TYPE'] = 'AUDIT_RECORD'; + $combined_records[] = $audit_record; + } + while ($reply = hesk_dbFetchAssoc($result)) { + $reply['SORT_TYPE'] = 'REPLY'; + $combined_records[] = $reply; + } + + // Re-sort them so they're in order by date + usort($combined_records, function ($a, $b) { + $a_date = null; + $b_date = null; + if ($a['SORT_TYPE'] == 'REPLY') { + $a_date = strtotime($a['dt']); + } else { + $a_date = strtotime($a['date']); + } + + if ($b['SORT_TYPE'] == 'REPLY') { + $b_date = strtotime($b['dt']); + } else { + $b_date = strtotime($b['date']); + } + + if ($a_date === $b_date && $a['SORT_TYPE'] != $b['SORT_TYPE']) { + if ($a['SORT_TYPE'] != $b['SORT_TYPE']) { + return $a['SORT_TYPE'] == 'REPLY' ? -1 : 1; + } + } + + return $a_date - $b_date; + }); + echo '
      '; if (!$hesk_settings['new_top']) { @@ -1758,83 +1860,194 @@ function hesk_printTicketReplies() echo '
    • '; } - while ($reply = hesk_dbFetchAssoc($result)) { - $reply['dt'] = hesk_date($reply['dt'], true); - ?> -
    • - - - - - -
      - -

      -
      -
      -
      - -
      -
      - -
      + foreach ($combined_records as $record) { + if ($record['SORT_TYPE'] == 'REPLY') { + mfh_print_reply($record); + } else { + mfh_print_audit_record($record); + } + } + + if ($hesk_settings['new_top']) { + mfh_print_message(); + } else { + echo '
    • '; + } + echo '
    '; + + return; + +} // End hesk_printTicketReplies() + +function mfh_print_reply($reply) { + global $hesklang, $hesk_settings; + + $reply['dt'] = hesk_date($reply['dt'], true); + ?> +
  • + + + + + +
    + +

    +
    +
    +
    + +
    +
    +
    - + - -
  • - + + '; - } - echo ''; +function mfh_print_audit_record($record) { + global $hesklang; - return; + $record['date'] = hesk_date($record['date'], true); + $font_icon = null; + switch ($record['language_key']) { + case 'audit_moved_category': + $font_icon = 'fa-pie-chart'; + break; + case 'audit_assigned': + case 'audit_assigned_self': + $font_icon = 'fa-user-plus'; + break; + case 'audit_unassigned': + $font_icon = 'fa-user-times'; + break; + case 'audit_autoassigned': + $font_icon = 'fa-bolt'; + break; + case 'audit_closed': + case 'audit_automatically_closed': + $font_icon = 'fa-check-circle'; + break; + case 'audit_opened': + $font_icon = 'fa-circle-o'; + break; + case 'audit_locked': + case 'audit_automatically_locked': + $font_icon = 'fa-lock'; + break; + case 'audit_unlocked': + $font_icon = 'fa-unlock-alt'; + break; + case 'audit_created': + case 'audit_submitted_by': + $font_icon = 'fa-user'; + break; + case 'audit_priority': + // The new priority is in arg[1] + $priority = $record['replacement_values'][1]; + if ($priority === 'critical') { + $font_icon = 'fa-long-arrow-up'; + } elseif ($priority === 'high') { + $font_icon = 'fa-angle-double-up'; + } elseif ($priority === 'medium') { + $font_icon = 'fa-angle-double-down'; + } else { + $font_icon = 'fa-long-arrow-down'; + } -} // End hesk_printTicketReplies() + // Now localize the text for display + $record['replacement_values'][1] = $hesklang[$priority]; + break; + case 'audit_status': + $font_icon = 'fa-exchange'; + break; + case 'audit_submitted_via_piping': + case 'audit_submitted_via_pop': + $font_icon = 'fa-envelope-o'; + break; + case 'audit_attachment_deleted': + $font_icon = 'fa-paperclip'; + break; + case 'audit_merged': + $font_icon = 'fa-code-fork'; + break; + case 'audit_time_worked': + $font_icon = 'fa fa-clock-o'; + break; + case 'audit_due_date_removed': + $font_icon = 'fa fa-calendar-minus-o'; + break; + case 'audit_due_date_changed': + $font_icon = 'fa fa-calendar'; + + //-- Format the date + $record['replacement_values'][1] = date('Y-m-d', strtotime($record['replacement_values'][1])); + break; + case 'audit_linked_ticket': + $font_icon = 'fa fa-link'; + break; + case 'audit_unlinked_ticket': + $font_icon = 'fa fa-chain-broken'; + break; + default: + $font_icon = 'fa-question-circle'; + break; + } + ?> +
  • + +
    + +

    + +

    +
    +
  • + ' . $hesklang['unas'] . '', $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - $res = hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `owner`=0 , `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + $res = hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `owner`=0 WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_unassigned', hesk_date(), + array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')')); hesk_process_messages($hesklang['tunasi2'], $_SERVER['PHP_SELF'], 'SUCCESS'); } elseif ($owner < 1) { @@ -96,8 +97,17 @@ if ($ticket['owner'] && $ticket['owner'] != $owner && hesk_REQUEST('unassigned') /* Assigning to self? */ if ($can_assign_others || ($owner == $_SESSION['id'] && $can_assign_self)) { - $revision = sprintf($hesklang['thist2'], hesk_date(), $row['name'] . ' (' . $row['user'] . ')', $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - $res = hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `owner`={$owner} , `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + $res = hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `owner`={$owner} WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + + if ($owner == $_SESSION['id'] && $can_assign_self) { + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_assigned_self', hesk_date(), + array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')')); + } else { + // current user -> assigned user + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_assigned', hesk_date(), + array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $row['name'] . ' (' . $row['user'] . ')')); + } if ($owner != $_SESSION['id'] && !hesk_checkPermission('can_view_ass_others', 0)) { $_SERVER['PHP_SELF'] = 'admin_main.php'; diff --git a/admin/change_status.php b/admin/change_status.php index 42d002f1..cc3f1fa7 100644 --- a/admin/change_status.php +++ b/admin/change_status.php @@ -37,6 +37,10 @@ hesk_token_check(); /* Ticket ID */ $trackingID = hesk_cleanID() or die($hesklang['int_error'] . ': ' . $hesklang['no_trackID']); +$ticket_id_rs = hesk_dbQuery("SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `trackid` = '" . hesk_dbEscape($trackingID) . "'"); +$ticket_id_row = hesk_dbFetchAssoc($ticket_id_rs); +$ticket_id = $ticket_id_row['id']; + /* Valid statuses */ $statusSql = "SELECT `ID` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses`"; $status_options = array(); @@ -54,6 +58,11 @@ if (!isset($status_options[$status])) { $locked = 0; +$audit_closed = null; +$audit_locked = null; +$audit_status = null; +$audit_opened = null; + $statusRow = hesk_dbFetchAssoc(hesk_dbQuery("SELECT `ID`, `IsClosed` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses` WHERE ID = " . $status)); if ($statusRow['IsClosed']) // Closed { @@ -62,10 +71,14 @@ if ($statusRow['IsClosed']) // Closed } $action = $hesklang['ticket_been'] . ' ' . $hesklang['close']; - $revision = sprintf($hesklang['thist3'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_closed = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_status = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $status_options[$status]); + if ($hesk_settings['custopen'] != 1) { $locked = 1; + $audit_locked = array(); } // Notify customer of closed ticket? @@ -91,21 +104,43 @@ if ($statusRow['IsClosed']) // Closed } elseif ($statusRow['IsNewTicketStatus'] == 0) //Ticket is still open, but not new { $action = sprintf($hesklang['tsst'], $status_options[$status]); - $revision = sprintf($hesklang['thist9'], hesk_date(), $status_options[$status], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_status = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', + 1 => $status_options[$status]); + // Ticket is not resolved $closedby_sql = ' , `closedat`=NULL, `closedby`=NULL '; } else // Ticket is marked as "NEW" { $action = $hesklang['ticket_been'] . ' ' . $hesklang['opened']; - $revision = sprintf($hesklang['thist4'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_opened = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); // Ticket is not resolved $closedby_sql = ' , `closedat`=NULL, `closedby`=NULL '; } -hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`='{$status}', `locked`='{$locked}' $closedby_sql , `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); +hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`='{$status}', `locked`='{$locked}' $closedby_sql WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + +if ($audit_status !== null) { + mfh_insert_audit_trail_record($ticket_id, 'TICKET', 'audit_status', hesk_date(), + $audit_status); +} + +if ($audit_closed !== null) { + mfh_insert_audit_trail_record($ticket_id, 'TICKET', 'audit_closed', hesk_date(), + $audit_closed); +} + +if ($audit_locked !== null) { + mfh_insert_audit_trail_record($ticket_id, 'TICKET', 'audit_automatically_locked', hesk_date(), + array()); +} + +if ($audit_opened !== null) { + mfh_insert_audit_trail_record($ticket_id, 'TICKET', 'audit_opened', hesk_date(), + $audit_opened); +} if (hesk_dbAffectedRows() != 1) { hesk_error("$hesklang[int_error]: $hesklang[trackID_not_found]."); diff --git a/admin/custom_fields.php b/admin/custom_fields.php index a71b6a5a..04917840 100755 --- a/admin/custom_fields.php +++ b/admin/custom_fields.php @@ -166,7 +166,31 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
    + + $descriptions = hesk_SESSION(array('new_cf','descriptions')); ?> +
    +
    + + 1): ?> + + $info): ?> + + + + + +
    + +
    + +
    + +
    +
    '; @@ -473,9 +478,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); echo ''; } - - echo ' -
    + if (!empty($v['mfh_description'])) { + echo '
    ' . $v['mfh_description'] . '
    '; + } + echo ''; + echo '
    '; break; @@ -496,6 +503,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); echo '
    '; } + if (!empty($v['mfh_description'])) { + echo '
    ' . $v['mfh_description'] . '
    '; + } echo '
    '; @@ -510,8 +520,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
    - -
    + '; + if (!empty($v['mfh_description'])) { + echo '
    ' . $v['mfh_description'] . '
    '; + } + echo '
    '; break; @@ -530,8 +543,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
    - -
    + '; + if (!empty($v['mfh_description'])) { + echo '
    ' . $v['mfh_description'] . '
    '; + } + echo '
    '; break; @@ -546,8 +562,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
    - -
    + '; + if (!empty($v['mfh_description'])) { + echo '
    ' . $v['mfh_description'] . '
    '; + } + echo '
    @@ -568,8 +587,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
    - -
    + '; + if (!empty($v['mfh_description'])) { + echo '
    ' . $v['mfh_description'] . '
    '; + } + echo '
    '; diff --git a/admin/index.php b/admin/index.php index cef05749..ba7c4b00 100644 --- a/admin/index.php +++ b/admin/index.php @@ -202,7 +202,6 @@ function do_login() /* Close any old tickets here so Cron jobs aren't necessary */ if ($hesk_settings['autoclose']) { - $revision = sprintf($hesklang['thist3'], hesk_date(), $hesklang['auto']); $dt = date('Y-m-d H:i:s', time() - $hesk_settings['autoclose'] * 86400); @@ -210,22 +209,25 @@ function do_login() $closedStatus = hesk_dbFetchAssoc($closedStatusRs); // Are we allowed to close tickets in this status? if ($closedStatus['Closable'] == 'yes' || $closedStatus['Closable'] == 'sonly') { - // Notify customer of closed ticket? - if ($hesk_settings['notify_closed']) { - // Get list of tickets - $result = hesk_dbQuery("SELECT * FROM `" . $hesk_settings['db_pfix'] . "tickets` WHERE `status` = " . $closedStatus['ID'] . " AND `lastchange` <= '" . hesk_dbEscape($dt) . "' "); - if (hesk_dbNumRows($result) > 0) { - global $ticket; - - // Load required functions? - if (!function_exists('hesk_notifyCustomer')) { - require(HESK_PATH . 'inc/email_functions.inc.php'); - } - while ($ticket = hesk_dbFetchAssoc($result)) { - $ticket['dt'] = hesk_date($ticket['dt'], true); - $ticket['lastchange'] = hesk_date($ticket['lastchange'], true); - $ticket = hesk_ticketToPlain($ticket, 1, 0); + $result = hesk_dbQuery("SELECT * FROM `" . $hesk_settings['db_pfix'] . "tickets` WHERE `status` = " . $closedStatus['ID'] . " AND `lastchange` <= '" . hesk_dbEscape($dt) . "' "); + if (hesk_dbNumRows($result) > 0) { + global $ticket; + + // Load required functions? + if (!function_exists('hesk_notifyCustomer')) { + require(HESK_PATH . 'inc/email_functions.inc.php'); + } + + while ($ticket = hesk_dbFetchAssoc($result)) { + $ticket['dt'] = hesk_date($ticket['dt'], true); + $ticket['lastchange'] = hesk_date($ticket['lastchange'], true); + $ticket = hesk_ticketToPlain($ticket, 1, 0); + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_automatically_closed', hesk_date(), array()); + + // Notify customer of closed ticket? + if ($hesk_settings['notify_closed']) { + // Get list of tickets hesk_notifyCustomer($modsForHesk_settings, 'ticket_closed'); } } @@ -234,7 +236,7 @@ function do_login() // Update ticket statuses and history in database if we're allowed to do so $defaultCloseRs = hesk_dbQuery('SELECT `ID` FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'statuses` WHERE `IsAutocloseOption` = 1'); $defaultCloseStatus = hesk_dbFetchAssoc($defaultCloseRs); - hesk_dbQuery("UPDATE `" . $hesk_settings['db_pfix'] . "tickets` SET `status`=" . intval($defaultCloseStatus['ID']) . ", `closedat`=NOW(), `closedby`='-1', `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `status` = '" . $closedStatus['ID'] . "' AND `lastchange` <= '" . hesk_dbEscape($dt) . "' "); + hesk_dbQuery("UPDATE `" . $hesk_settings['db_pfix'] . "tickets` SET `status`=" . intval($defaultCloseStatus['ID']) . ", `closedat`=NOW(), `closedby`='-1' WHERE `status` = " . $closedStatus['ID'] . " AND `lastchange` <= '" . hesk_dbEscape($dt) . "' "); } } diff --git a/admin/lock.php b/admin/lock.php index 57bedfd6..ec1836db 100644 --- a/admin/lock.php +++ b/admin/lock.php @@ -37,27 +37,31 @@ hesk_token_check(); /* Ticket ID */ $trackingID = hesk_cleanID() or die($hesklang['int_error'] . ': ' . $hesklang['no_trackID']); + +// Get ticket info +$result = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `trackid`='" . hesk_dbEscape($trackingID) . "' LIMIT 1"); +if (hesk_dbNumRows($result) != 1) { + hesk_error($hesklang['ticket_not_found']); +} +$ticket = hesk_dbFetchAssoc($result); + +$audit_unlocked = null; +$audit_locked = null; + /* New locked status */ if (empty($_GET['locked'])) { $status = 0; $tmp = $hesklang['tunlock']; - $revision = sprintf($hesklang['thist6'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_unlocked = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); $closedby_sql = ' , `closedat`=NULL, `closedby`=NULL '; } else { $status = 1; $tmp = $hesklang['tlock']; - $revision = sprintf($hesklang['thist5'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); + $audit_locked = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); $closedby_sql = ' , `closedat`=NOW(), `closedby`=' . intval($_SESSION['id']) . ' '; // Notify customer of closed ticket? if ($hesk_settings['notify_closed']) { - // Get ticket info - $result = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `trackid`='" . hesk_dbEscape($trackingID) . "' LIMIT 1"); - if (hesk_dbNumRows($result) != 1) { - hesk_error($hesklang['ticket_not_found']); - } - $ticket = hesk_dbFetchAssoc($result); - $closedStatusRS = hesk_dbQuery('SELECT `ID` FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'statuses` WHERE `IsClosed` = 1'); $ticketIsOpen = true; while ($row = hesk_dbFetchAssoc($closedStatusRS)) { @@ -82,7 +86,17 @@ $statusRs = hesk_dbQuery($statusSql); $statusRow = hesk_dbFetchAssoc($statusRs); $statusId = $statusRow['ID']; -hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`= {$statusId},`locked`='{$status}' $closedby_sql , `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); +hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`= {$statusId},`locked`='{$status}' $closedby_sql WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'"); + +if ($audit_unlocked) { + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_unlocked', hesk_date(), + $audit_unlocked); +} + +if ($audit_locked) { + mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_locked', hesk_date(), + $audit_locked); +} /* Back to ticket page and show a success message */ hesk_process_messages($tmp, 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . rand(10000, 99999), 'SUCCESS'); \ No newline at end of file diff --git a/admin/manage_categories.php b/admin/manage_categories.php index c90aebc8..5b6cf082 100644 --- a/admin/manage_categories.php +++ b/admin/manage_categories.php @@ -16,6 +16,7 @@ define('HESK_PATH', '../'); define('VALIDATOR', 1); define('PAGE_TITLE', 'ADMIN_CATEGORIES'); define('MFH_PAGE_LAYOUT', 'TOP_ONLY'); +define('EXTRA_JS', ''); /* Get all the required files and functions */ require(HESK_PATH . 'hesk_settings.inc.php'); @@ -84,349 +85,98 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); ' . $mycat['name'] . ''; -} ?>
    -
    -
    -

    - -

    -
    - -
    -
    -
    - -
    - - -
    - - data-error="" - required> -
    -
    -
    +
    +
    +

    + +

    +
    +
    -
    - - -
    - - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - -
        -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - - - -
    -
    - -
    -
    -
    -
    -

    - -

    -
    -
    -
    -
    - - - - - - - - - - - - - +
    ' - ', - 1 => '', - 2 => '  ' - ); - - while ($mycat = hesk_dbFetchAssoc($res)) { - $j++; - - if (isset($_SESSION['selcat2']) && $mycat['id'] == $_SESSION['selcat2']) { - $color = 'admin_green'; - unset($_SESSION['selcat2']); - } else { - $color = $i ? 'admin_white' : 'admin_gray'; - } - - $tmp = $i ? 'White' : 'Blue'; - $style = 'background: ' . $mycat['background_color']; - $backgroundVolatile = 'background-volatile'; - if ($mycat['foreground_color'] != 'AUTO') { - $style .= '; color: ' . $mycat['foreground_color']; - $backgroundVolatile = ''; - - if ($mycat['display_border_outline']) { - $style .= '; border: solid 1px ' . $mycat['foreground_color']; - } - } - - if ($mycat['foreground_color'] == 'AUTO') { - $mycat['foreground_color'] = ''; - } - - $i = $i ? 0 : 1; - - /* Number of tickets and graph width */ - $all = isset($tickets_all[$mycat['id']]) ? $tickets_all[$mycat['id']] : 0; - $width_all = 0; - if ($tickets_total && $all) { - $width_all = round(($all / $tickets_total) * 100); - } - - /* Deleting category with ID 1 (default category) is not allowed */ - if ($mycat['id'] == 1) { - $remove_code = ' '; - } else { - $remove_code = ' '; - } - - /* Is category private or public? */ - if ($mycat['type']) { - $type_code = ''; - } else { - $type_code = ''; - } - - /* Is auto assign enabled? */ - if ($hesk_settings['autoassign']) { - if ($mycat['autoassign']) { - $autoassign_code = ''; - } else { - $autoassign_code = ''; - } - } else { - $autoassign_code = ''; - } - - echo ' -
    - - - - - - - - - - '; - - } // End while - ?> -
    ' . $mycat['id'] . '' . $mycat['name'] . '' . $priorities[$mycat['priority']]['formatted'] . '' . $all . ' -
    -
    -
    -
    -
    ' . $usage[$mycat['usage']] . '' . get_manager($mycat['manager'], $users) . ' - - ' . $autoassign_code . ' - ' . $type_code . ' '; - - if ($orderBy != 'name' && $num > 1) { - if ($j == 1) { - echo '  '; - } elseif ($j == $num) { - echo ' '; - } else { - echo ' - -   - '; - } - } - echo ''; - echo $remove_code . '
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + +
    -
    -
    +
    - -