Files
itflow/functions/logging.php
johnnyq 9457442804 Emails stage 3: every email now goes through emailQueue and the shared layout - the client portal invite sends a link to set a password (or to sign in with Microsoft) instead of the password itself, offered on New Contact as well as Edit through portalMailInvite, with Forgot password, password changed and two-step sign-in emails through portalMailPasswordReset and portalMailAccountChange; shared link and code emails with the note quoted and the whole link printed under the button, replacing shareMailHtml and shareQueueMail; calendar invitations, updates and cancellations with when, repeats, where, the description and a join button; Email on the contacts and clients lists in the layout from one of the configured mailboxes with an optional greeting and send time, leaving out archived contacts and contacts without an address, each address once, nothing queued without mail set up, through emailBulkSend and emailBulkPost with a shared bulk_email_fields window; Admin > Mail > Previews shows all 52 emails built by their real senders on sample records inside a rolled-back transaction with emailQueue capturing and custom actions off, with computer and phone widths and Send me a copy; emailHtml gains content and button show_url, emailQueue gains queued_at and always so billing still queues as before, plus emailMailboxes and emailTestSend; ticketMailCreated, ticketMailReply, ticketMailWatchersAdded, ticketMailTaskApproval, ticketMailClosedReply, ticketSlaAlert and billingMailCardSaved moved into functions; fixes Email on the contacts list queuing mail to contacts with no address, the clients list including archived contacts, and both taking any From address posted; changelog updated
2026-10-17 23:26:25 -04:00

175 lines
7.0 KiB
PHP

