"\0", 'n' => "\n", 'r' => "\r", 't' => "\t", 'b' => "\x08", 'Z' => "\x1a", '%', '_' => $pair[0], default => $pair[1], }; }, (string) $text); return mysqli_real_escape_string($mysqli, mb_substr((string) $plain, 0, $length)); } 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 = logSqlText($type, 200); $details = logSqlText($details, 1000); $action = logSqlText($action, 250); $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 = logSqlText($type, 200); $action = logSqlText($action, 255); $description = logSqlText($description, 1000); // Cut to their columns too: a User-Agent longer than log_user_agent failed the // insert under strict mode, so nothing was logged - and the sign-in lockout, // which counts these rows, never counted a failed sign-in sent with one $ip = logSqlText($session_ip, 200); $user_agent = logSqlText($session_user_agent, 250); mysqli_query($mysqli, "INSERT INTO logs SET log_type = '$type', log_action = '$action', log_description = '$description', log_ip = '$ip', log_user_agent = '$user_agent', log_client_id = $client_id, log_user_id = $session_user_id, log_entity_id = $entity_id"); // On to a syslog server or SIEM, when that is set up (functions/log_forward.php) logForwardQueue('audit', mysqli_insert_id($mysqli)); } /* * 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: callers pass the * description already escaped, and logSqlText() makes sure of it. * * 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 = logSqlText($description, 255); $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: callers pass * the status and description already escaped, and logSqlText() makes sure of it. * * 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 = logSqlText($status, 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 = logSqlText($description, 1000); 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"); } /* * Writes an app log entry - what ITFlow itself does: cron runs, mail, payment * providers, the API, errors (Admin > App Logs, functions/app_log.php). * Unlike logAudit(), it takes plain text and escapes it here. $type is the * level - info, warning, error or debug; anything else is put to the nearest, * as the column takes only those. */ function logApp($category, $type, $details) { global $mysqli; $type = strtolower(trim((string) $type)); if (!in_array($type, ['info', 'warning', 'error', 'debug'], true)) { $type = in_array($type, ['critical', 'fatal', 'alert', 'emergency'], true) ? 'error' : ($type === 'warn' ? 'warning' : 'info'); } // Cut by character, so a name or message with accents isn't cut through the middle of one $category = mb_substr((string) $category, 0, 200); $details = mb_substr((string) $details, 0, 1000); $category_sql = mysqli_real_escape_string($mysqli, $category); $details_sql = mysqli_real_escape_string($mysqli, $details); mysqli_query($mysqli, "INSERT INTO app_logs SET app_log_category = '$category_sql', app_log_type = '$type', app_log_details = '$details_sql'"); // On to a syslog server or SIEM, from the level picked (functions/log_forward.php) logForwardQueue('app', mysqli_insert_id($mysqli), $type, $category); }