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/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', `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') ";
+ $priority_sql = ",`priority`='$priority' ";
+
+ $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_submit_ticket.php b/admin/admin_submit_ticket.php
index 9eb40c3a..80d1269e 100644
--- a/admin/admin_submit_ticket.php
+++ b/admin/admin_submit_ticket.php
@@ -176,11 +176,11 @@ foreach ($hesk_settings['custom_fields'] as $k=>$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 9743f626..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'])) {
@@ -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');
}
@@ -1605,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;
?>
@@ -1766,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']) {
@@ -1775,66 +1860,12 @@ function hesk_printTicketReplies()
echo ' ';
}
- while ($reply = hesk_dbFetchAssoc($result)) {
- $reply['dt'] = hesk_date($reply['dt'], true);
- ?>
- -
-
-
-
-
-
-
-
-
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ ' . $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/delete_tickets.php b/admin/delete_tickets.php
index 7bc77b44..45a7eaac 100644
--- a/admin/delete_tickets.php
+++ b/admin/delete_tickets.php
@@ -81,10 +81,10 @@ $i = 0;
// Possible priorities
$priorities = array(
- 'critical' => array('value' => 0, 'text' => $hesklang['critical'], 'formatted' => '' . $hesklang['critical'] . ''),
- 'high' => array('value' => 1, 'text' => $hesklang['high'], 'formatted' => '' . $hesklang['high'] . ''),
- 'medium' => array('value' => 2, 'text' => $hesklang['medium'], 'formatted' => '' . $hesklang['medium'] . ''),
- 'low' => array('value' => 3, 'text' => $hesklang['low'], 'formatted' => $hesklang['low']),
+ 'critical' => array('value' => 0, 'lang' => 'critical', 'text' => $hesklang['critical'], 'formatted' => '' . $hesklang['critical'] . ''),
+ 'high' => array('value' => 1, 'lang' => 'high', 'text' => $hesklang['high'], 'formatted' => '' . $hesklang['high'] . ''),
+ 'medium' => array('value' => 2, 'lang' => 'medium', 'text' => $hesklang['medium'], 'formatted' => '' . $hesklang['medium'] . ''),
+ 'low' => array('value' => 3, 'lang' => 'low', 'text' => $hesklang['low'], 'formatted' => $hesklang['low']),
);
// Change priority
@@ -113,8 +113,10 @@ if (array_key_exists($_POST['a'], $priorities)) {
hesk_okCategory($ticket['category']);
- $revision = sprintf($hesklang['thist8'], hesk_date(), $priority['formatted'], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')');
- hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `priority`='{$priority['value']}', `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `id`={$this_id}");
+ hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `priority`='{$priority['value']}' WHERE `id`={$this_id}");
+ mfh_insert_audit_trail_record($this_id, 'TICKET', 'audit_priority', hesk_date(),
+ array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')',
+ 1 => $priority['lang']));
$i++;
}
@@ -133,8 +135,6 @@ elseif ($_POST['a'] == 'delete') {
require(HESK_PATH . 'inc/email_functions.inc.php');
}
- $revision = sprintf($hesklang['thist3'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')');
-
foreach ($_POST['id'] as $this_id) {
if (is_array($this_id)) {
continue;
@@ -222,8 +222,6 @@ else {
hesk_token_check('POST');
require(HESK_PATH . 'inc/email_functions.inc.php');
- $revision = sprintf($hesklang['thist3'], hesk_date(), $_SESSION['name'] . ' (' . $_SESSION['user'] . ')');
-
foreach ($_POST['id'] as $this_id) {
if (is_array($this_id)) {
continue;
@@ -239,7 +237,11 @@ else {
$closedStatusRS = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses` WHERE `IsStaffClosedOption` = 1");
$closedStatus = hesk_dbFetchAssoc($closedStatusRS);
- hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`='" . $closedStatus['ID'] . "', `closedat`=NOW(), `closedby`=" . intval($_SESSION['id']) . ", `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `id`='" . intval($this_id) . "'");
+ hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`='" . $closedStatus['ID'] . "', `closedat`=NOW(), `closedby`=" . intval($_SESSION['id']) . " WHERE `id`='" . intval($this_id) . "'");
+
+ mfh_insert_audit_trail_record($this_id, 'TICKET', 'audit_closed', hesk_date(),
+ array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'));
+
$i++;
// Notify customer of closed ticket?
@@ -284,6 +286,14 @@ function hesk_fullyDeleteTicket()
/* Delete ticket notes */
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "notes` WHERE `ticket`='" . intval($ticket['id']) . "'");
+ /* Delete audit trail records */
+ hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail_to_replacement_values`
+ WHERE `audit_trail_id` IN (
+ SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail`
+ WHERE `entity_type` = 'TICKET' AND `entity_id` = " . intval($ticket['id']) . ")");
+ hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail` WHERE `entity_type`='TICKET'
+ AND `entity_id` = " . intval($ticket['id']));
+
/* Delete ticket reply drafts */
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "reply_drafts` WHERE `ticket`=" . intval($ticket['id']));
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');
- }
+ $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;
- 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);
+ // 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/move_category.php b/admin/move_category.php
index e049255e..35971e02 100755
--- a/admin/move_category.php
+++ b/admin/move_category.php
@@ -70,8 +70,6 @@ if (hesk_dbNumRows($res) != 1) {
}
$ticket = hesk_dbFetchAssoc($res);
-/* Log that ticket is being moved */
-$history = sprintf($hesklang['thist1'], hesk_date(), $row['name'], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')');
/* Is the ticket assigned to someone? If yes, check that the user has access to category or change to unassigned */
$need_to_reassign = 0;
@@ -92,18 +90,30 @@ if ($ticket['owner']) {
}
/* Reassign automatically if possible */
+$autoassign_owner = null;
if ($need_to_reassign || !$ticket['owner']) {
$need_to_reassign = 1;
$autoassign_owner = hesk_autoAssignTicket($category);
if ($autoassign_owner) {
$ticket['owner'] = $autoassign_owner['id'];
- $history .= sprintf($hesklang['thist10'], hesk_date(), $autoassign_owner['name'] . ' (' . $autoassign_owner['user'] . ')');
} else {
$ticket['owner'] = 0;
}
}
-hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `category`='" . intval($category) . "', `owner`='" . intval($ticket['owner']) . "' , `history`=CONCAT(`history`,'" . hesk_dbEscape($history) . "') WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'");
+hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `category`='" . intval($category) . "', `owner`='" . intval($ticket['owner']) . "' WHERE `trackid`='" . hesk_dbEscape($trackingID) . "'");
+
+/* Log that ticket is being moved */
+mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_moved_category', hesk_date(), array(
+ 0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')',
+ 1 => $row['name']
+));
+
+if ($autoassign_owner) {
+ mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_autoassigned', hesk_date(), array(
+ 0 => $autoassign_owner['name'] . ' (' . $autoassign_owner['user'] . ')'
+ ));
+}
$ticket['category'] = $category;
diff --git a/admin/priority.php b/admin/priority.php
new file mode 100644
index 00000000..ac86750f
--- /dev/null
+++ b/admin/priority.php
@@ -0,0 +1,73 @@
+ 3)
+{
+ hesk_process_messages($hesklang['inpr'],'admin_ticket.php?track='.$trackingID.'&Refresh='.mt_rand(10000,99999),'NOTICE');
+}
+
+$options = array(
+ 0 => ''.$hesklang['critical'].'',
+ 1 => ''.$hesklang['high'].'',
+ 2 => ''.$hesklang['medium'].'',
+ 3 => $hesklang['low']
+);
+
+$plain_options = array(
+ 0 => 'critical',
+ 1 => 'high',
+ 2 => 'medium',
+ 3 => 'low'
+);
+
+$ticketRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `trackid` = '" . hesk_dbEscape($trackingID) . "'");
+$ticket = hesk_dbFetchAssoc($ticketRs);
+
+hesk_dbQuery("UPDATE `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` SET `priority`='{$priority}' WHERE `trackid`='".hesk_dbEscape($trackingID)."'");
+
+mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_priority', hesk_date(), array(
+ 0 => $_SESSION['name'].' ('.$_SESSION['user'].')',
+ 1 => $plain_options[$priority]
+));
+
+if (hesk_dbAffectedRows() != 1)
+{
+ hesk_process_messages($hesklang['inpr'],'admin_ticket.php?track='.$trackingID.'&Refresh='.mt_rand(10000,99999),'NOTICE');
+}
+
+hesk_process_messages(sprintf($hesklang['chpri2'],$options[$priority]),'admin_ticket.php?track='.$trackingID.'&Refresh='.mt_rand(10000,99999),'SUCCESS');
+?>
diff --git a/api/BusinessLogic/DateTimeHelpers.php b/api/BusinessLogic/DateTimeHelpers.php
new file mode 100644
index 00000000..6cea5cb2
--- /dev/null
+++ b/api/BusinessLogic/DateTimeHelpers.php
@@ -0,0 +1,19 @@
+email), $msg);
+ $msg = str_replace('%%EMAIL%%', implode(';', $ticket->email), $msg);
$msg = str_replace('%%CREATED%%', $ticket->dateCreated, $msg);
$msg = str_replace('%%UPDATED%%', $ticket->lastChanged, $msg);
$msg = str_replace('%%ID%%', $ticket->id, $msg);
diff --git a/api/BusinessLogic/Security/UserContext.php b/api/BusinessLogic/Security/UserContext.php
index ee1522a9..70a907ac 100644
--- a/api/BusinessLogic/Security/UserContext.php
+++ b/api/BusinessLogic/Security/UserContext.php
@@ -55,6 +55,21 @@ class UserContext extends \BaseClass {
/* @var $active bool */
public $active;
+ static function buildAnonymousUser() {
+ $userContext = new UserContext();
+ $userContext->id = -1;
+ $userContext->username = "API - ANONYMOUS USER"; // Usernames can't have spaces, so no one will take this username
+ $userContext->admin = false;
+ $userContext->name = "ANONYMOUS USER";
+ $userContext->email = "anonymous-user@example.com";
+ $userContext->categories = array();
+ $userContext->permissions = array();
+ $userContext->autoAssign = false;
+ $userContext->active = true;
+
+ return $userContext;
+ }
+
/**
* Builds a user context based on the current session. **The session must be active!**
* @param $dataRow array the $_SESSION superglobal or the hesk_users result set
diff --git a/api/BusinessLogic/Tickets/AuditTrail.php b/api/BusinessLogic/Tickets/AuditTrail.php
new file mode 100644
index 00000000..46993521
--- /dev/null
+++ b/api/BusinessLogic/Tickets/AuditTrail.php
@@ -0,0 +1,24 @@
+id = intval($row['id']);
$ticket->trackingId = $row['trackid'];
@@ -143,6 +143,7 @@ class Ticket extends \BaseClass {
$replies[$reply->id] = $reply;
}
$ticket->replies = $replies;
+ $ticket->auditTrail = $auditRecords;
return $ticket;
}
@@ -163,7 +164,7 @@ class Ticket extends \BaseClass {
public $name;
/**
- * @var array|null
+ * @var string[]|null
*/
public $email;
@@ -309,6 +310,11 @@ class Ticket extends \BaseClass {
*/
public $auditTrailHtml;
+ /**
+ * @var AuditTrail
+ */
+ public $auditTrail;
+
/**
* @var string[]
*/
diff --git a/api/BusinessLogic/Tickets/TicketCreator.php b/api/BusinessLogic/Tickets/TicketCreator.php
index 5956de9b..4175ce0e 100644
--- a/api/BusinessLogic/Tickets/TicketCreator.php
+++ b/api/BusinessLogic/Tickets/TicketCreator.php
@@ -2,11 +2,13 @@
namespace BusinessLogic\Tickets;
+use BusinessLogic\DateTimeHelpers;
use BusinessLogic\Emails\Addressees;
use BusinessLogic\Emails\EmailSenderHelper;
use BusinessLogic\Emails\EmailTemplateRetriever;
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Statuses\DefaultStatusForAction;
+use DataAccess\AuditTrail\AuditTrailGateway;
use DataAccess\Security\UserGateway;
use DataAccess\Settings\ModsForHeskSettingsGateway;
use DataAccess\Statuses\StatusGateway;
@@ -56,6 +58,9 @@ class TicketCreator extends \BaseClass {
/* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */
private $modsForHeskSettingsGateway;
+ /* @var $auditTrailGateway AuditTrailGateway */
+ private $auditTrailGateway;
+
function __construct(NewTicketValidator $newTicketValidator,
TrackingIdGenerator $trackingIdGenerator,
Autoassigner $autoassigner,
@@ -64,7 +69,8 @@ class TicketCreator extends \BaseClass {
VerifiedEmailChecker $verifiedEmailChecker,
EmailSenderHelper $emailSenderHelper,
UserGateway $userGateway,
- ModsForHeskSettingsGateway $modsForHeskSettingsGateway) {
+ ModsForHeskSettingsGateway $modsForHeskSettingsGateway,
+ AuditTrailGateway $auditTrailGateway) {
$this->newTicketValidator = $newTicketValidator;
$this->trackingIdGenerator = $trackingIdGenerator;
$this->autoassigner = $autoassigner;
@@ -74,6 +80,7 @@ class TicketCreator extends \BaseClass {
$this->emailSenderHelper = $emailSenderHelper;
$this->userGateway = $userGateway;
$this->modsForHeskSettingsGateway = $modsForHeskSettingsGateway;
+ $this->auditTrailGateway = $auditTrailGateway;
}
/**
@@ -113,7 +120,7 @@ class TicketCreator extends \BaseClass {
// Transform one-to-one properties
$ticket->name = $ticketRequest->name;
- $ticket->email = $ticketRequest->email;
+ $ticket->email = $this->getAddressees($ticketRequest->email);
$ticket->priorityId = $ticketRequest->priority;
$ticket->categoryId = $ticketRequest->category;
$ticket->subject = $ticketRequest->subject;
@@ -147,8 +154,13 @@ class TicketCreator extends \BaseClass {
$ticket->timeWorked = '00:00:00';
$ticket->lastReplier = 0;
+ $this->auditTrailGateway->insertAuditTrailRecord($ticket->id, AuditTrailEntityType::TICKET,
+ 'audit_created', DateTimeHelpers::heskDate($heskSettings), array(
+ 0 => $ticket->name
+ ), $heskSettings);
+
$addressees = new Addressees();
- $addressees->to = $this->getAddressees($ticket->email);
+ $addressees->to = $ticket->email;
if ($ticketRequest->sendEmailToCustomer && $emailVerified) {
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_TICKET, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings);
diff --git a/api/DataAccess/AuditTrail/AuditTrailGateway.php b/api/DataAccess/AuditTrail/AuditTrailGateway.php
new file mode 100644
index 00000000..0c174706
--- /dev/null
+++ b/api/DataAccess/AuditTrail/AuditTrailGateway.php
@@ -0,0 +1,28 @@
+init();
+
+ hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail` (`entity_id`, `entity_type`,
+ `language_key`, `date`) VALUES (" . intval($entityId) . ", '" . hesk_dbEscape($entityType) . "',
+ '" . hesk_dbEscape($languageKey) . "', '" . hesk_dbEscape($date) . "')");
+
+ $auditId = hesk_dbInsertID();
+
+ foreach ($replacementValues as $replacementIndex => $replacementValue) {
+ hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail_to_replacement_values`
+ (`audit_trail_id`, `replacement_index`, `replacement_value`) VALUES (" . intval($auditId) . ",
+ " . intval($replacementIndex) . ", '" . hesk_dbEscape($replacementValue) . "')");
+ }
+
+ $this->close();
+
+ return $auditId;
+ }
+}
\ No newline at end of file
diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php
index a14d623e..4c72a9f2 100644
--- a/api/DataAccess/Tickets/TicketGateway.php
+++ b/api/DataAccess/Tickets/TicketGateway.php
@@ -5,6 +5,8 @@ namespace DataAccess\Tickets;
use BusinessLogic\Attachments\AttachmentType;
use BusinessLogic\Tickets\Attachment;
+use BusinessLogic\Tickets\AuditTrail;
+use BusinessLogic\Tickets\AuditTrailEntityType;
use BusinessLogic\Tickets\Ticket;
use BusinessLogic\Tickets\TicketGatewayGeneratedFields;
use DataAccess\CommonDao;
@@ -29,7 +31,42 @@ class TicketGateway extends CommonDao {
$repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($id) . " ORDER BY `id` ASC");
- $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings);
+ $auditTrailRs = hesk_dbQuery("SELECT `audit`.`id`, `audit`.`language_key`, `audit`.`date`,
+ `values`.`replacement_index`, `values`.`replacement_value`
+ FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail` AS `audit`
+ LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail_to_replacement_values` AS `values`
+ ON `audit`.`id` = `values`.`audit_trail_id`
+ WHERE `entity_type` = 'TICKET' AND `entity_id` = " . intval($id) . "
+ ORDER BY `audit`.`date` ASC");
+
+ $auditRecords = array();
+
+ /* @var $currentAuditRecord AuditTrail|null */
+ $currentAuditRecord = null;
+ while ($auditRow = hesk_dbFetchAssoc($auditTrailRs)) {
+ if ($currentAuditRecord == null || $currentAuditRecord->id != $auditRow['id']) {
+ if ($currentAuditRecord != null) {
+ $auditRecords[] = $currentAuditRecord;
+ }
+ $currentAuditRecord = new AuditTrail();
+ $currentAuditRecord->id = $auditRow['id'];
+ $currentAuditRecord->entityId = $id;
+ $currentAuditRecord->entityType = AuditTrailEntityType::TICKET;
+ $currentAuditRecord->languageKey = $auditRow['language_key'];
+ $currentAuditRecord->date = $auditRow['date'];
+ $currentAuditRecord->replacementValues = array();
+ }
+
+ if ($auditRow['replacement_index'] != null) {
+ $currentAuditRecord->replacementValues[intval($auditRow['replacement_index'])] = $auditRow['replacement_value'];
+ }
+ }
+
+ if ($currentAuditRecord != null) {
+ $auditRecords[] = $currentAuditRecord;
+ }
+
+ $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $auditRecords, $heskSettings);
$this->close();
@@ -92,7 +129,7 @@ class TicketGateway extends CommonDao {
function getTicketByTrackingId($trackingId, $heskSettings) {
$this->init();
- $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `trackid` = " . intval($trackingId));
+ $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `trackid` = '" . hesk_dbEscape($trackingId) . "'");
if (hesk_dbNumRows($rs) === 0) {
return null;
}
@@ -101,7 +138,40 @@ class TicketGateway extends CommonDao {
$linkedTicketsRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($trackingId));
$repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($row['id']) . " ORDER BY `id` ASC");
- $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings);
+ $audiTrailRs = hesk_dbQuery("SELECT `audit`.`id`, `audit`.`language_key`, `audit`.`date`,
+ `values`.`replacement_index`, `values`.`replacement_value`
+ FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail` AS `audit`
+ LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail_to_replacement_values` AS `values`
+ ON `audit`.`id` = `values`.`audit_trail_id`
+ WHERE `entity_type` = 'TICKET' AND `entity_id` = " . intval($row['id']));
+ $auditRecords = array();
+
+ /* @var $currentAuditRecord AuditTrail */
+ $currentAuditRecord = null;
+ while ($auditRow = hesk_dbFetchAssoc($audiTrailRs)) {
+ if ($currentAuditRecord == null || $currentAuditRecord->id != $auditRow['id']) {
+ if ($currentAuditRecord != null) {
+ $auditRecords[] = $currentAuditRecord;
+ }
+ $currentAuditRecord = new AuditTrail();
+ $currentAuditRecord->id = $auditRow['id'];
+ $currentAuditRecord->entityId = $row['id'];
+ $currentAuditRecord->entityType = AuditTrailEntityType::TICKET;
+ $currentAuditRecord->languageKey = $auditRow['language_key'];
+ $currentAuditRecord->date = $auditRow['date'];
+ $currentAuditRecord->replacementValues = array();
+ }
+
+ if ($auditRow['replacement_index'] != null) {
+ $currentAuditRecord->replacementValues[intval($auditRow['replacement_index'])] = $auditRow['replacement_value'];
+ }
+ }
+
+ if ($currentAuditRecord != null) {
+ $auditRecords[] = $currentAuditRecord;
+ }
+
+ $ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $auditRecords, $heskSettings);
$this->close();
@@ -170,6 +240,8 @@ class TicketGateway extends CommonDao {
$ipAddress = $ticket->ipAddress !== null
&& $ticket->ipAddress !== '' ? $ticket->ipAddress : '';
+ $emailAddresses = implode(';', $ticket->email);
+
$tableName = $isEmailVerified ? 'tickets' : 'stage_tickets';
$sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . $tableName ."`
@@ -205,7 +277,7 @@ class TicketGateway extends CommonDao {
(
'" . hesk_dbEscape($ticket->trackingId) . "',
'" . hesk_dbEscape($ticket->name) . "',
- '" . hesk_dbEscape($ticket->email) . "',
+ '" . hesk_dbEscape($emailAddresses) . "',
'" . intval($ticket->categoryId) . "',
'" . intval($ticket->priorityId) . "',
'" . hesk_dbEscape($ticket->subject) . "',
diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php
index c2577ab0..69d817d6 100644
--- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php
+++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php
@@ -18,6 +18,7 @@ use BusinessLogic\Tickets\TrackingIdGenerator;
use BusinessLogic\Tickets\VerifiedEmailChecker;
use BusinessLogic\ValidationModel;
use Core\Constants\Priority;
+use DataAccess\AuditTrail\AuditTrailGateway;
use DataAccess\Security\UserGateway;
use DataAccess\Settings\ModsForHeskSettingsGateway;
use DataAccess\Statuses\StatusGateway;
@@ -97,6 +98,9 @@ class CreateTicketTest extends TestCase {
/* @var $modsForHeskSettings array */
private $modsForHeskSettings;
+ /* @var $auditTrailGateway \PHPUnit_Framework_MockObject_MockObject|AuditTrailGateway */
+ private $auditTrailGateway;
+
protected function setUp() {
$this->ticketGateway = $this->createMock(TicketGateway::clazz());
$this->newTicketValidator = $this->createMock(NewTicketValidator::clazz());
@@ -107,10 +111,11 @@ class CreateTicketTest extends TestCase {
$this->emailSenderHelper = $this->createMock(EmailSenderHelper::clazz());
$this->userGateway = $this->createMock(UserGateway::clazz());
$this->modsForHeskSettingsGateway = $this->createMock(ModsForHeskSettingsGateway::clazz());
+ $this->auditTrailGateway = $this->createMock(AuditTrailGateway::clazz());
$this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator,
$this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker,
- $this->emailSenderHelper, $this->userGateway, $this->modsForHeskSettingsGateway);
+ $this->emailSenderHelper, $this->userGateway, $this->modsForHeskSettingsGateway, $this->auditTrailGateway);
$this->ticketRequest = new CreateTicketByCustomerModel();
$this->ticketRequest->name = 'Name';
@@ -126,7 +131,8 @@ class CreateTicketTest extends TestCase {
'require_subject' => 1,
'require_message' => 1,
'custom_fields' => array(),
- 'autoassign' => 0
+ 'autoassign' => 0,
+ 'timeformat' => 'Y-m-d',
);
$this->modsForHeskSettings = array(
'customer_email_verification_required' => false
@@ -222,7 +228,7 @@ class CreateTicketTest extends TestCase {
//-- Assert
self::assertThat($ticket->ticket->name, self::equalTo($this->ticketRequest->name));
- self::assertThat($ticket->ticket->email, self::equalTo($this->ticketRequest->email));
+ self::assertThat($ticket->ticket->email[0], self::equalTo($this->ticketRequest->email));
self::assertThat($ticket->ticket->priorityId, self::equalTo($this->ticketRequest->priority));
self::assertThat($ticket->ticket->categoryId, self::equalTo($this->ticketRequest->category));
self::assertThat($ticket->ticket->subject, self::equalTo($this->ticketRequest->subject));
diff --git a/api/composer.lock b/api/composer.lock
index c5a1a92d..f8503360 100644
--- a/api/composer.lock
+++ b/api/composer.lock
@@ -829,12 +829,12 @@
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
- "reference": "7b50ab4d6c9fdaa1ed53ae310c955900af6e3372"
+ "reference": "d39a8798c473b6b896059a8de576598e4d17d992"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-http/discovery/zipball/7b50ab4d6c9fdaa1ed53ae310c955900af6e3372",
- "reference": "7b50ab4d6c9fdaa1ed53ae310c955900af6e3372",
+ "url": "https://api.github.com/repos/php-http/discovery/zipball/d39a8798c473b6b896059a8de576598e4d17d992",
+ "reference": "d39a8798c473b6b896059a8de576598e4d17d992",
"shasum": ""
},
"require": {
@@ -883,7 +883,7 @@
"message",
"psr7"
],
- "time": "2017-08-03 10:12:53"
+ "time": "2017-09-13 14:06:45"
},
{
"name": "php-http/guzzle6-adapter",
@@ -1624,23 +1624,21 @@
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
+ "reference": "7af8066e48b8a4cbd90849bbe9234ab444057968"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
- "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/7af8066e48b8a4cbd90849bbe9234ab444057968",
+ "reference": "7af8066e48b8a4cbd90849bbe9234ab444057968",
"shasum": ""
},
"require": {
"php": "^7.1"
},
"require-dev": {
- "athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
- "phpunit/phpunit": "^6.2.3",
- "squizlabs/php_codesniffer": "^3.0.2"
+ "phpunit/phpunit": "^6.2.3"
},
"type": "library",
"extra": {
@@ -1670,7 +1668,7 @@
"constructor",
"instantiate"
],
- "time": "2017-07-22 11:58:36"
+ "time": "2017-09-19T12:41:22+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -1822,12 +1820,12 @@
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "a046af61c36e9162372f205de091a1cab7340f1c"
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/a046af61c36e9162372f205de091a1cab7340f1c",
- "reference": "a046af61c36e9162372f205de091a1cab7340f1c",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+ "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"shasum": ""
},
"require": {
@@ -1868,7 +1866,7 @@
"reflection",
"static analysis"
],
- "time": "2017-04-30 11:58:12"
+ "time": "2017-09-11T18:02:19+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
diff --git a/api/index.php b/api/index.php
index 393455a4..4766701c 100644
--- a/api/index.php
+++ b/api/index.php
@@ -43,7 +43,10 @@ function internalOrAuthHandler() {
}
function publicHandler() {
- //-- No-op
+ global $userContext;
+
+ //-- Create an "anonymous" UserContext
+ $userContext = \BusinessLogic\Security\UserContext::buildAnonymousUser();
}
function assertApiIsEnabled() {
@@ -188,7 +191,7 @@ Link::all(array(
'/v1/categories/{i}' => action(\Controllers\Categories\CategoryController::clazz(), array(RequestMethod::GET, RequestMethod::PUT, RequestMethod::DELETE), SecurityHandler::INTERNAL_OR_AUTH_TOKEN),
'/v1-internal/categories/{i}/sort/{s}' => action(\Controllers\Categories\CategoryController::clazz() . '::sort', array(RequestMethod::POST), SecurityHandler::INTERNAL),
// Tickets
- '/v1/tickets' => action(\Controllers\Tickets\CustomerTicketController::clazz(), RequestMethod::all()),
+ '/v1/tickets' => action(\Controllers\Tickets\CustomerTicketController::clazz(), RequestMethod::all(), SecurityHandler::OPEN),
// Tickets - Staff
'/v1/staff/tickets/{i}' => action(\Controllers\Tickets\StaffTicketController::clazz(), RequestMethod::all()),
// Attachments
diff --git a/change_status.php b/change_status.php
index 5fee55e1..133a7fda 100644
--- a/change_status.php
+++ b/change_status.php
@@ -52,7 +52,7 @@ if ($status == 3) // Closed
}
$status = $closedStatus;
$action = $hesklang['closed'];
- $revision = sprintf($hesklang['thist3'], hesk_date(), $hesklang['customer']);
+ $revision_key = 'audit_closed';
if ($hesk_settings['custopen'] != 1) {
$locked = 1;
@@ -73,7 +73,7 @@ if ($status == 3) // Closed
$status = $statusRow['ID'];
$action = $hesklang['opened'];
- $revision = sprintf($hesklang['thist4'], hesk_date(), $hesklang['customer']);
+ $revision_key = 'audit_opened';
// We will ask the customer why is the ticket being reopened
$_SESSION['force_form_top'] = true;
@@ -94,12 +94,9 @@ hesk_verifyEmailMatch($trackingID);
$_SESSION['t_track'] = $trackingID;
$_SESSION['t_email'] = $hesk_settings['e_email'];
-// Load statuses
-require_once(HESK_PATH . 'inc/statuses.inc.php');
-
// Is current ticket status even changeable by customers?
-$ticket = hesk_dbFetchAssoc( hesk_dbQuery( "SELECT `status`, `staffreplies`, `lastreplier` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` WHERE `trackid`='".hesk_dbEscape($trackingID)."' LIMIT 1") );
-if (!hesk_can_customer_change_status($ticket['status'])) {
+$ticket = hesk_dbFetchAssoc( hesk_dbQuery( "SELECT `id`, `status`, `staffreplies`, `lastreplier` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` WHERE `trackid`='".hesk_dbEscape($trackingID)."' LIMIT 1") );
+if (!mfh_can_customer_change_status($ticket['status'])) {
hesk_process_messages($hesklang['scno'],'ticket.php');
}
@@ -121,7 +118,10 @@ if ($oldStatus == 2) {
// Modify values in the database
-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) . "' AND `locked` != '1'");
+hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status`='{$status}', `locked`='{$locked}' $closedby_sql WHERE `trackid`='" . hesk_dbEscape($trackingID) . "' AND `locked` != '1'");
+
+// Insert audit trail record
+mfh_insert_audit_trail_record($ticket['id'], 'TICKET', $revision_key, hesk_date(), array(0 => $hesklang['customer']));
// Did we modify anything*
if (hesk_dbAffectedRows() != 1) {
diff --git a/css/mods-for-hesk-new.css b/css/mods-for-hesk-new.css
index 756fd12c..d1dec79a 100644
--- a/css/mods-for-hesk-new.css
+++ b/css/mods-for-hesk-new.css
@@ -350,4 +350,9 @@ div.ticket-info {
border-color: #3c8dbc;
box-shadow: none;
outline: 0;
+}
+
+.timeline > li > .timeline-item > .timeline-header.audit-record {
+ font-size: 12px;
+ border-bottom: none;
}
\ No newline at end of file
diff --git a/inc/admin_functions.inc.php b/inc/admin_functions.inc.php
index f2edf27a..cadb5a88 100644
--- a/inc/admin_functions.inc.php
+++ b/inc/admin_functions.inc.php
@@ -201,7 +201,10 @@ function hesk_mergeTickets($merge_these, $merge_into)
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `id`='" . intval($row['id']) . "'");
/* Log that ticket has been merged */
- $history .= sprintf($hesklang['thist13'], hesk_date(), $row['trackid'], $_SESSION['name'] . ' (' . $_SESSION['user'] . ')');
+ mfh_insert_audit_trail_record($merge_into, 'TICKET', 'audit_merged', hesk_date(), array(
+ 0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')',
+ 1 => $row['trackid']
+ ));
/* Add old ticket ID to target ticket "merged" field */
$merged .= '#' . $row['trackid'];
@@ -234,7 +237,7 @@ function hesk_mergeTickets($merge_these, $merge_into)
}
/* Update history (log) and merged IDs of target ticket */
- hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET $replies_sql `time_worked`=ADDTIME(`time_worked`, '" . hesk_dbEscape($sec_worked) . "'), `merged`=CONCAT(`merged`,'" . hesk_dbEscape($merged . '#') . "'), `history`=CONCAT(`history`,'" . hesk_dbEscape($history) . "') WHERE `id`='" . intval($merge_into) . "'");
+ hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET $replies_sql `time_worked`=ADDTIME(`time_worked`, '" . hesk_dbEscape($sec_worked) . "'), `merged`=CONCAT(`merged`,'" . hesk_dbEscape($merged . '#') . "') WHERE `id`='" . intval($merge_into) . "'");
return true;
@@ -419,14 +422,17 @@ function hesk_autoLogin($noredirect = 0)
hesk_setcookie('hesk_p', "$hash", strtotime('+1 year'));
/* Close any old tickets here so Cron jobs aren't necessary */
- if ($hesk_settings['autoclose']) {
- $revision = sprintf($hesklang['thist3'], hesk_date(), $hesklang['auto']);
+ $closedStatusRs = hesk_dbQuery('SELECT `ID`, `Closable` FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'statuses` WHERE `IsDefaultStaffReplyStatus` = 1');
+ $closedStatus = hesk_dbFetchAssoc($closedStatusRs);
+ // Are we allowed to close tickets in this status?
+ if (($closedStatus['Closable'] == 'yes' || $closedStatus['Closable'] == 'sonly') && $hesk_settings['autoclose']) {
$dt = date('Y-m-d H:i:s', time() - $hesk_settings['autoclose'] * 86400);
// 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` = '2' AND `lastchange` <= '" . hesk_dbEscape($dt) . "' ");
+ $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;
@@ -439,6 +445,7 @@ function hesk_autoLogin($noredirect = 0)
$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());
$modsForHesk_settings = mfh_getSettings();
hesk_notifyCustomer($modsForHesk_settings, 'ticket_closed');
}
@@ -446,7 +453,9 @@ function hesk_autoLogin($noredirect = 0)
}
// Update ticket statuses and history in database
- hesk_dbQuery("UPDATE `" . $hesk_settings['db_pfix'] . "tickets` SET `status`='3', `closedat`=NOW(), `closedby`='-1', `history`=CONCAT(`history`,'" . hesk_dbEscape($revision) . "') WHERE `status` = '2' AND `lastchange` <= '" . hesk_dbEscape($dt) . "' ");
+ $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`= " . $defaultCloseStatus['ID'] . ", `closedat`=NOW(), `closedby`='-1' WHERE `status` = " . $closedStatus['ID'] . " AND `lastchange` <= '" . hesk_dbEscape($dt) . "' ");
}
/* If session expired while a HESK page is open just continue using it, don't redirect */
diff --git a/inc/common.inc.php b/inc/common.inc.php
index 94f487dc..be8f07eb 100644
--- a/inc/common.inc.php
+++ b/inc/common.inc.php
@@ -2155,4 +2155,32 @@ function mfh_get_hidden_fields_for_language($keys) {
$output .= '
';
return $output;
-}
\ No newline at end of file
+}
+
+function mfh_insert_audit_trail_record($entity_id, $entity_type, $language_key, $date, $replacement_values = array()) {
+ global $hesk_settings;
+
+ hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail` (`entity_id`, `entity_type`,
+ `language_key`, `date`) VALUES (" . intval($entity_id) . ", '" . hesk_dbEscape($entity_type) . "',
+ '" . hesk_dbEscape($language_key) . "', '" . hesk_dbEscape($date) . "')");
+
+ $audit_id = hesk_dbInsertID();
+
+ foreach ($replacement_values as $replacement_index => $replacement_value) {
+ hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail_to_replacement_values`
+ (`audit_trail_id`, `replacement_index`, `replacement_value`) VALUES (" . intval($audit_id) . ",
+ " . intval($replacement_index) . ", '" . hesk_dbEscape($replacement_value) . "')");
+ }
+
+ return $audit_id;
+}
+
+function mfh_can_customer_change_status($status)
+{
+ global $hesk_settings;
+
+ $res = hesk_dbQuery("SELECT `Closable` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses` WHERE `ID` = " . intval($status));
+ $row = hesk_dbFetchAssoc($res);
+
+ return $row['Closable'] == 'yes' || $row['Closable'] == 'conly';
+} // END hesk_get_ticket_status()
\ No newline at end of file
diff --git a/inc/pipe_functions.inc.php b/inc/pipe_functions.inc.php
index 4b92df4a..7a98f02d 100755
--- a/inc/pipe_functions.inc.php
+++ b/inc/pipe_functions.inc.php
@@ -269,7 +269,6 @@ function hesk_email2ticket($results, $pop3 = 0, $set_category = 1, $set_priority
// Auto assign tickets if aplicable
$tmpvar['owner'] = 0;
- $tmpvar['history'] = $pop3 ? sprintf($hesklang['thist16'], hesk_date()) : sprintf($hesklang['thist11'], hesk_date());
$tmpvar['openedby'] = $pop3 ? -2 : -1;
$autoassign_owner = hesk_autoAssignTicket($tmpvar['category']);
@@ -278,7 +277,6 @@ function hesk_email2ticket($results, $pop3 = 0, $set_category = 1, $set_priority
if ($autoassign_owner) {
$tmpvar['owner'] = $autoassign_owner['id'];
- $tmpvar['history'] .= sprintf($hesklang['thist10'], hesk_date(), $autoassign_owner['name'] . ' (' . $autoassign_owner['user'] . ')');
}
// Custom fields will be empty as there is no reliable way of detecting them
@@ -297,6 +295,13 @@ function hesk_email2ticket($results, $pop3 = 0, $set_category = 1, $set_priority
// Insert ticket to database
$ticket = hesk_newTicket($tmpvar);
+ mfh_insert_audit_trail_record($ticket['id'], 'TICKET', ($pop3 ? 'audit_submitted_via_pop' : 'audit_submitted_via_piping'), hesk_date());
+
+ 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
if ($hesk_settings['notify_new']) {
$possible_SPAM = false;
diff --git a/inc/posting_functions.inc.php b/inc/posting_functions.inc.php
index a2cd5221..7e81bfbf 100644
--- a/inc/posting_functions.inc.php
+++ b/inc/posting_functions.inc.php
@@ -115,7 +115,7 @@ function hesk_newTicket($ticket, $isVerified = true)
" . hesk_dbEscape($ticket['screen_resolution_height']) . ",
" . hesk_dbEscape($ticket['screen_resolution_width']) . ",
{$due_date},
- '" . hesk_dbEscape($ticket['history']) . "'
+ ''
{$custom_what}
)
");
diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php
index 18cb6dc4..19991d8c 100644
--- a/install/mods-for-hesk/sql/installSql.php
+++ b/install/mods-for-hesk/sql/installSql.php
@@ -1137,9 +1137,9 @@ function execute320Scripts() {
hesk_dbConnect();
executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories`
- ADD COLUMN `mfh_description` VARCHAR(255)");
+ ADD COLUMN `mfh_description` TEXT");
executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_fields`
- ADD COLUMN `mfh_description` VARCHAR(255)");
+ ADD COLUMN `mfh_description` TEXT");
// Purge the custom field caches as we're adding a new field
foreach ($hesk_settings['languages'] as $key => $value) {
@@ -1147,5 +1147,17 @@ function execute320Scripts() {
hesk_unlink(HESK_PATH . "cache/cf_{$language_hash}.cache.php");
}
+ executeQuery("CREATE TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail` (
+ `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `entity_id` INT NOT NULL,
+ `entity_type` VARCHAR(50) NOT NULL,
+ `language_key` VARCHAR(100) NOT NULL,
+ `date` TIMESTAMP NOT NULL)");
+ executeQuery("CREATE TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "audit_trail_to_replacement_values` (
+ `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `audit_trail_id` INT NOT NULL,
+ `replacement_index` INT NOT NULL,
+ `replacement_value` TEXT NOT NULL)");
+
updateVersion('3.2.0');
}
\ No newline at end of file
diff --git a/internal-api/dao/calendar_dao.php b/internal-api/dao/calendar_dao.php
index 9c87e458..a3f15d98 100644
--- a/internal-api/dao/calendar_dao.php
+++ b/internal-api/dao/calendar_dao.php
@@ -194,12 +194,25 @@ function delete_event($id, $hesk_settings) {
}
function update_ticket_due_date($ticket, $hesk_settings) {
+ $ticket_id_rs = hesk_dbQuery("SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `trackid` = '" . hesk_dbEscape($ticket['trackid']) . "'");
+ $ticket_id = hesk_dbFetchAssoc($ticket_id_rs);
+
$due_date = 'NULL';
+ $language_key = 'audit_due_date_removed';
+ $audit_array = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')');
if ($ticket['due_date'] != NULL) {
+ $audit_array = array(
+ 0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')',
+ 1 => date('Y-m-d H:i:s', strtotime($ticket['due_date']))
+ );
$due_date = "'" . date('Y-m-d H:i:s', strtotime($ticket['due_date'])) . "'";
+ $language_key = 'audit_due_date_changed';
}
$sql = "UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `due_date` = {$due_date}, `overdue_email_sent` = '0'
WHERE `trackid` = '" . hesk_dbEscape($ticket['trackid']) . "'";
+ mfh_insert_audit_trail_record($ticket_id['id'], 'TICKET', $language_key, hesk_date(),
+ $audit_array);
+
hesk_dbQuery($sql);
}
\ No newline at end of file
diff --git a/language/en/text.php b/language/en/text.php
index 61f0c1cd..d543ccd3 100644
--- a/language/en/text.php
+++ b/language/en/text.php
@@ -1146,6 +1146,7 @@ $hesklang['maxpost']='You probably tried to submit more data than this server ac
// --> Ticket history log
// Unless otherwise specified, first %s will be replaced with date and second with name/username
+// THESE STRINGS ARE DEPRECATED AS OF MODS FOR HESK 3.2.0
$hesklang['thist1']='%s | moved to category %s by %s'; // %s = date, new category, user making change
$hesklang['thist2']='%s | assigned to %s by %s'; // %s = date, assigned user, user making change
$hesklang['thist3']='%s | closed by %s';
@@ -2188,5 +2189,31 @@ $hesklang['permission_group_colon'] = 'Permission Group:';
$hesklang['permission_group'] = 'Permission Group';
$hesklang['changing_permissions_will_reset_permission_group'] = 'Changing a user\'s categories / features will reset their permission group! Click "Discard Changes" to reset the user\'s categories / features.';
+// --> Ticket audit trail
+$hesklang['audit_moved_category']='%s moved ticket to category %s'; // %s = new category, user making change, thist1
+$hesklang['audit_assigned']='%s assigned ticket to %s'; // %s = assigned user, user making change
+$hesklang['audit_assigned_self'] = '%s assigned ticket to themself';
+$hesklang['audit_unassigned'] = '%s unassigned ticket';
+$hesklang['audit_closed']='%s closed ticket'; // thist3
+$hesklang['audit_automatically_closed'] ='Ticket automatically closed';
+$hesklang['audit_opened']='%s opened ticket'; // thist4
+$hesklang['audit_locked']='%s locked ticket'; // thist5
+$hesklang['audit_automatically_locked'] = 'Ticket automatically locked';
+$hesklang['audit_unlocked']='%s unlocked ticket'; // thist6
+$hesklang['audit_created']='%s created ticket';
+$hesklang['audit_priority']='%s changed priority to %s'; // %s = date,new priority, user making change, thist8
+$hesklang['audit_status']='%s changed status to %s'; // %s = date, new status, user making change, thist9
+$hesklang['audit_autoassigned']='%s automatically assigned to ticket'; //thist10
+$hesklang['audit_submitted_via_piping']='Ticket submitted via e-mail piping'; // thist11
+$hesklang['audit_attachment_deleted']='%s deleted attachment %s'; // %s = date, deleted attachment, user making change
+$hesklang['audit_merged']='%s merged ticket %s with this ticket'; // %s = date, merged ticket ID, user making change, thist13
+$hesklang['audit_time_worked']='%s updated time worked to %s'; // %s = date, new time worked, user making change
+$hesklang['audit_submitted_by']='%s submitted ticket';
+$hesklang['audit_submitted_via_pop']='Ticket submitted via POP3 fetching'; // thist16
+$hesklang['audit_due_date_removed'] = '%s removed due date';
+$hesklang['audit_due_date_changed'] = '%s changed due date to %s';
+$hesklang['audit_linked_ticket'] = '%s linked ticket %s to this ticket';
+$hesklang['audit_unlinked_ticket'] = '%s unlinked ticket %s';
+
// DO NOT CHANGE BELOW
if (!defined('IN_SCRIPT')) die('PHP syntax OK!');
diff --git a/submit_ticket.php b/submit_ticket.php
index 03c710e4..06090ef6 100644
--- a/submit_ticket.php
+++ b/submit_ticket.php
@@ -405,13 +405,11 @@ if ($hesk_settings['kb_enable'] && $hesk_settings['kb_recommendanswers'] && isse
// All good now, continue with ticket creation
$tmpvar['owner'] = 0;
-$tmpvar['history'] = sprintf($hesklang['thist15'], hesk_date(), $tmpvar['name']);
// Auto assign tickets if aplicable
$autoassign_owner = hesk_autoAssignTicket($tmpvar['category']);
if ($autoassign_owner) {
$tmpvar['owner'] = $autoassign_owner['id'];
- $tmpvar['history'] .= sprintf($hesklang['thist10'], hesk_date(), $autoassign_owner['name'] . ' (' . $autoassign_owner['user'] . ')');
}
// Insert attachments
@@ -463,6 +461,14 @@ if ($createTicket) {
//-- email has been verified, and a ticket can be created
$ticket = hesk_newTicket($tmpvar);
+ mfh_insert_audit_trail_record($ticket['id'], 'TICKET', 'audit_submitted_by', hesk_date(),
+ array(0 => $tmpvar['name']));
+
+ 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
if ($hesk_settings['notify_new'] && $email_available) {
hesk_notifyCustomer($modsForHesk_settings);