<?php
// Logging, notifications and custom action triggers
// Split from the former monolithic functions.php
function triggerCustomAction($trigger, $entity) {
// Not for the sample records an email preview makes and throws away (functions/email_preview.php)
if (function_exists('emailCapturing') && emailCapturing()) {
return;
}
$original_dir = getcwd(); // Save
chdir(dirname(__FILE__));
if (file_exists(__DIR__ . "/custom/custom_action_handler.php")) {
include_once __DIR__ . "/custom/custom_action_handler.php";
}
chdir($original_dir); // Restore original working directory
}
function appNotify($type, $details, $action = null, $client_id = 0, $entity_id = 0) {
global $mysqli;
if (is_null($action)) {
$action = "NULL"; // Without quotes for SQL NULL
}
$client_id = intval($client_id);
$entity_id = intval($entity_id);
$type = substr($type, 0, 200);
$details = substr($details, 0, 1000);
$action = substr($action, 0, 250);
// Callers pass values that are already SQL-safe, but cutting at a fixed
// length (or an escaper that leaves backslashes, e.g. escapeHtml) can leave
// an odd trailing backslash that would escape this query's closing quote
if ((strlen($type) - strlen(rtrim($type, '\\'))) % 2 === 1) {
$type = substr($type, 0, -1);
}
if ((strlen($details) - strlen(rtrim($details, '\\'))) % 2 === 1) {
$details = substr($details, 0, -1);
}
if ((strlen($action) - strlen(rtrim($action, '\\'))) % 2 === 1) {
$action = substr($action, 0, -1);
}
$sql = mysqli_query($mysqli, "SELECT user_id FROM users
WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL
");
while ($row = mysqli_fetch_assoc($sql)) {
$user_id = intval($row['user_id']);
mysqli_query($mysqli, "INSERT INTO notifications SET notification_type = '$type', notification = '$details', notification_action = '$action', notification_client_id = $client_id, notification_entity_id = $entity_id, notification_user_id = $user_id");
}
}
function logAudit($type, $action, $description, $client_id = 0, $entity_id = 0) {
global $mysqli, $session_user_agent, $session_ip, $session_user_id;
$client_id = intval($client_id);
$entity_id = intval($entity_id);
$session_user_id = intval($session_user_id);
if (empty($session_user_id)) {
$session_user_id = 0;
}
$type = substr($type, 0, 200);
$action = substr($action, 0, 255);
$description = substr($description, 0, 1000);
// Callers pass values that are already SQL-safe, but cutting at a fixed
// length (or an escaper that leaves backslashes, e.g. escapeHtml) can leave
// an odd trailing backslash that would escape this query's closing quote
if ((strlen($type) - strlen(rtrim($type, '\\'))) % 2 === 1) {
$type = substr($type, 0, -1);
}
if ((strlen($action) - strlen(rtrim($action, '\\'))) % 2 === 1) {
$action = substr($action, 0, -1);
}
if ((strlen($description) - strlen(rtrim($description, '\\'))) % 2 === 1) {
$description = substr($description, 0, -1);
}
mysqli_query($mysqli, "INSERT INTO logs SET log_type = '$type', log_action = '$action', log_description = '$description', log_ip = '$session_ip', log_user_agent = '$session_user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $entity_id");
}
/*
* Records a ticket history entry - the per-ticket change trail shown in the
* History card on agent/ticket.php.
*
* Follows the same convention as logAudit() above: the description is
* interpolated as-is, so callers pass values that are already SQL-safe.
*
* Call this AFTER the change has been written - the status stamped on the entry
* is whatever the ticket is in at the time of the call.
*/
function logTicketHistory($ticket_id, $description) {
global $mysqli;
$ticket_id = intval($ticket_id);
// ticket_history_description is varchar(255) and NOT NULL, so an over-long
// description would be an error under strict mode rather than a truncation
$description = substr($description, 0, 255);
// The description arrives already escaped, and cutting it at 255 can split a
// \' pair - the leftover backslash would escape this query's closing quote
$trailing_backslashes = strlen($description) - strlen(rtrim($description, '\\'));
if ($trailing_backslashes % 2 === 1) {
$description = substr($description, 0, -1);
}
$status_name = '';
$sql = mysqli_query(
$mysqli,
"SELECT ticket_status_name FROM tickets
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
WHERE ticket_id = $ticket_id"
);
if ($sql && mysqli_num_rows($sql)) {
$status_name = escapeSql(mysqli_fetch_assoc($sql)['ticket_status_name']);
}
mysqli_query($mysqli, "INSERT INTO ticket_history SET ticket_history_status = '$status_name', ticket_history_description = '$description', ticket_history_ticket_id = $ticket_id");
}
/*
* Records an invoice / quote / recurring invoice history entry - the change
* trail shown in the History card on agent/invoice.php and agent/quote.php.
*
* Same convention as logAudit() and logTicketHistory() above: the description
* is interpolated as-is, so callers pass values that are already SQL-safe.
*
* Pass the id of the document the entry belongs to and leave the other two at
* zero - the history table serves all three from one set of rows and each
* detail page filters on its own column.
*/
function logHistory($status, $description, $invoice_id = 0, $quote_id = 0, $recurring_invoice_id = 0) {
global $mysqli;
$invoice_id = intval($invoice_id);
$quote_id = intval($quote_id);
$recurring_invoice_id = intval($recurring_invoice_id);
$status = substr($status, 0, 200);
// history_description is text as of db 2.7.7, so this cap is about keeping
// the trail readable rather than avoiding an overflow
$description = substr($description, 0, 1000);
// Both arrive already escaped, and cutting at a fixed length can split a \'
// pair - the leftover backslash would escape this query's closing quote
if ((strlen($status) - strlen(rtrim($status, '\\'))) % 2 === 1) {
$status = substr($status, 0, -1);
}
if ((strlen($description) - strlen(rtrim($description, '\\'))) % 2 === 1) {
$description = substr($description, 0, -1);
}
mysqli_query($mysqli, "INSERT INTO history SET history_status = '$status', history_description = '$description', history_invoice_id = $invoice_id, history_quote_id = $quote_id, history_recurring_invoice_id = $recurring_invoice_id");
}
function logApp($category, $type, $details) {
global $mysqli;
$category = mysqli_real_escape_string($mysqli, substr($category, 0, 200));
$details = mysqli_real_escape_string($mysqli, substr($details, 0, 1000));
mysqli_query($mysqli, "INSERT INTO app_logs SET app_log_category = '$category', app_log_type = '$type', app_log_details = '$details'");
}