Files
Cactiplugin_monitor/poller_functions.php
Thomas VincentandTheWitness 11fa109548 refactor: safe PHP 7.4 modernization (#212)
* refactor: add strict typing and clean up standalone infra

* refactor: safe PHP 7.4 modernization (arrays, null coalescing)

* fix: restore corrupted function calls from refactor tool

Revert bulk array()->[] rewrite damage affecting:
- is_array, in_array, xml2array
- call_user_func_array, filter_var_array
- Function declarations with _array suffix

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: restore corrupted syntax from refactor tool

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix: remove duplicate declare(strict_types) statements

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(security): escape request reuse in monitor views

* fix:Run php-cs-fixit to resolve workflows

* fix: Remove strict types for now

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-07-09 09:20:25 -04:00

1077 lines
31 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDTool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
/**
* Add a host id to each recipient entry in the reboot recipient map.
*
* @param array $reboot_emails Recipient map keyed by email, then host id.
* @param array $alert_emails List of raw email addresses.
* @param int|string $host_id Host id to associate with each recipient.
*
* @return void
*/
function monitorAddEmails(array &$reboot_emails, array $alert_emails, mixed $host_id): void {
if (cacti_sizeof($alert_emails)) {
foreach ($alert_emails as $email) {
$reboot_emails[trim(strtolower($email))][$host_id] = $host_id;
}
}
}
/**
* Add a host id to recipients coming from a notification list id.
*
* @param array $reboot_emails Recipient map keyed by email, then host id.
* @param int|string|null $notify_list Notification list id.
* @param int|string $host_id Host id to add.
* @param array $notification_lists Map of list id to comma-delimited emails.
*
* @return void
*/
function monitorAddNotificationList(array &$reboot_emails, mixed $notify_list, mixed $host_id, array $notification_lists): void {
if ($notify_list !== null && $notify_list > 0 && isset($notification_lists[$notify_list])) {
$emails = explode(',', $notification_lists[$notify_list]);
monitorAddEmails($reboot_emails, $emails, $host_id);
}
}
/**
* Fetch the configured global alert email list.
*
* @return array
*/
function getAlertEmails(): array {
$alert_email = read_config_option('alert_email');
return ($alert_email != '') ? explode(',', $alert_email) : [];
}
/**
* Delete monitor table rows that reference missing hosts.
*
* @param string $table_name Monitor table to purge.
*
* @return void
*/
function purgeOrphanMonitorRows(string $table_name): void {
$removed_hosts = db_fetch_assoc("SELECT mu.host_id
FROM $table_name AS mu
LEFT JOIN host AS h
ON h.id = mu.host_id
WHERE h.id IS NULL");
if (cacti_sizeof($removed_hosts)) {
db_execute("DELETE mu
FROM $table_name AS mu
LEFT JOIN host AS h
ON h.id = mu.host_id
WHERE h.id IS NULL");
}
}
/**
* Get monitored hosts whose uptime indicates a reboot.
*
* @return array
*/
function getRebootedHosts(): array {
return db_fetch_assoc('SELECT h.id, h.description,
h.hostname, h.snmp_sysUpTimeInstance, mu.uptime
FROM host AS h
LEFT JOIN plugin_monitor_uptime AS mu
ON h.id = mu.host_id
WHERE h.snmp_version > 0
AND status IN (2,3)
AND h.deleted = ""
AND h.monitor = "on"
AND (mu.uptime IS NULL OR mu.uptime > h.snmp_sysUpTimeInstance)
AND h.snmp_sysUpTimeInstance > 0');
}
/**
* Fetch notification lists and map id to email string.
*
* @return array
*/
function getNotificationListsMap(): array {
return array_rekey(
db_fetch_assoc('SELECT id, emails
FROM plugin_notification_lists
ORDER BY id'),
'id',
'emails'
);
}
/**
* Add reboot recipients based on host threshold notification settings.
*
* @param array $reboot_emails Recipient map keyed by email, then host id.
* @param int|string $host_id Host id being processed.
* @param array $alert_emails Global alert email list.
* @param array $notification_lists Map of notification list id to emails.
*
* @return void
*/
function addTholdRebootRecipients(array &$reboot_emails, mixed $host_id, array $alert_emails, array $notification_lists): void {
$notify = db_fetch_row_prepared(
'SELECT thold_send_email, thold_host_email
FROM host
WHERE id = ?',
[$host_id]
);
if (!cacti_sizeof($notify)) {
return;
}
switch ($notify['thold_send_email']) {
case '1':
monitorAddEmails($reboot_emails, $alert_emails, $host_id);
break;
case '2':
monitorAddNotificationList($reboot_emails, $notify['thold_host_email'], $host_id, $notification_lists);
break;
case '3':
monitorAddEmails($reboot_emails, $alert_emails, $host_id);
monitorAddNotificationList($reboot_emails, $notify['thold_host_email'], $host_id, $notification_lists);
break;
default:
break;
}
}
/**
* Build reboot recipient map and persist reboot history rows.
*
* @param array $rebooted_hosts Rebooted host rows.
* @param array $alert_emails Global alert email list.
*
* @return array
*/
function buildRebootEmailMap(array $rebooted_hosts, array $alert_emails): array {
$reboot_emails = [];
$notification_lists = getNotificationListsMap();
$monitor_list = read_config_option('monitor_list');
$monitor_thold = read_config_option('monitor_reboot_thold');
foreach ($rebooted_hosts as $host) {
db_execute_prepared(
'INSERT INTO plugin_monitor_reboot_history
(host_id, reboot_time)
VALUES (?, ?)',
[$host['id'], date(MONITOR_DATE_TIME_FORMAT, time() - intval($host['snmp_sysUpTimeInstance']))]
);
monitorAddNotificationList($reboot_emails, $monitor_list, $host['id'], $notification_lists);
if ($monitor_thold == 'on') {
addTholdRebootRecipients($reboot_emails, $host['id'], $alert_emails, $notification_lists);
}
}
return $reboot_emails;
}
/**
* Dispatch reboot notifications per-recipient or as a single batched email.
*
* @param array $reboot_emails Recipient map keyed by email, then host id.
*
* @return void
*/
function sendRebootNotifications(array $reboot_emails): void {
$monitor_send_one_email = read_config_option('monitor_send_one_email');
if (!cacti_sizeof($reboot_emails)) {
return;
}
$all_hosts = [];
$to_email = '';
foreach ($reboot_emails as $email => $hosts) {
if ($email == '') {
monitorDebug('Unable to process reboot notification due to empty Email address.');
continue;
}
$to_email .= ($to_email != '' ? ',' : '') . $email;
$all_hosts = array_unique(array_merge($all_hosts, array_values($hosts)));
if ($monitor_send_one_email !== 'on') {
monitorDebug('Processing the Email address: ' . $email);
processRebootEmail($email, $hosts);
}
}
if ($monitor_send_one_email == 'on' && $to_email !== '') {
monitorDebug('Processing the Email address: ' . $to_email);
processRebootEmail($to_email, $all_hosts);
}
}
/**
* Process reboot detection, uptime refresh, and down-event history logging.
*
* @return array{int, int} Reboot count and recent down count.
*/
function monitorUptimeChecker(): array {
monitorDebug('Checking for Uptime of Devices');
$alert_emails = getAlertEmails();
purgeOrphanMonitorRows('plugin_monitor_uptime');
purgeOrphanMonitorRows('plugin_monitor_reboot_history');
$rebooted_hosts = getRebootedHosts();
if (cacti_sizeof($rebooted_hosts)) {
$reboot_emails = buildRebootEmailMap($rebooted_hosts, $alert_emails);
sendRebootNotifications($reboot_emails);
}
// Freshen the uptimes
db_execute('REPLACE INTO plugin_monitor_uptime
(host_id, uptime)
SELECT id, snmp_sysUpTimeInstance
FROM host
WHERE snmp_version > 0
AND status IN(2,3)
AND deleted = ""
AND monitor = "on"
AND snmp_sysUpTimeInstance > 0');
// Log Recently Down
db_execute('INSERT IGNORE INTO plugin_monitor_notify_history
(host_id, notify_type, notification_time, notes)
SELECT h.id, "3" AS notify_type, status_fail_date AS notification_time, status_last_error AS notes
FROM host AS h
WHERE status = 1
AND deleted = ""
AND monitor = "on"
AND status_event_count = 1');
$recent = db_affected_rows();
return [cacti_sizeof($rebooted_hosts), $recent];
}
/**
* Build reboot details table/text and capture the last resolved host row.
*
* @param array $hosts Host ids to include.
*
* @return array{string, string, array}
*/
function buildRebootDetails(array $hosts): array {
$body_txt = '';
$last_host = [];
$body = '<table class="report_table">' . PHP_EOL;
$body .= '<tr class="header_row">' . PHP_EOL;
$body .=
'<th class="left">' . __('Description', 'monitor') . '</th>' .
'<th class="left">' . __('Hostname', 'monitor') . '</th>' . PHP_EOL;
$body .= '</tr>' . PHP_EOL;
foreach ($hosts as $host_id) {
$host = db_fetch_row_prepared(
'SELECT description, hostname
FROM host
WHERE id = ?',
[$host_id]
);
if (!cacti_sizeof($host)) {
continue;
}
$last_host = $host;
$body .= '<tr>' .
'<td class="left">' . $host['description'] . '</td>' .
'<td class="left">' . $host['hostname'] . '</td>' .
'</tr>' . PHP_EOL;
$body_txt .=
__('Description: ', 'monitor') . $host['description'] . PHP_EOL .
__('Hostname: ', 'monitor') . $host['hostname'] . PHP_EOL . PHP_EOL;
}
$body .= '</table>' . PHP_EOL;
return [$body, $body_txt, $last_host];
}
/**
* Build reboot notification subject from host count and delivery mode.
*
* @param array $hosts Host id list.
* @param array $last_host Last host row seen while building details.
*
* @return string
*/
function buildRebootSubject(array $hosts, array $last_host): string {
$subject = read_config_option('monitor_subject');
$monitor_send_one_email = read_config_option('monitor_send_one_email');
if ($monitor_send_one_email == 'on' && cacti_sizeof($last_host)) {
return $subject . ' ' . $last_host['description'] . ' (' . $last_host['hostname'] . ')';
}
if (cacti_sizeof($hosts) == 1 && cacti_sizeof($last_host)) {
return $subject . ' 1 device - ' . $last_host['description'] . ' (' . $last_host['hostname'] . ')';
}
return $subject . ' ' . cacti_sizeof($hosts) . ' devices';
}
/**
* Wrap body output with report format template and build mail headers.
*
* @param string $body HTML body content.
* @param string $body_txt Plain text body content.
*
* @return array{string, string, array}
*/
function prepareReportOutput(string $body, string $body_txt): array {
$output = '';
$report_tag = '';
$theme = 'modern';
monitorDebug('Loading Format File');
$format_ok = reports_load_format_file(read_config_option('monitor_format_file'), $output, $report_tag, $theme);
monitorDebug('Format File Loaded, Format is ' . ($format_ok ? 'Ok' : 'Not Ok') . ', Report Tag is ' . $report_tag);
if ($format_ok) {
if ($report_tag) {
$output = str_replace('<REPORT>', $body, $output);
} else {
$output = $output . PHP_EOL . $body;
}
} else {
$output = $body;
}
monitorDebug('HTML Processed');
if (defined('CACTI_VERSION')) {
$version = CACTI_VERSION;
} else {
$version = get_cacti_version();
}
$headers = ['User-Agent' => 'Cacti-Monitor-v' . $version];
return [$output, $body_txt, $headers];
}
/**
* Render and send one reboot notification email payload.
*
* @param string $email Recipient email address string.
* @param array $hosts Host ids to include in the message.
*
* @return void
*/
function processRebootEmail(string $email, array $hosts): void {
monitorDebug("Reboot Processing for $email starting");
[$body, $body_txt, $last_host] = buildRebootDetails($hosts);
$subject = buildRebootSubject($hosts, $last_host);
$template_output = read_config_option('monitor_body');
$template_output = str_replace('<DETAILS>', $body, $template_output) . PHP_EOL;
if (strpos($template_output, '<DETAILS>') !== false) {
$toutput = str_replace('<DETAILS>', $body_txt, $template_output) . PHP_EOL;
} else {
$toutput = $body_txt;
}
if (read_config_option('monitor_reboot_notify') != 'on') {
return;
}
[$output, $toutput, $headers] = prepareReportOutput($body, $toutput);
processSendEmail($email, $subject, $output, $toutput, $headers, 'Reboot Notifications');
}
/**
* Resolve alert/warn host ids for requested global/list subscription scopes.
*
* @param array $lists Requested scopes (`global` or list ids).
* @param array $global_list Flattened global notification host ids by severity.
* @param array $notify_list Flattened list notification host ids by severity.
*
* @return array{array, array}
*/
function collectNotificationHosts(array $lists, array $global_list, array $notify_list): array {
$alert_hosts = [];
$warn_hosts = [];
foreach ($lists as $list) {
if ($list === 'global') {
if (isset($global_list['alert'])) {
$alert_hosts = array_merge($alert_hosts, explode(',', $global_list['alert']));
}
if (isset($global_list['warn'])) {
$warn_hosts = array_merge($warn_hosts, explode(',', $global_list['warn']));
}
continue;
}
if (isset($notify_list[$list]['alert'])) {
$alert_hosts = array_merge($alert_hosts, explode(',', $notify_list[$list]['alert']));
}
if (isset($notify_list[$list]['warn'])) {
$warn_hosts = array_merge($warn_hosts, explode(',', $notify_list[$list]['warn']));
}
}
return [$alert_hosts, $warn_hosts];
}
/**
* De-duplicate alert/warn host ids and log notification history entries.
*
* @param array $alert_hosts Alert host ids; normalized in place.
* @param array $warn_hosts Warning host ids; normalized in place.
*
* @return void
*/
function normalizeAndLogNotificationHosts(array &$alert_hosts, array &$warn_hosts): void {
if (cacti_sizeof($alert_hosts)) {
$alert_hosts = array_unique($alert_hosts, SORT_NUMERIC);
logMessages('alert', $alert_hosts);
}
if (cacti_sizeof($warn_hosts)) {
$warn_hosts = array_unique($warn_hosts, SORT_NUMERIC);
logMessages('warn', $warn_hosts);
}
}
/**
* Build shared intro copy for ping threshold email and text output.
*
* @param int|string $freq Resend frequency in minutes.
*
* @return array{string, string}
*/
function buildPingNotificationIntro(mixed $freq): array {
$body = '<h1>' . __(MONITOR_PING_NOTIFICATION_SUBJECT, 'monitor') . '</h1>' . PHP_EOL;
$body_txt = __(MONITOR_PING_NOTIFICATION_SUBJECT, 'monitor') . PHP_EOL;
$message = __('The following report will identify Devices that have eclipsed their ping latency thresholds. You are receiving this report since you are subscribed to a Device associated with the Cacti system located at the following URL below.');
$body .= '<p>' . $message . '</p>' . PHP_EOL;
$body_txt .= $message . PHP_EOL;
$body .= '<h2><a href="' . read_config_option('base_url') . '">Cacti Monitoring Site</a></h2>' . PHP_EOL;
$body_txt .= __('Cacti Monitoring Site', 'monitor') . PHP_EOL;
if ($freq > 0) {
$body .= '<p>' . __('You will receive notifications every %d minutes if the Device is above its threshold.', $freq, 'monitor') . '</p>' . PHP_EOL;
$body_txt .= __('You will receive notifications every %d minutes if the Device is above its threshold.', $freq, 'monitor') . PHP_EOL;
} else {
$body .= '<p>' . __('You will receive notifications every time the Device is above its threshold.', 'monitor') . '</p>' . PHP_EOL;
$body_txt .= __('You will receive notifications every time the Device is above its threshold.', 'monitor') . PHP_EOL;
}
return [$body, $body_txt];
}
/**
* Append one severity section for breached host thresholds.
*
* @param string $body HTML body output buffer.
* @param string $body_txt Plain text output buffer.
* @param array $host_ids Host ids for the section.
* @param array $criticalities Criticality label map.
* @param string $section_text Section heading text.
* @param string $threshold_field Host threshold field name.
*
* @return void
*/
function appendThresholdSection(string &$body, string &$body_txt, array $host_ids, array $criticalities, string $section_text, string $threshold_field): void {
global $config;
if (!cacti_sizeof($host_ids)) {
return;
}
$body .= '<p>' . __($section_text, 'monitor') . '</p>' . PHP_EOL;
$body_txt .= __($section_text, 'monitor') . PHP_EOL;
$body .= '<table class="report_table">' . PHP_EOL;
$body .= '<tr class="header_row">' . PHP_EOL;
$body .=
'<th class="left">' . __('Hostname', 'monitor') . '</th>' .
'<th class="left">' . __('Criticality', 'monitor') . '</th>' .
'<th class="right">' . __(MONITOR_ALERT_PING_LABEL, 'monitor') . '</th>' .
'<th class="right">' . __(MONITOR_CURRENT_PING_LABEL, 'monitor') . '</th>' . PHP_EOL;
$body .= '</tr>' . PHP_EOL;
$body_txt .=
__('Hostname', 'monitor') . "\t" .
__('Criticality', 'monitor') . "\t" .
__(MONITOR_ALERT_PING_LABEL, 'monitor') . "\t" .
__(MONITOR_CURRENT_PING_LABEL, 'monitor') . PHP_EOL;
$hosts = db_fetch_assoc('SELECT *
FROM host
WHERE id IN(' . implode(',', $host_ids) . ')
AND deleted = ""');
if (cacti_sizeof($hosts)) {
foreach ($hosts as $host) {
$body .= '<tr>' . PHP_EOL;
$body .= '<td class="left"><a class="hyperLink" href="' . htmlspecialchars($config['url_path'] . 'host.php?action=edit&id=' . $host['id']) . '">' . $host['description'] . '</a></td>' . PHP_EOL;
$body .= '<td class="left">' . $criticalities[$host['monitor_criticality']] . '</td>' . PHP_EOL;
$body .= '<td class="right">' . number_format_i18n($host[$threshold_field], 2) . ' ms</td>' . PHP_EOL;
$body .= '<td class="right">' . number_format_i18n($host['cur_time'], 2) . ' ms</td>' . PHP_EOL;
$body .= '</tr>' . PHP_EOL;
$body_txt .=
$host['description'] . "\t" .
$criticalities[$host['monitor_criticality']] . "\t" .
number_format_i18n($host[$threshold_field], 2) . " ms\t" .
number_format_i18n($host['cur_time'], 2) . ' ms' . PHP_EOL;
}
}
$body .= '</table>' . PHP_EOL;
}
/**
* Build log-friendly summary text for alert/warn delivery counts.
*
* @param array $alert_hosts Alert host ids.
* @param array $warn_hosts Warning host ids.
*
* @return string
*/
function buildNotificationStatus(array $alert_hosts, array $warn_hosts): string {
$status = '';
if (cacti_sizeof($alert_hosts)) {
$status = sizeof($alert_hosts) . ' Alert Notifications';
}
if (cacti_sizeof($warn_hosts)) {
if ($status !== '') {
$status .= ', and ';
}
$status .= sizeof($warn_hosts) . ' Warning Notifications';
}
return $status;
}
/**
* Build and send a ping-threshold notification for one recipient.
*
* @param string $email Recipient email.
* @param array $lists Requested scopes (`global` or list ids).
* @param array $global_list Flattened global host ids by severity.
* @param array $notify_list Flattened list host ids by severity.
*
* @return void
*/
function processEmail(string $email, array $lists, array $global_list, array $notify_list): void {
monitorDebug('Into Processing');
$criticalities = [
0 => __('Disabled', 'monitor'),
1 => __('Low', 'monitor'),
2 => __('Medium', 'monitor'),
3 => __('High', 'monitor'),
4 => __('Mission Critical', 'monitor')
];
[$alert_hosts, $warn_hosts] = collectNotificationHosts($lists, $global_list, $notify_list);
monitorDebug('Lists Processed');
normalizeAndLogNotificationHosts($alert_hosts, $warn_hosts);
monitorDebug('Found ' . sizeof($alert_hosts) . ' Alert Hosts, and ' . sizeof($warn_hosts) . ' Warn Hosts');
if (!cacti_sizeof($alert_hosts) && !cacti_sizeof($warn_hosts)) {
return;
}
monitorDebug('Formatting Email');
$freq = read_config_option('monitor_resend_frequency');
$subject = __(MONITOR_PING_NOTIFICATION_SUBJECT, 'monitor');
[$body, $body_txt] = buildPingNotificationIntro($freq);
appendThresholdSection(
$body,
$body_txt,
$alert_hosts,
$criticalities,
'The following Devices have breached their Alert Notification Threshold.',
'monitor_alert'
);
appendThresholdSection(
$body,
$body_txt,
$warn_hosts,
$criticalities,
'The following Devices have breached their Warning Notification Threshold.',
'monitor_warn'
);
[$output, $toutput, $headers] = prepareReportOutput($body, $body_txt);
$status = buildNotificationStatus($alert_hosts, $warn_hosts);
processSendEmail($email, $subject, $output, $toutput, $headers, $status);
}
/**
* Send an email through Cacti mailer with configured sender fallbacks.
*
* @param string $email Recipient email string.
* @param string $subject Message subject.
* @param string $output HTML output body.
* @param string $toutput Plain text output body.
* @param array $headers Extra headers passed to mailer.
* @param string $status Status text used in logs.
*
* @return void
*/
function processSendEmail(string $email, string $subject, string $output, string $toutput, array $headers, string $status): void {
$from_email = read_config_option('monitor_fromemail');
if ($from_email == '') {
$from_email = read_config_option('settings_from_email');
if ($from_email == '') {
$from_email = 'Cacti@cacti.net';
}
}
$from_name = read_config_option('monitor_fromname');
if ($from_name == '') {
$from_name = read_config_option('settings_from_name');
if ($from_name == '') {
$from_name = 'Cacti Reporting';
}
}
$html = true;
if (read_config_option('thold_send_text_only') == 'on') {
$output = monitorText($toutput);
$html = false;
}
monitorDebug("Sending Email to '$email' for $status");
$error = mailer(
[$from_email, $from_name],
$email,
'',
'',
'',
$subject,
$output,
monitorText($toutput),
null,
$headers,
$html
);
monitorDebug("The return from the mailer was '$error'");
if (strlen($error)) {
cacti_log("WARNING: Monitor had problems sending to '$email' for $status. The error was '$error'", false, 'MONITOR');
} else {
cacti_log("NOTICE: Email Notification Sent to '$email' for $status.", false, 'MONITOR');
}
}
/**
* Convert HTML-ish report content into plain text line output.
*
* @param string $output HTML or mixed output.
*
* @return string
*/
function monitorText(string $output): string {
$output = explode(PHP_EOL, $output);
$new_output = '';
if (cacti_sizeof($output)) {
foreach ($output as $line) {
$line = str_replace('<br>', PHP_EOL, $line);
$line = str_replace('<br />', PHP_EOL, $line);
$line = trim(strip_tags($line));
$new_output .= $line . PHP_EOL;
}
}
return $new_output;
}
/**
* Persist alert or warning notification history rows once per host.
*
* @param string $type Severity type (`alert` or `warn`).
* @param array $alert_hosts Host ids to log for the severity.
*
* @return void
*/
function logMessages(string $type, array $alert_hosts): void {
global $start_date;
static $processed = [];
if ($type == 'warn') {
$type = '0';
$column = 'monitor_warn';
} elseif ($type == 'alert') {
$type = '1';
$column = 'monitor_alert';
}
foreach ($alert_hosts as $id) {
if (!isset($processed[$id])) {
db_execute_prepared(
"INSERT INTO plugin_monitor_notify_history
(host_id, notify_type, ping_time, ping_threshold, notification_time)
SELECT id, '$type' AS notify_type, cur_time, $column, '$start_date' AS notification_time
FROM host
WHERE deleted = ''
AND monitor = 'on'
AND id = ?",
[$id]
);
}
$processed[$id] = true;
}
}
/**
* Add grouped host ids to global/list collections by host email mode.
*
* @param string $type Severity key (`alert` or `warn`).
* @param array $entry Grouped SQL row containing mode/list/ids.
* @param array $global_list Global grouped bucket, updated in place.
* @param array $notify_list Per-list grouped bucket, updated in place.
* @param array $lists Set of list ids used for recipient lookups.
*
* @return void
*/
function addGroupedNotificationEntry(string $type, array $entry, array &$global_list, array &$notify_list, array &$lists): void {
if ($entry['thold_send_email'] == '1' || $entry['thold_send_email'] == '3') {
$global_list[$type][] = $entry;
}
if (($entry['thold_send_email'] == '2' || $entry['thold_send_email'] == '3') && $entry['thold_host_email'] > 0) {
$notify_list[$type][$entry['thold_host_email']][] = $entry;
$lists[$entry['thold_host_email']] = $entry['thold_host_email'];
}
}
/**
* Query and group threshold-breached hosts for one severity type.
*
* @param int|string $type Severity key (`alert` or `warn`).
* @param int $criticality Minimum criticality threshold.
* @param array $global_list Global grouped bucket, updated in place.
* @param array $notify_list Per-list grouped bucket, updated in place.
* @param array $lists Set of notification list ids, updated in place.
*
* @return void
*/
function getHostsByListType(string $type, mixed $criticality, array &$global_list, array &$notify_list, array &$lists): void {
$last_time = date(MONITOR_DATE_TIME_FORMAT, time() - read_config_option('monitor_resend_frequency') * 60);
$hosts = db_fetch_cell_prepared(
"SELECT COUNT(*)
FROM host
WHERE status = 3
AND deleted = ''
AND monitor = 'on'
AND thold_send_email > 0
AND monitor_criticality >= ?
AND cur_time > monitor_$type",
[$criticality]
);
if ($hosts <= 0) {
return;
}
$htype = ($type == 'warn') ? 1 : 0;
$groups = db_fetch_assoc_prepared(
"SELECT
thold_send_email, thold_host_email, GROUP_CONCAT(host.id) AS id
FROM host
LEFT JOIN (
SELECT host_id, MAX(notification_time) AS notification_time
FROM plugin_monitor_notify_history
WHERE notify_type = ?
GROUP BY host_id
) AS nh
ON host.id=nh.host_id
WHERE status = 3
AND deleted = ''
AND monitor = 'on'
AND thold_send_email > 0
AND monitor_criticality >= ?
AND cur_time > monitor_$type " . ($type == 'warn' ? ' AND cur_time < monitor_alert' : '') . '
AND (notification_time < ? OR notification_time IS NULL)
AND host.total_polls > 1
GROUP BY thold_host_email, thold_send_email
ORDER BY thold_host_email, thold_send_email',
[$htype, $criticality, $last_time]
);
if (!cacti_sizeof($groups)) {
return;
}
foreach ($groups as $entry) {
addGroupedNotificationEntry($type, $entry, $global_list, $notify_list, $lists);
}
}
/**
* Flatten grouped host id chunks for a single severity bucket.
*
* @param array $list Grouped rows, each row containing a CSV `id` field.
*
* @return string
*/
function flattenGroupSeverityList(array $list): string {
$flattened = '';
foreach ($list as $item) {
$flattened .= ($flattened !== '' ? ',' : '') . $item['id'];
}
return $flattened;
}
/**
* Flatten grouped host ids for each notification list id.
*
* @param array $lists Grouped entries keyed by notification list id.
*
* @return array
*/
function flattenNotifySeverityLists(array $lists): array {
$flattened = [];
foreach ($lists as $id => $list) {
$flattened[$id] = flattenGroupSeverityList($list);
}
return $flattened;
}
/**
* Flatten grouped global and per-list structures into CSV host id strings.
*
* @param array $global_list Global grouped structure, updated in place.
* @param array $notify_list Per-list grouped structure, updated in place.
*
* @return void
*/
function flattenLists(array &$global_list, array &$notify_list): void {
if (cacti_sizeof($global_list)) {
$new_global = [];
foreach ($global_list as $severity => $list) {
$new_global[$severity] = flattenGroupSeverityList($list);
}
$global_list = $new_global;
}
if (cacti_sizeof($notify_list)) {
$new_list = [];
foreach ($notify_list as $severity => $lists) {
$new_list[$severity] = flattenNotifySeverityLists($lists);
}
$notify_list = $new_list;
}
}
/**
* Add email addresses into the recipient scope map.
*
* @param array $notification_emails Recipient map, updated in place.
* @param array $emails Raw email values to normalize.
* @param string|int $scope_key Scope key (`global` or list id).
*
* @return void
*/
function addEmailsToNotificationMap(array &$notification_emails, array $emails, mixed $scope_key): void {
foreach ($emails as $user) {
$user = trim($user);
if ($user !== '') {
$notification_emails[$user][$scope_key] = true;
}
}
}
/**
* Build recipient map for global and notification list subscriptions.
*
* @param array $lists Notification list ids to resolve.
*
* @return array
*/
function getEmailsAndLists(array $lists): array {
$notification_emails = [];
$alert_email = read_config_option('alert_email');
$global_emails = ($alert_email != '') ? explode(',', $alert_email) : [];
if (cacti_sizeof($global_emails)) {
addEmailsToNotificationMap($notification_emails, $global_emails, 'global');
}
if (!cacti_sizeof($lists)) {
return $notification_emails;
}
$list_emails = db_fetch_assoc('SELECT id, emails
FROM plugin_notification_lists
WHERE id IN (' . implode(',', $lists) . ')');
if (!cacti_sizeof($list_emails)) {
return $notification_emails;
}
foreach ($list_emails as $email) {
addEmailsToNotificationMap($notification_emails, explode(',', $email['emails']), $email['id']);
}
return $notification_emails;
}
/**
* Purge notification and reboot history records older than configured retention.
*
* @return array{int, int} Purged notify count and purged reboot count.
*/
function purgeEventRecords(): array {
// Purge old records
$days = read_config_option('monitor_log_storage');
if (empty($days)) {
$days = 120;
}
db_execute_prepared(
'DELETE FROM plugin_monitor_notify_history
WHERE notification_time < FROM_UNIXTIME(UNIX_TIMESTAMP() - (? * 86400))',
[$days]
);
$purge_n = db_affected_rows();
db_execute_prepared(
'DELETE FROM plugin_monitor_reboot_history
WHERE log_time < FROM_UNIXTIME(UNIX_TIMESTAMP() - (? * 86400))',
[$days]
);
$purge_r = db_affected_rows();
return [$purge_n, $purge_r];
}
/**
* Print a debug line when poller debug mode is enabled.
*
* @param string $message Debug message.
*
* @return void
*/
function monitorDebug(string $message): void {
global $debug;
if ($debug) {
print trim($message) . PHP_EOL;
}
}
/**
* Print monitor poller version information.
*
* @return void
*/
function displayVersion(): void {
global $config;
if (!function_exists('plugin_monitor_version')) {
include_once $config['base_path'] . '/plugins/monitor/setup.php';
}
$info = plugin_monitor_version();
print 'Cacti Monitor Poller, Version ' . $info['version'] . ', ' . COPYRIGHT_YEARS . PHP_EOL;
}
/**
* Print CLI help output for this poller entrypoint.
*
* @return void
*/
function displayHelp(): void {
displayVersion();
print PHP_EOL;
print 'usage: poller_monitor.php [--debug]' . PHP_EOL . PHP_EOL;
print ' --debug - debug execution, e.g. for testing' . PHP_EOL . PHP_EOL;
}