diff --git a/.github/.phpstan.neon b/.github/.phpstan.neon index 5d363fa..ca8061f 100644 --- a/.github/.phpstan.neon +++ b/.github/.phpstan.neon @@ -1,7 +1,9 @@ parameters: level: 6 paths: - - . + - %currentWorkingDirectory%/setup.php + - %currentWorkingDirectory%/functions.php + - %currentWorkingDirectory%/maint.php excludePaths: - vendor - locales diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml index 9f50492..ffd8e94 100644 --- a/.github/workflows/plugin-ci-workflow.yml +++ b/.github/workflows/plugin-ci-workflow.yml @@ -124,7 +124,7 @@ jobs: composer validate --strict || true fi - - name: Install Composer Dependencies + - name: Install Composer Dependencies for Cacti run: | cd ${{ github.workspace }}/cacti if [ -f composer.json ]; then @@ -181,22 +181,10 @@ jobs: exit 1 fi - - name: Verify Maint Plugin Composer Setup - run: | - ls -la ${{ github.workspace }}/cacti/plugins/maint - cat ${{ github.workspace }}/cacti/plugins/maint/composer.json - - - name: Install Maint Plugin Composer Dependencies - run: composer install --prefer-dist --no-progress --no-interaction + - name: Install Plugin Composer Dependencies + run: composer install --prefer-dist --no-progress working-directory: ${{ github.workspace }}/cacti/plugins/maint - env: - COMPOSER_NO_DEV: 0 - - name: Verify Maint Plugin Vendor Binaries - run: | - ls -la ${{ github.workspace }}/cacti/plugins/maint/vendor - ls -la ${{ github.workspace }}/cacti/plugins/maint/vendor/bin - - name: Run Linter on base code run: composer run-script lint working-directory: ${{ github.workspace }}/cacti/plugins/maint diff --git a/composer.json b/composer.json index 98444da..deedd1a 100644 --- a/composer.json +++ b/composer.json @@ -3,15 +3,14 @@ "description": "Cacti maintenance window plugin.", "type": "project", "license": "GPL-2.0-or-later", - "require": {}, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.60", + "friendsofphp/php-cs-fixer": "^3.93", "phpstan/phpstan": "^1.11", "overtrue/phplint": "^9.2" }, "scripts": { - "lint": "./vendor/bin/phplint -c .github/.phplint.yml", - "phpcsfixer": "./vendor/bin/php-cs-fixer fix --dry-run --diff --config .github/.php-cs-fixer.php", - "phpstan": "./vendor/bin/phpstan analyse -c .github/.phpstan.neon" + "lint": "bash -c 'cd ../../ && plugins/maint/vendor/bin/phplint plugins/maint --exclude=vendor'", + "phpcsfixer": "bash -c 'cd ../../ && plugins/maint/vendor/bin/php-cs-fixer fix --dry-run --diff --config=.php-cs-fixer.php plugins/maint'", + "phpstan": "bash -c 'cd ../../ && plugins/maint/vendor/bin/phpstan analyse --level=6 plugins/maint'" } } diff --git a/functions.php b/functions.php index 48f8430..44a91a9 100644 --- a/functions.php +++ b/functions.php @@ -24,92 +24,134 @@ +-------------------------------------------------------------------------+ */ -function plugin_maint_check_cacti_host($host) -{ - return plugin_maint_check_host(1, $host); +/** + * Check if a Cacti host is in maintenance + * + * This is called via the 'is_device_in_maintenance' hook. + * + * @param int $host Host ID to check + * + * @return bool True if host is in active maintenance, false otherwise + */ +function plugin_maint_check_cacti_host(int $host): bool { + return plugin_maint_check_host(1, $host); } -function plugin_maint_check_webseer_url($host) -{ - return plugin_maint_check_host(2, $host); +/** + * Check if a WebSeer URL is in maintenance + * + * @param int $host WebSeer URL ID to check + * + * @return bool True if URL is in active maintenance, false otherwise + */ +function plugin_maint_check_webseer_url(int $host): bool { + return plugin_maint_check_host(2, $host); } -function plugin_maint_check_servcheck_test($host) -{ - return plugin_maint_check_host(3, $host); +/** + * Check if a Servcheck test is in maintenance + * + * @param int $host Servcheck test ID to check + * + * @return bool True if test is in active maintenance, false otherwise + */ +function plugin_maint_check_servcheck_test(int $host): bool { + return plugin_maint_check_host(3, $host); } - -function plugin_maint_check_host($type, $host) -{ - $schedules = db_fetch_assoc_prepared( - 'SELECT * +/** + * Check if a host is in maintenance based on type + * + * @param int $type Host type (1=Cacti host, 2=WebSeer URL, 3=Servcheck test) + * @param int $host Host/URL/Test ID to check + * + * @return bool True if host is in active maintenance schedule, false otherwise + */ +function plugin_maint_check_host(int $type, int $host): bool { + $schedules = db_fetch_assoc_prepared( + 'SELECT * FROM plugin_maint_hosts WHERE TYPE = ? AND (host = ? OR host = 0)', - [$type, $host], - ); - - if (!empty($schedules)) { - foreach ($schedules as $s) { - if (plugin_maint_check_schedule($s['schedule'])) { - return true; - } - } - } - return false; + [$type, $host], + ); + + if (!empty($schedules)) { + foreach ($schedules as $s) { + if (plugin_maint_check_schedule($s['schedule'])) { + return true; + } + } + } + + return false; } -function plugin_maint_check_schedule($schedule) -{ - $sc = db_fetch_row_prepared( - 'SELECT * +/** + * Check if a maintenance schedule is currently active + * + * Handles both one-time and recurring schedules. + * For recurring schedules that have passed, automatically calculates + * and updates the next occurrence. + * + * @param int $schedule Schedule ID to check + * + * @return bool True if schedule is active now, false otherwise + */ +function plugin_maint_check_schedule(int $schedule): bool { + $sc = db_fetch_row_prepared( + 'SELECT * FROM plugin_maint_schedules WHERE enabled = \'on\' AND id = ?', - [$schedule], - ); - - if (!empty($sc)) { - $t = time(); - switch ($sc['mtype']) { - case 1: - if ($t > $sc['stime'] && $t < $sc['etime']) { - return true; - } - break; - - case 2: // Recurring - /* past, calculate next */ - if ($sc['etime'] < $t) { - /* convert start and end to local so that hour stays same for add days across daylight saving time change */ - $starttimelocal = (new DateTime('@' . strval($sc['stime'])))->setTimezone(new DateTimeZone(date_default_timezone_get())); - $endtimelocal = (new DateTime('@' . strval($sc['etime'])))->setTimezone(new DateTimeZone(date_default_timezone_get())); - $nowtime = new DateTime(); - /* add interval days */ - $addday = new DateInterval('P' . strval($sc['minterval'] / 86400) . 'D'); - while ($endtimelocal < $nowtime) { - $starttimelocal = $starttimelocal->add($addday); - $endtimelocal = $endtimelocal->add($addday); - } - - $sc['stime'] = $starttimelocal->getTimestamp(); - $sc['etime'] = $endtimelocal->getTimestamp(); - /* save next interval so not need to recalculate */ - db_execute_prepared( - 'UPDATE plugin_maint_schedules + [$schedule], + ); + + if (!empty($sc)) { + $t = time(); + + switch ($sc['mtype']) { + case 1: + if ($t > $sc['stime'] && $t < $sc['etime']) { + return true; + } + + break; + case 2: // Recurring + // past, calculate next + if ($sc['etime'] < $t) { + // convert start and end to local so that hour stays same for add days across daylight saving time change + $starttimelocal = (new DateTime('@' . strval($sc['stime'])))->setTimezone(new DateTimeZone(date_default_timezone_get())); + $endtimelocal = (new DateTime('@' . strval($sc['etime'])))->setTimezone(new DateTimeZone(date_default_timezone_get())); + $nowtime = new DateTime(); + // add interval days + $addday = new DateInterval('P' . strval($sc['minterval'] / 86400) . 'D'); + + while ($endtimelocal < $nowtime) { + $starttimelocal = $starttimelocal->add($addday); + $endtimelocal = $endtimelocal->add($addday); + } + + $sc['stime'] = $starttimelocal->getTimestamp(); + $sc['etime'] = $endtimelocal->getTimestamp(); + // save next interval so not need to recalculate + db_execute_prepared( + 'UPDATE plugin_maint_schedules SET stime = ?, etime = ? WHERE id = ?', - [$sc['stime'], $sc['etime'], $schedule], - ); - /* format yyyy-mm-dd hh:mm */ - cacti_log('INFO: Maintenance schedule "' . $sc['name'] . '" Next start ' . $starttimelocal->format('Y-m-d H:i') - . ' End ' . $endtimelocal->format('Y-m-d H:i'), false, 'MAINT'); - } - if ($t > $sc['stime'] && $t < $sc['etime']) { - return true; - } - break; - } - } - return false; + [$sc['stime'], $sc['etime'], $schedule], + ); + // format yyyy-mm-dd hh:mm + cacti_log('INFO: Maintenance schedule "' . $sc['name'] . '" Next start ' . $starttimelocal->format('Y-m-d H:i') + . ' End ' . $endtimelocal->format('Y-m-d H:i'), false, 'MAINT'); + } + + if ($t > $sc['stime'] && $t < $sc['etime']) { + return true; + } + + break; + } + } + + return false; } diff --git a/maint.php b/maint.php index 6a7428a..29d839b 100644 --- a/maint.php +++ b/maint.php @@ -34,53 +34,53 @@ // Maint Schedule Actions $actions = [ - 1 => __('Update Time (Now + 1 Hour)', 'maint'), - 2 => __('Delete', 'maint'), + 1 => __('Update Time (Now + 1 Hour)', 'maint'), + 2 => __('Delete', 'maint'), ]; // Host Maint Schedule Actions $assoc_actions = [ - 1 => __('Associate', 'maint'), - 2 => __('Disassociate', 'maint'), + 1 => __('Associate', 'maint'), + 2 => __('Disassociate', 'maint'), ]; $maint_types = [ - 1 => __('One Time', 'maint'), - 2 => __('Recurring', 'maint'), + 1 => __('One Time', 'maint'), + 2 => __('Recurring', 'maint'), ]; $maint_intervals = [ - 0 => __('Not Defined', 'maint'), - 86400 => __('Every Day', 'maint'), - 604800 => __('Every Week', 'maint'), + 0 => __('Not Defined', 'maint'), + 86400 => __('Every Day', 'maint'), + 604800 => __('Every Week', 'maint'), ]; $yesno = [ - '' => __('No', 'maint'), // table value - 0 => __('No', 'maint'), - 1 => __('Yes', 'maint'), - 'on' => __('Yes', 'maint'), - 'off' => __('No', 'maint'), + '' => __('No', 'maint'), // table value + 0 => __('No', 'maint'), + 1 => __('Yes', 'maint'), + 'on' => __('Yes', 'maint'), + 'off' => __('No', 'maint'), ]; // Present a tabbed interface $tabs = [ - 'general' => __('General', 'maint'), + 'general' => __('General', 'maint'), ]; if (api_plugin_is_enabled('thold')) { - $tabs['hosts'] = __('Thold Devices', 'maint'); - define('MAINT_HOST_TYPE_HOSTS', '1'); + $tabs['hosts'] = __('Thold Devices', 'maint'); + define('MAINT_HOST_TYPE_HOSTS', '1'); } if (api_plugin_is_enabled('webseer')) { - $tabs['webseer'] = __('WebSeer', 'maint'); - define('MAINT_HOST_TYPE_WEBSEER', '2'); + $tabs['webseer'] = __('WebSeer', 'maint'); + define('MAINT_HOST_TYPE_WEBSEER', '2'); } if (api_plugin_is_enabled('servcheck')) { - $tabs['servcheck'] = __('Servcheck', 'maint'); - define('MAINT_HOST_TYPE_SERVCHECK', '3'); + $tabs['servcheck'] = __('Servcheck', 'maint'); + define('MAINT_HOST_TYPE_SERVCHECK', '3'); } $tabs = api_plugin_hook_function('maint_tabs', $tabs); @@ -88,294 +88,340 @@ set_default_action(); switch (get_request_var('action')) { - case 'save': - form_save(); - break; - case 'actions': - form_actions(); - break; - case 'edit': - top_header(); - schedule_edit(); - bottom_footer(); - break; - default: - top_header(); - schedules(); - bottom_footer(); - break; + case 'save': + form_save(); + + break; + case 'actions': + form_actions(); + + break; + case 'edit': + top_header(); + schedule_edit(); + bottom_footer(); + + break; + default: + top_header(); + schedules(); + bottom_footer(); + + break; } -function schedule_delete() -{ - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); +/** + * Delete selected maintenance schedules + * + * Deletes schedules and all associated host mappings. + * Redirects to main page after deletion. + * + * @return void This function exits after redirect + */ +function schedule_delete(): void { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - if ($selected_items != false) { - foreach ($selected_items as $id) { - db_execute_prepared('DELETE FROM plugin_maint_schedules WHERE id=? LIMIT 1', [$id]); - db_execute_prepared('DELETE FROM plugin_maint_hosts WHERE schedule = ?', [$id]); - } - } + if ($selected_items != false) { + foreach ($selected_items as $id) { + db_execute_prepared('DELETE FROM plugin_maint_schedules WHERE id=? LIMIT 1', [$id]); + db_execute_prepared('DELETE FROM plugin_maint_hosts WHERE schedule = ?', [$id]); + } + } - header('Location: maint.php?header=false'); + header('Location: maint.php?header=false'); - exit; + exit; } -function schedule_update() -{ - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - if ($selected_items != false) { - foreach ($selected_items as $id) { - $stime = intval(time() / 60) * 60; - $etime = $stime + 3600; - db_execute_prepared( - 'UPDATE plugin_maint_schedules +/** + * Update selected schedules to start now and end in 1 hour + * + * Sets the start time to current time (rounded to nearest minute) + * and end time to 1 hour later. Redirects to main page after update. + * + * @return void This function exits after redirect + */ +function schedule_update(): void { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + + if ($selected_items != false) { + foreach ($selected_items as $id) { + $stime = intval(time() / 60) * 60; + $etime = $stime + 3600; + db_execute_prepared( + 'UPDATE plugin_maint_schedules SET stime = ?, etime = ? WHERE id = ? LIMIT 1', - [$stime, $etime, $id], - ); - } - } + [$stime, $etime, $id], + ); + } + } - header('Location: maint.php?header=false'); + header('Location: maint.php?header=false'); - exit; + exit; } +/** + * Save a maintenance schedule from edit form + * + * Validates and saves schedule data including: + * - Name, type (one-time or recurring), enabled status + * - Start/end times, interval for recurring schedules + * Redirects to edit page after save. + * + * @return void This function exits after redirect + */ +function form_save(): void { + global $plugins; + + if (isset_request_var('save_component')) { + // ================= input validation ================= + get_filter_request_var('id'); + get_filter_request_var('mtype'); + get_filter_request_var('minterval'); + + if (isset_request_var('name')) { + // Remove HTML <> + set_request_var('name', trim(str_replace(['\\', "'", '"', '<', '>'], '', get_nfilter_request_var('name')))); + } + + if (isset_request_var('stime')) { + set_request_var('stime', trim(str_replace(['\\', "'", '"'], '', get_nfilter_request_var('stime')))); + } + + if (isset_request_var('etime')) { + set_request_var('etime', trim(str_replace(['\\', "'", '"'], '', get_nfilter_request_var('etime')))); + } + // ==================================================== + + $save['id'] = get_nfilter_request_var('id'); + $save['name'] = get_nfilter_request_var('name'); + $save['mtype'] = get_nfilter_request_var('mtype'); + $save['stime'] = strtotime(get_nfilter_request_var('stime')); + $save['etime'] = strtotime(get_nfilter_request_var('etime')); + $save['minterval'] = get_nfilter_request_var('minterval'); + + if (isset_request_var('enabled')) { + $save['enabled'] = 'on'; + } else { + $save['enabled'] = ''; + } -function form_save() -{ - global $plugins; - - if (isset_request_var('save_component')) { - /* ================= input validation ================= */ - get_filter_request_var('id'); - get_filter_request_var('mtype'); - get_filter_request_var('minterval'); - - if (isset_request_var('name')) { - /* Remove HTML <> */ - set_request_var('name', trim(str_replace(["\\", "'", '"', '<', '>'], '', get_nfilter_request_var('name')))); - } - if (isset_request_var('stime')) { - set_request_var('stime', trim(str_replace(["\\", "'", '"'], '', get_nfilter_request_var('stime')))); - } - if (isset_request_var('etime')) { - set_request_var('etime', trim(str_replace(["\\", "'", '"'], '', get_nfilter_request_var('etime')))); - } - /* ==================================================== */ - - $save['id'] = get_nfilter_request_var('id'); - $save['name'] = get_nfilter_request_var('name'); - $save['mtype'] = get_nfilter_request_var('mtype'); - $save['stime'] = strtotime(get_nfilter_request_var('stime')); - $save['etime'] = strtotime(get_nfilter_request_var('etime')); - $save['minterval'] = get_nfilter_request_var('minterval'); - - if (isset_request_var('enabled')) { - $save['enabled'] = 'on'; - } else { - $save['enabled'] = ''; - } - - if ($save['mtype'] == 1) { - $save['minterval'] = 0; - } - - if ($save['stime'] >= $save['etime']) { - raise_message(2); - } - - if (!is_error_message()) { - $id = sql_save($save, 'plugin_maint_schedules'); - if ($id) { - raise_message(1); - } else { - raise_message(2); - } - } - - header('Location: maint.php?tab=general&action=edit&header=false&id=' . (empty($id) ? $save['id'] : $id)); - - exit; - } + if ($save['mtype'] == 1) { + $save['minterval'] = 0; + } + + if ($save['stime'] >= $save['etime']) { + raise_message(2); + } + + if (!is_error_message()) { + $id = sql_save($save, 'plugin_maint_schedules'); + + if ($id) { + raise_message(1); + } else { + raise_message(2); + } + } + + header('Location: maint.php?tab=general&action=edit&header=false&id=' . (empty($id) ? $save['id'] : $id)); + + exit; + } } -function form_actions() -{ - global $actions, $assoc_actions; - - /* ================= input validation ================= */ - get_filter_request_var('id'); - get_filter_request_var('drp_action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z0-9_]+)$/']]); - /* ================= input validation ================= */ - - /* if we are to save this form, instead of display it */ - if (isset_request_var('selected_items')) { - if (isset_request_var('save_list')) { - if (get_request_var('drp_action') == '2') { /* delete */ - schedule_delete(); - } elseif (get_request_var('drp_action') == '1') { /* update */ - schedule_update(); - } - - header('Location: maint.php?header=false'); - - exit; - } elseif (isset_request_var('save_hosts')) { - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - - if ($selected_items != false) { - if (get_request_var('drp_action') == '1') { /* associate */ - for ($i = 0; ($i < count($selected_items)); $i++) { - db_execute_prepared( - 'REPLACE INTO plugin_maint_hosts (type, host, schedule) +/** + * Handle form actions for schedules and host associations + * + * Processes: + * - Schedule actions (delete, update time) + * - Host association actions (associate, disassociate) + * - WebSeer URL associations + * - Servcheck test associations + * + * @return void This function may exit after processing + */ +function form_actions(): void { + global $actions, $assoc_actions; + + // ================= input validation ================= + get_filter_request_var('id'); + get_filter_request_var('drp_action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z0-9_]+)$/']]); + + // ================= input validation ================= // if we are to save this form, instead of display it + if (isset_request_var('selected_items')) { + if (isset_request_var('save_list')) { + if (get_request_var('drp_action') == '2') { // delete + schedule_delete(); + } elseif (get_request_var('drp_action') == '1') { // update + schedule_update(); + } + + header('Location: maint.php?header=false'); + + exit; + } + + if (isset_request_var('save_hosts')) { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + + if ($selected_items != false) { + if (get_request_var('drp_action') == '1') { // associate + for ($i = 0; ($i < count($selected_items)); $i++) { + db_execute_prepared( + 'REPLACE INTO plugin_maint_hosts (type, host, schedule) VALUES (?, ?, ?)', - [MAINT_HOST_TYPE_HOSTS, $selected_items[$i], get_request_var('id')], - ); - } - } elseif (get_request_var('drp_action') == '2') { /* disassociate */ - for ($i = 0; ($i < count($selected_items)); $i++) { - db_execute_prepared( - 'DELETE FROM plugin_maint_hosts + [MAINT_HOST_TYPE_HOSTS, $selected_items[$i], get_request_var('id')], + ); + } + } elseif (get_request_var('drp_action') == '2') { // disassociate + for ($i = 0; ($i < count($selected_items)); $i++) { + db_execute_prepared( + 'DELETE FROM plugin_maint_hosts WHERE type = ? AND host = ? AND schedule = ?', - [MAINT_HOST_TYPE_HOSTS, $selected_items[$i], get_request_var('id')], - ); - } - } - } - - header('Location: maint.php?action=edit&tab=hosts&header=false&id=' . get_request_var('id')); - - exit; - } elseif (isset_request_var('save_webseer')) { - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - - if ($selected_items != false) { - if (get_request_var('drp_action') == '1') { /* associate */ - for ($i = 0; ($i < count($selected_items)); $i++) { - db_execute_prepared( - 'REPLACE INTO plugin_maint_hosts (type, host, schedule) + [MAINT_HOST_TYPE_HOSTS, $selected_items[$i], get_request_var('id')], + ); + } + } + } + + header('Location: maint.php?action=edit&tab=hosts&header=false&id=' . get_request_var('id')); + + exit; + } + + if (isset_request_var('save_webseer')) { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + + if ($selected_items != false) { + if (get_request_var('drp_action') == '1') { // associate + for ($i = 0; ($i < count($selected_items)); $i++) { + db_execute_prepared( + 'REPLACE INTO plugin_maint_hosts (type, host, schedule) VALUES (?, ?, ?)', - [MAINT_HOST_TYPE_WEBSEER, $selected_items[$i], get_request_var('id')], - ); - } - } elseif (get_request_var('drp_action') == '2') { /* disassociate */ - for ($i = 0; ($i < count($selected_items)); $i++) { - db_execute_prepared( - 'DELETE FROM plugin_maint_hosts + [MAINT_HOST_TYPE_WEBSEER, $selected_items[$i], get_request_var('id')], + ); + } + } elseif (get_request_var('drp_action') == '2') { // disassociate + for ($i = 0; ($i < count($selected_items)); $i++) { + db_execute_prepared( + 'DELETE FROM plugin_maint_hosts WHERE type = ? AND host = ? AND schedule = ?', - [MAINT_HOST_TYPE_WEBSEER, $selected_items[$i], get_request_var('id')], - ); - } - } - } - - header('Location: maint.php?action=edit&tab=webseer&header=false&id=' . get_request_var('id')); - - exit; - } elseif (isset_request_var('save_servcheck')) { - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - - if ($selected_items != false) { - if (get_request_var('drp_action') == '1') { /* associate */ - for ($i = 0; ($i < count($selected_items)); $i++) { - db_execute_prepared( - 'REPLACE INTO plugin_maint_hosts (type, host, schedule) + [MAINT_HOST_TYPE_WEBSEER, $selected_items[$i], get_request_var('id')], + ); + } + } + } + + header('Location: maint.php?action=edit&tab=webseer&header=false&id=' . get_request_var('id')); + + exit; + } + + if (isset_request_var('save_servcheck')) { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + + if ($selected_items != false) { + if (get_request_var('drp_action') == '1') { // associate + for ($i = 0; ($i < count($selected_items)); $i++) { + db_execute_prepared( + 'REPLACE INTO plugin_maint_hosts (type, host, schedule) VALUES (?, ?, ?)', - [MAINT_HOST_TYPE_SERVCHECK, $selected_items[$i], get_request_var('id')], - ); - } - } elseif (get_request_var('drp_action') == '2') { /* disassociate */ - for ($i = 0; ($i < count($selected_items)); $i++) { - db_execute_prepared( - 'DELETE FROM plugin_maint_hosts + [MAINT_HOST_TYPE_SERVCHECK, $selected_items[$i], get_request_var('id')], + ); + } + } elseif (get_request_var('drp_action') == '2') { // disassociate + for ($i = 0; ($i < count($selected_items)); $i++) { + db_execute_prepared( + 'DELETE FROM plugin_maint_hosts WHERE type = ? AND host = ? AND schedule = ?', - [MAINT_HOST_TYPE_SERVCHECK, $selected_items[$i], get_request_var('id')], - ); - } - } - } - - header('Location: maint.php?action=edit&tab=servcheck&header=false&id=' . get_request_var('id')); - - exit; - } else { - api_plugin_hook_function('maint_actions_execute'); - } - } - - /* setup some variables */ - $list = ''; - $array = []; - $list_name = ''; - if (isset_request_var('id')) { - $list_name = html_escape( - db_fetch_cell_prepared( - 'SELECT name + [MAINT_HOST_TYPE_SERVCHECK, $selected_items[$i], get_request_var('id')], + ); + } + } + } + + header('Location: maint.php?action=edit&tab=servcheck&header=false&id=' . get_request_var('id')); + + exit; + } else { + api_plugin_hook_function('maint_actions_execute'); + } + } + + // setup some variables + $list = ''; + $array = []; + $list_name = ''; + + if (isset_request_var('id')) { + $list_name = html_escape( + db_fetch_cell_prepared( + 'SELECT name FROM plugin_maint_schedules WHERE id = ?', - [get_request_var('id')], - ), - ); - } - - if (isset_request_var('save_list')) { - /* loop through each of the notification lists selected on the previous page and get more info about them */ - foreach ($_POST as $var => $val) { - if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) { - /* ================= input validation ================= */ - input_validate_input_number($matches[1]); - /* ==================================================== */ - - $list .= '
  • ' . html_escape( - db_fetch_cell_prepared( - 'SELECT name + [get_request_var('id')], + ), + ); + } + + if (isset_request_var('save_list')) { + // loop through each of the notification lists selected on the previous page and get more info about them + foreach ($_POST as $var => $val) { + if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) { + // ================= input validation ================= + input_validate_input_number($matches[1]); + // ==================================================== + + $list .= '
  • ' . html_escape( + db_fetch_cell_prepared( + 'SELECT name FROM plugin_maint_schedules WHERE id=?', - [$matches[1]], - ), - ) - . '
  • '; - $array[] = $matches[1]; - } - } + [$matches[1]], + ), + ) + . ''; + $array[] = $matches[1]; + } + } - top_header(); + top_header(); - form_start('maint.php'); + form_start('maint.php'); - html_start_box($actions[get_request_var('drp_action')] . " $list_name", '60%', '', '3', 'center', ''); + html_start_box($actions[get_request_var('drp_action')] . " $list_name", '60%', '', '3', 'center', ''); - if (cacti_sizeof($array)) { - if (get_request_var('drp_action') == '1') { /* update */ - print " + if (cacti_sizeof($array)) { + if (get_request_var('drp_action') == '1') { // update + print "

    " . __('Click \'Continue\' to Update the following Maintenance Schedule(s).', 'maint') . "

    "; - $save_html = " - "; - } elseif (get_request_var('drp_action') == '2') { /* delete */ - print " + $save_html = " + '; + } elseif (get_request_var('drp_action') == '2') { // delete + print "

    " . __('Click \'Continue\' to Delete the following Maintenance Schedule(s). Any Devices(s) Associated with this Schedule will be Disassociated.', 'maint') . "

    "; - $save_html = " "; - } - } else { - print "" . __('You must select at least one Maintenance Schedule.', 'maint') . ""; - $save_html = " - "; - } elseif (get_request_var('drp_action') == '2') { /* disassociate */ - print " + $save_html = " + '; + } elseif (get_request_var('drp_action') == '2') { // disassociate + print "

    " . __('Click \'Continue\' to Disassociate the following Device(s) with the Maintenance Schedule \'%s\'.', $list_name, 'maint') . "

    "; - $save_html = " - "; - } - } else { - print "" . __('You must select at least one Device.', 'maint') . ""; - $save_html = ""; - } + $save_html = " + '; + } + } else { + print "" . __('You must select at least one Device.', 'maint') . ''; + $save_html = "'; + } - print " + print " @@ -455,65 +501,65 @@ function form_actions() "; - html_end_box(); + html_end_box(); - form_end(); + form_end(); - bottom_footer(); - } elseif (isset_request_var('save_webseer')) { - /* loop through each of the notification lists selected on the previous page and get more info about them */ - foreach ($_POST as $var => $val) { - if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) { - /* ================= input validation ================= */ - input_validate_input_number($matches[1]); - /* ==================================================== */ + bottom_footer(); + } elseif (isset_request_var('save_webseer')) { + // loop through each of the notification lists selected on the previous page and get more info about them + foreach ($_POST as $var => $val) { + if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) { + // ================= input validation ================= + input_validate_input_number($matches[1]); + // ==================================================== - $description = db_fetch_cell_prepared( - 'SELECT display_name + $description = db_fetch_cell_prepared( + 'SELECT display_name FROM plugin_webseer_urls WHERE id = ?', - [$matches[1]], - ); + [$matches[1]], + ); - $list .= '
  • ' . html_escape($description) . '
  • '; - $array[] = $matches[1]; - } - } + $list .= '
  • ' . html_escape($description) . '
  • '; + $array[] = $matches[1]; + } + } - top_header(); + top_header(); - form_start('maint.php'); + form_start('maint.php'); - html_start_box($assoc_actions[get_request_var('drp_action')] . ' ' . __('Webseer(s)', 'maint'), '60%', '', '3', 'center', ''); + html_start_box($assoc_actions[get_request_var('drp_action')] . ' ' . __('Webseer(s)', 'maint'), '60%', '', '3', 'center', ''); - if (cacti_sizeof($array)) { - if (get_request_var('drp_action') == '1') { /* associate */ - print " + if (cacti_sizeof($array)) { + if (get_request_var('drp_action') == '1') { // associate + print "

    " . __('Click \'Continue\' to Associate the Webseer(s) below with the Maintenance Schedule \'%s\'.', $list_name, 'maint') . "

    "; - $save_html = " - "; - } elseif (get_request_var('drp_action') == '2') { /* disassociate */ - print " + $save_html = " + '; + } elseif (get_request_var('drp_action') == '2') { // disassociate + print "

    " . __('Click \'Continue\' to Disassociate the Webseer(s) below from the Maintenance Schedule \'%s\'.', $list_name, 'maint') . "

    "; - $save_html = " - "; - } - } else { - print "" . __('You must select at least one Webseer.', 'maint') . ""; - $save_html = ""; - } + $save_html = " + '; + } + } else { + print "" . __('You must select at least one Webseer.', 'maint') . ''; + $save_html = "'; + } - print " + print " @@ -524,65 +570,65 @@ function form_actions() "; - html_end_box(); + html_end_box(); - form_end(); + form_end(); - bottom_footer(); - } elseif (isset_request_var('save_servcheck')) { - /* loop through each of the notification lists selected on the previous page and get more info about them */ - foreach ($_POST as $var => $val) { - if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) { - /* ================= input validation ================= */ - input_validate_input_number($matches[1]); - /* ==================================================== */ + bottom_footer(); + } elseif (isset_request_var('save_servcheck')) { + // loop through each of the notification lists selected on the previous page and get more info about them + foreach ($_POST as $var => $val) { + if (preg_match('/^chk_([0-9]+)$/', $var, $matches)) { + // ================= input validation ================= + input_validate_input_number($matches[1]); + // ==================================================== - $description = db_fetch_cell_prepared( - 'SELECT display_name + $description = db_fetch_cell_prepared( + 'SELECT display_name FROM plugin_servcheck_test WHERE id = ?', - [$matches[1]], - ); + [$matches[1]], + ); - $list .= '
  • ' . html_escape($description) . '
  • '; - $array[] = $matches[1]; - } - } + $list .= '
  • ' . html_escape($description) . '
  • '; + $array[] = $matches[1]; + } + } - top_header(); + top_header(); - form_start('maint.php'); + form_start('maint.php'); - html_start_box($assoc_actions[get_request_var('drp_action')] . ' ' . __('Servcheck(s)', 'maint'), '60%', '', '3', 'center', ''); + html_start_box($assoc_actions[get_request_var('drp_action')] . ' ' . __('Servcheck(s)', 'maint'), '60%', '', '3', 'center', ''); - if (cacti_sizeof($array)) { - if (get_request_var('drp_action') == '1') { /* associate */ - print " + if (cacti_sizeof($array)) { + if (get_request_var('drp_action') == '1') { // associate + print "

    " . __('Click \'Continue\' to Associate the Servcheck(s) below with the Maintenance Schedule \'%s\'.', $list_name, 'maint') . "

    "; - $save_html = " - "; - } elseif (get_request_var('drp_action') == '2') { /* disassociate */ - print " + $save_html = " + '; + } elseif (get_request_var('drp_action') == '2') { // disassociate + print "

    " . __('Click \'Continue\' to Disassociate the Servcheck(s) below from the Maintenance Schedule \'%s\'.', $list_name, 'maint') . "

    "; - $save_html = " - " . __esc('Continue', 'maint') . ""; - } - } else { - print "" . __('You must select at least one Servcheck test.', 'maint') . ""; - $save_html = ""; - } + $save_html = " + " . __esc('Continue', 'maint') . ''; + } + } else { + print "" . __('You must select at least one Servcheck test.', 'maint') . ''; + $save_html = "'; + } - print " + print " @@ -593,171 +639,195 @@ function form_actions() "; - html_end_box(); + html_end_box(); - form_end(); + form_end(); - bottom_footer(); - } else { - api_plugin_hook_function('maint_actions_prepare'); - } + bottom_footer(); + } else { + api_plugin_hook_function('maint_actions_prepare'); + } } -function get_header_label() -{ - if (!isempty_request_var('id')) { - $list = db_fetch_row_prepared( - 'SELECT * +/** + * Get the header label for the current schedule + * + * @return string Header label showing schedule name for edit or '[new]' for new schedule + */ +function get_header_label(): string { + if (!isempty_request_var('id')) { + $list = db_fetch_row_prepared( + 'SELECT * FROM plugin_maint_schedules WHERE id = ?', - [get_filter_request_var('id')], - ); - $header_label = __esc('[edit: %s]', $list['name'], 'maint'); - } else { - $header_label = __('[new]', 'maint'); - } - - return $header_label; + [get_filter_request_var('id')], + ); + $header_label = __esc('[edit: %s]', $list['name'], 'maint'); + } else { + $header_label = __('[new]', 'maint'); + } + + return $header_label; } -function maint_tabs() -{ - global $config, $tabs; - - /* ================= input validation ================= */ - get_filter_request_var('id'); - get_filter_request_var('tab', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z]+)$/']]); - /* ==================================================== */ - - load_current_session_value('tab', 'sess_maint_tab', 'general'); - $current_tab = get_request_var('tab'); - - print "
    "; +/** + * Display maintenance schedule tabs + * + * Shows tabs for: + * - General settings + * - Thold devices (if plugin enabled) + * - WebSeer URLs (if plugin enabled) + * - Servcheck tests (if plugin enabled) + * + * @return void + */ +function maint_tabs(): void { + global $config, $tabs; + + // ================= input validation ================= + get_filter_request_var('id'); + get_filter_request_var('tab', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z]+)$/']]); + // ==================================================== + + load_current_session_value('tab', 'sess_maint_tab', 'general'); + $current_tab = get_request_var('tab'); + + print "
    '; } -function schedule_edit() -{ - global $plugins, $config, $tabs, $maint_types, $maint_intervals; +/** + * Display schedule edit form + * + * Shows the appropriate tab content based on current tab: + * - General: Schedule settings form + * - hosts: Thold device associations + * - webseer: WebSeer URL associations + * - servcheck: Servcheck test associations + * + * @return void + */ +function schedule_edit(): void { + global $plugins, $config, $tabs, $maint_types, $maint_intervals; - /* ================= input validation ================= */ - get_filter_request_var('id'); - /* ==================================================== */ + // ================= input validation ================= + get_filter_request_var('id'); + // ==================================================== - maint_tabs(); + maint_tabs(); - if (isset_request_var('id')) { - $id = get_request_var('id'); - $maint_item_data = db_fetch_row_prepared( - 'SELECT * + if (isset_request_var('id')) { + $id = get_request_var('id'); + $maint_item_data = db_fetch_row_prepared( + 'SELECT * FROM plugin_maint_schedules WHERE id = ?', - [$id], - ); - } else { - $id = 0; - $maint_item_data = ['id' => 0, 'name' => __('New Maintenance Schedule', 'maint'), 'enabled' => 'on', 'mtype' => 1, 'stime' => time(), 'etime' => time() + 3600, 'minterval' => 0]; - } - - $header_label = get_header_label(); - - if (get_request_var('tab') == 'general') { - form_start('maint.php', 'maint'); - - html_start_box(__('General Settings %s', htmlspecialchars($header_label), 'maint'), '100%', '', '3', 'center', ''); - - $form_array = [ - 'general_header' => [ - 'friendly_name' => __('Schedule', 'maint'), - 'method' => 'spacer', - ], - 'name' => [ - 'friendly_name' => __('Schedule Name', 'maint'), - 'method' => 'textbox', - 'max_length' => 100, - 'default' => $maint_item_data['name'], - 'description' => __('Provide the Maintenance Schedule a meaningful name', 'maint'), - 'value' => isset($maint_item_data['name']) ? $maint_item_data['name'] : '', - ], - 'enabled' => [ - 'friendly_name' => __('Enabled', 'maint'), - 'method' => 'checkbox', - 'default' => 'on', - 'description' => __('Whether or not this schedule will be checked.', 'maint'), - 'value' => isset($maint_item_data['enabled']) ? ($maint_item_data['enabled'] == 'on' ? 'on' : 'off') : '', - ], - 'mtype' => [ - 'friendly_name' => __('Schedule Type', 'maint'), - 'method' => 'drop_array', - 'on_change' => 'changemaintType()', - 'array' => $maint_types, - 'description' => __('The type of schedule, one time or recurring.', 'maint'), - 'value' => isset($maint_item_data['mtype']) ? $maint_item_data['mtype'] : '', - ], - 'minterval' => [ - 'friendly_name' => __('Interval', 'maint'), - 'method' => 'drop_array', - 'array' => $maint_intervals, - 'default' => 86400, - 'description' => __('This is the interval in which the start / end time will repeat.', 'maint'), - 'value' => isset($maint_item_data['minterval']) ? $maint_item_data['minterval'] : '1', - ], - 'stime' => [ - 'friendly_name' => __('Start Time', 'maint'), - 'method' => 'textbox', - 'max_length' => 22, - 'size' => 22, - 'description' => __('The start date / time for this schedule. Most date / time formats accepted.', 'maint'), - 'default' => date('Y-m-d H:i', time()), - 'value' => isset($maint_item_data['stime']) ? date('Y-m-d H:i', $maint_item_data['stime']) : '', - ], - 'etime' => [ - 'friendly_name' => __('End Time', 'maint'), - 'method' => 'textbox', - 'max_length' => 22, - 'size' => 22, - 'default' => date('Y-m-d H:i', time() + 3600), - 'description' => __('The end date / time for this schedule. Most date / time formats accepted.', 'maint'), - 'value' => isset($maint_item_data['etime']) ? date('Y-m-d H:i', $maint_item_data['etime']) : '', - ], - 'save_component' => [ - 'method' => 'hidden', - 'value' => '1', - ], - 'save' => [ - 'method' => 'hidden', - 'value' => 'edit', - ], - 'id' => [ - 'method' => 'hidden', - 'value' => $id, - ], - ]; - - draw_edit_form( - [ - 'config' => [ - 'no_form_tag' => true, - ], - 'fields' => $form_array, - ], - ); - - html_end_box(); - - form_save_button('maint.php', 'return'); - - ?> + [$id], + ); + } else { + $id = 0; + $maint_item_data = ['id' => 0, 'name' => __('New Maintenance Schedule', 'maint'), 'enabled' => 'on', 'mtype' => 1, 'stime' => time(), 'etime' => time() + 3600, 'minterval' => 0]; + } + + $header_label = get_header_label(); + + if (get_request_var('tab') == 'general') { + form_start('maint.php', 'maint'); + + html_start_box(__('General Settings %s', htmlspecialchars($header_label), 'maint'), '100%', '', '3', 'center', ''); + + $form_array = [ + 'general_header' => [ + 'friendly_name' => __('Schedule', 'maint'), + 'method' => 'spacer', + ], + 'name' => [ + 'friendly_name' => __('Schedule Name', 'maint'), + 'method' => 'textbox', + 'max_length' => 100, + 'default' => $maint_item_data['name'], + 'description' => __('Provide the Maintenance Schedule a meaningful name', 'maint'), + 'value' => isset($maint_item_data['name']) ? $maint_item_data['name'] : '', + ], + 'enabled' => [ + 'friendly_name' => __('Enabled', 'maint'), + 'method' => 'checkbox', + 'default' => 'on', + 'description' => __('Whether or not this schedule will be checked.', 'maint'), + 'value' => isset($maint_item_data['enabled']) ? ($maint_item_data['enabled'] == 'on' ? 'on' : 'off') : '', + ], + 'mtype' => [ + 'friendly_name' => __('Schedule Type', 'maint'), + 'method' => 'drop_array', + 'on_change' => 'changemaintType()', + 'array' => $maint_types, + 'description' => __('The type of schedule, one time or recurring.', 'maint'), + 'value' => isset($maint_item_data['mtype']) ? $maint_item_data['mtype'] : '', + ], + 'minterval' => [ + 'friendly_name' => __('Interval', 'maint'), + 'method' => 'drop_array', + 'array' => $maint_intervals, + 'default' => 86400, + 'description' => __('This is the interval in which the start / end time will repeat.', 'maint'), + 'value' => isset($maint_item_data['minterval']) ? $maint_item_data['minterval'] : '1', + ], + 'stime' => [ + 'friendly_name' => __('Start Time', 'maint'), + 'method' => 'textbox', + 'max_length' => 22, + 'size' => 22, + 'description' => __('The start date / time for this schedule. Most date / time formats accepted.', 'maint'), + 'default' => date('Y-m-d H:i', time()), + 'value' => isset($maint_item_data['stime']) ? date('Y-m-d H:i', $maint_item_data['stime']) : '', + ], + 'etime' => [ + 'friendly_name' => __('End Time', 'maint'), + 'method' => 'textbox', + 'max_length' => 22, + 'size' => 22, + 'default' => date('Y-m-d H:i', time() + 3600), + 'description' => __('The end date / time for this schedule. Most date / time formats accepted.', 'maint'), + 'value' => isset($maint_item_data['etime']) ? date('Y-m-d H:i', $maint_item_data['etime']) : '', + ], + 'save_component' => [ + 'method' => 'hidden', + 'value' => '1', + ], + 'save' => [ + 'method' => 'hidden', + 'value' => 'edit', + ], + 'id' => [ + 'method' => 'hidden', + 'value' => $id, + ], + ]; + + draw_edit_form( + [ + 'config' => [ + 'no_form_tag' => true, + ], + 'fields' => $form_array, + ], + ); + + html_end_box(); + + form_save_button('maint.php', 'return'); + + ?> " . __('No Schedules', 'maint') . ""; - } - - html_end_box(false); - - form_hidden_box('save_list', '1', ''); - - /* draw the dropdown containing a list of available actions for this form */ - draw_actions_dropdown($actions); - - form_end(); + form_start('maint.php', 'chk'); + + html_start_box(__('Maintenance Schedules', 'maint'), '100%', '', '2', 'center', 'maint.php?tab=general&action=edit'); + + html_header_checkbox( + [ + __('Name', 'maint'), + __('Active', 'maint'), + __('Type', 'maint'), + __('Start', 'maint'), + __('End', 'maint'), + __('Interval', 'maint'), + __('Enabled', 'maint')], + ); + + if (cacti_sizeof($schedules)) { + foreach ($schedules as $schedule) { + $active = plugin_maint_check_schedule($schedule['id']); + + form_alternate_row('line' . $schedule['id']); + form_selectable_cell(filter_value($schedule['name'], get_request_var('filter'), 'maint.php?action=edit&id=' . $schedule['id']), $schedule['id']); + form_selectable_cell($yesno[plugin_maint_check_schedule($schedule['id'])], $schedule['id'], '', $active ? 'deviceUp' : ''); + form_selectable_cell($maint_types[$schedule['mtype']], $schedule['id']); + + switch ($schedule['minterval']) { + case 86400: + if (date('j', $schedule['etime']) != date('j', $schedule['stime'])) { + form_selectable_cell(date(date_time_format(), $schedule['stime']), $schedule['id']); + form_selectable_cell(date(date_time_format(), $schedule['etime']), $schedule['id']); + } else { + form_selectable_cell(date('G:i', $schedule['stime']), $schedule['id']); + form_selectable_cell(date('G:i', $schedule['etime']), $schedule['id']); + } + + break; + case 604800: + form_selectable_cell(date('l G:i', $schedule['stime']), $schedule['id']); + form_selectable_cell(date('l G:i', $schedule['etime']), $schedule['id']); + + break; + default: + form_selectable_cell(date(date_time_format(), $schedule['stime']), $schedule['id']); + form_selectable_cell(date(date_time_format(), $schedule['etime']), $schedule['id']); + } + + form_selectable_cell($maint_intervals[$schedule['minterval']], $schedule['id']); + form_selectable_cell($yesno[$schedule['enabled']], $schedule['id']); + form_checkbox_cell($schedule['name'], $schedule['id']); + form_end_row(); + } + } else { + print "" . __('No Schedules', 'maint') . ''; + } + + html_end_box(false); + + form_hidden_box('save_list', '1', ''); + + // draw the dropdown containing a list of available actions for this form + draw_actions_dropdown($actions); + + form_end(); } -function thold_hosts($header_label) -{ - global $assoc_actions, $item_rows; - - $schedule_created = get_request_var('id') ? true : false; - - /* ================= input validation and session storage ================= */ - get_filter_request_var('id'); - - $filters = [ - 'rows' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => MAINT_HOST_FILTER_ANY, - ], - 'page' => [ - 'filter' => FILTER_VALIDATE_INT, - 'default' => '1', - ], - 'site_id' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => MAINT_HOST_FILTER_ANY, - ], - 'poller_id' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => MAINT_HOST_FILTER_ANY, - ], - 'host_template_id' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => MAINT_HOST_FILTER_ANY, - ], - 'location' => [ - 'filter' => FILTER_CALLBACK, - 'pageset' => true, - 'default' => MAINT_HOST_FILTER_LOC_ANY, - 'options' => ['options' => 'sanitize_search_string'], - ], - 'filter' => [ - 'filter' => FILTER_CALLBACK, - 'pageset' => true, - 'default' => '', - 'options' => ['options' => 'sanitize_search_string'], - ], - 'sort_column' => [ - 'filter' => FILTER_CALLBACK, - 'default' => 'description', - 'options' => ['options' => 'sanitize_search_string'], - ], - 'sort_direction' => [ - 'filter' => FILTER_CALLBACK, - 'default' => 'ASC', - 'options' => ['options' => 'sanitize_search_string'], - ], - 'associated' => [ - 'filter' => FILTER_CALLBACK, - 'default' => 'true', - 'options' => ['options' => 'sanitize_search_string'], - ], - ]; - - validate_store_request_vars($filters, 'sess_maint'); - - /* ================= input validation ================= */ - - /* if the number of rows is -1, set it to the default */ - if (get_request_var('rows') == '-1') { - $rows = read_config_option('num_rows_table'); - } else { - $rows = get_request_var('rows'); - } - - /* Limit filter selection lists based on higher order items. For example, list only templates used in the selected location */ - $sql_where = ''; - $sql_where_params = []; - - ?> +/** + * Display and manage Thold device associations + * + * Shows a filterable table of Cacti devices with options to: + * - Filter by site, poller, template, location, status + * - Associate/disassociate devices with the current schedule + * - View current associations + * + * @param string $header_label Header label for the current schedule + * + * @return void + */ +function thold_hosts(string $header_label): void { + global $assoc_actions, $item_rows; + + $schedule_created = get_request_var('id') ? true : false; + + // ================= input validation and session storage ================= + get_filter_request_var('id'); + + $filters = [ + 'rows' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => MAINT_HOST_FILTER_ANY, + ], + 'page' => [ + 'filter' => FILTER_VALIDATE_INT, + 'default' => '1', + ], + 'site_id' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => MAINT_HOST_FILTER_ANY, + ], + 'poller_id' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => MAINT_HOST_FILTER_ANY, + ], + 'host_template_id' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => MAINT_HOST_FILTER_ANY, + ], + 'location' => [ + 'filter' => FILTER_CALLBACK, + 'pageset' => true, + 'default' => MAINT_HOST_FILTER_LOC_ANY, + 'options' => ['options' => 'sanitize_search_string'], + ], + 'filter' => [ + 'filter' => FILTER_CALLBACK, + 'pageset' => true, + 'default' => '', + 'options' => ['options' => 'sanitize_search_string'], + ], + 'sort_column' => [ + 'filter' => FILTER_CALLBACK, + 'default' => 'description', + 'options' => ['options' => 'sanitize_search_string'], + ], + 'sort_direction' => [ + 'filter' => FILTER_CALLBACK, + 'default' => 'ASC', + 'options' => ['options' => 'sanitize_search_string'], + ], + 'associated' => [ + 'filter' => FILTER_CALLBACK, + 'default' => 'true', + 'options' => ['options' => 'sanitize_search_string'], + ], + ]; + + validate_store_request_vars($filters, 'sess_maint'); + + // ================= input validation ================= + + // if the number of rows is -1, set it to the default + if (get_request_var('rows') == '-1') { + $rows = read_config_option('num_rows_table'); + } else { + $rows = get_request_var('rows'); + } + + // Limit filter selection lists based on higher order items. For example, list only templates used in the selected location + $sql_where = ''; + $sql_where_params = []; + + ?> + ?>
    @@ -1230,104 +1338,105 @@ function clearFilter() {
    - + - + - + - + - - + +
    - + - '> + '> - + - > + > - +
    - '> - '> + '> + '>
    '') { - $sql_where = 'WHERE' . substr($sql_where, 4); - } - - if ($schedule_created) { - $sql_params = array_merge([get_request_var('id')], $sql_where_params); - $total_rows = db_fetch_cell_prepared( - "SELECT COUNT(DISTINCT h.id) + html_end_box(); + + $sql_where = ''; + $sql_where_params = []; + + // form the 'where' clause for our main sql query + if (strlen(get_request_var('filter'))) { + $sql_where = ' AND (h.hostname LIKE ? OR h.description LIKE ?)'; + $sql_where_params = array_merge( + $sql_where_params, + ['%' . get_request_var('filter') . '%', '%' . get_request_var('filter') . '%'], + ); + } + + if (get_request_var('site_id') == MAINT_HOST_FILTER_ANY) { + // Show all items + } else { + $sql_where .= ' AND h.site_id = ?'; + $sql_where_params = array_merge($sql_where_params, [get_request_var('site_id')]); + } + + if (get_request_var('poller_id') == MAINT_HOST_FILTER_ANY) { + // Show all items + } else { + $sql_where .= ' AND h.poller_id = ?'; + $sql_where_params = array_merge($sql_where_params, [get_request_var('poller_id')]); + } + + if (get_request_var('location') != MAINT_HOST_FILTER_LOC_ANY) { + if (get_request_var('location') == MAINT_HOST_FILTER_LOC_NONE) { + $sql_where .= ' AND IFNULL(h.location,"") = ""'; + } else { + $sql_where .= ' AND h.location = ?'; + $sql_where_params = array_merge($sql_where_params, [get_request_var('location')]); + } + } + + if (get_request_var('host_template_id') == MAINT_HOST_FILTER_ANY) { + // Show all items + } elseif (get_request_var('host_template_id') == MAINT_HOST_FILTER_NONE || !isempty_request_var('host_template_id')) { + $sql_where .= ' AND h.host_template_id = ?'; + $sql_where_params = array_merge($sql_where_params, [get_request_var('host_template_id')]); + } + + if (get_request_var('associated') == 'false') { + // Show all items + } else { + $sql_where .= ' AND type = ? AND schedule = ?'; + $sql_where_params = array_merge($sql_where_params, [MAINT_HOST_TYPE_HOSTS, get_request_var('id')]); + } + + // Replace leading " AND" + if ($sql_where > '') { + $sql_where = 'WHERE' . substr($sql_where, 4); + } + + if ($schedule_created) { + $sql_params = array_merge([get_request_var('id')], $sql_where_params); + $total_rows = db_fetch_cell_prepared( + "SELECT COUNT(DISTINCT h.id) FROM host AS h LEFT JOIN (SELECT DISTINCT host_id FROM thold_data) AS td ON h.id = td.host_id @@ -1335,17 +1444,17 @@ function clearFilter() { ON h.id = pmh.host AND pmh.schedule = ? $sql_where", - $sql_params, - ); - } else { - $total_rows = 0; - } + $sql_params, + ); + } else { + $total_rows = 0; + } - $sql_order = get_order_string(); - $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ', ' . $rows; + $sql_order = get_order_string(); + $sql_limit = ' LIMIT ' . ($rows * (get_request_var('page') - 1)) . ', ' . $rows; - if ($schedule_created) { - $sql_query = "SELECT h.*, pmh.type, gl.graphs, dl.data_sources, tholds, + if ($schedule_created) { + $sql_query = "SELECT h.*, pmh.type, gl.graphs, dl.data_sources, tholds, (SELECT schedule FROM plugin_maint_hosts WHERE host=h.id AND schedule=?) AS associated FROM host as h LEFT JOIN (SELECT COUNT(id) AS tholds, host_id FROM thold_data GROUP BY host_id) AS td @@ -1362,214 +1471,222 @@ function clearFilter() { $sql_order $sql_limit"; - $sql_params = array_merge([get_request_var('id'), get_request_var('id')], $sql_where_params); - $hosts = db_fetch_assoc_prepared($sql_query, $sql_params); - } else { - $hosts = []; - } - - $display_text = [ - 'description' => [ - 'display' => __('Description', 'maint'), - 'align' => 'left', - 'sort' => 'ASC'], - 'id' => [ - 'display' => __('ID', 'maint'), - 'align' => 'right', - 'sort' => 'asc'], - 'nosort' => [ - 'display' => __('Associated Schedules', 'maint'), - 'align' => 'left', - 'sort' => ''], - 'graphs' => [ - 'display' => __('Graphs', 'maint'), - 'align' => 'right', - 'sort' => 'desc'], - 'data_sources' => [ - 'display' => __('Data Sources', 'maint'), - 'align' => 'right', - 'sort' => 'desc'], - 'tholds' => [ - 'display' => __('Thresholds', 'maint'), - 'align' => 'right', - 'sort' => 'desc'], - 'nosort1' => [ - 'display' => __('Status', 'maint'), - 'align' => 'center', - 'sort' => ''], - 'hostname' => [ - 'display' => __('Hostname', 'maint'), - 'align' => 'left', - 'sort' => 'desc'], - ]; - - /* generate page list */ - $nav = html_nav_bar('maint.php?action=edit&tab=hosts&id=' . get_request_var('id'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 13, __('Devices', 'maint'), 'page', 'main'); - - form_start('maint.php', 'chk'); - - print $nav; - - html_start_box('', '100%', '', '3', 'center', ''); - - html_header_sort_checkbox($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false, 'maint.php?action=edit&tab=hosts&id=' . get_request_var('id')); - - if (cacti_sizeof($hosts)) { - foreach ($hosts as $host) { - form_alternate_row('line' . $host['id']); - form_selectable_cell(filter_value($host['description'], get_request_var('filter')), $host['id']); - form_selectable_cell(number_format_i18n($host['id']), $host['id'], '', 'text-align:right'); - - if ($host['associated'] != '') { - $names = '' . __('Current Schedule', 'maint') . ''; - } else { - $names = ''; - } - - $lists = db_fetch_assoc_prepared( - 'SELECT name + $sql_params = array_merge([get_request_var('id'), get_request_var('id')], $sql_where_params); + $hosts = db_fetch_assoc_prepared($sql_query, $sql_params); + } else { + $hosts = []; + } + + $display_text = [ + 'description' => [ + 'display' => __('Description', 'maint'), + 'align' => 'left', + 'sort' => 'ASC'], + 'id' => [ + 'display' => __('ID', 'maint'), + 'align' => 'right', + 'sort' => 'asc'], + 'nosort' => [ + 'display' => __('Associated Schedules', 'maint'), + 'align' => 'left', + 'sort' => ''], + 'graphs' => [ + 'display' => __('Graphs', 'maint'), + 'align' => 'right', + 'sort' => 'desc'], + 'data_sources' => [ + 'display' => __('Data Sources', 'maint'), + 'align' => 'right', + 'sort' => 'desc'], + 'tholds' => [ + 'display' => __('Thresholds', 'maint'), + 'align' => 'right', + 'sort' => 'desc'], + 'nosort1' => [ + 'display' => __('Status', 'maint'), + 'align' => 'center', + 'sort' => ''], + 'hostname' => [ + 'display' => __('Hostname', 'maint'), + 'align' => 'left', + 'sort' => 'desc'], + ]; + + // generate page list + $nav = html_nav_bar('maint.php?action=edit&tab=hosts&id=' . get_request_var('id'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, 13, __('Devices', 'maint'), 'page', 'main'); + + form_start('maint.php', 'chk'); + + print $nav; + + html_start_box('', '100%', '', '3', 'center', ''); + + html_header_sort_checkbox($display_text, get_request_var('sort_column'), get_request_var('sort_direction'), false, 'maint.php?action=edit&tab=hosts&id=' . get_request_var('id')); + + if (cacti_sizeof($hosts)) { + foreach ($hosts as $host) { + form_alternate_row('line' . $host['id']); + form_selectable_cell(filter_value($host['description'], get_request_var('filter')), $host['id']); + form_selectable_cell(number_format_i18n($host['id']), $host['id'], '', 'text-align:right'); + + if ($host['associated'] != '') { + $names = '' . __('Current Schedule', 'maint') . ''; + } else { + $names = ''; + } + + $lists = db_fetch_assoc_prepared( + 'SELECT name FROM plugin_maint_schedules INNER JOIN plugin_maint_hosts ON plugin_maint_schedules.id = plugin_maint_hosts.schedule WHERE type = ? AND host = ? AND plugin_maint_schedules.id != ?', - [MAINT_HOST_TYPE_HOSTS, $host['id'], get_request_var('id')], - ); - - if (cacti_sizeof($lists)) { - foreach ($lists as $name) { - $names .= (strlen($names) ? ', ' : '') . "" . html_escape($name['name']) . ''; - } - } - if ($names == '') { - form_selectable_cell('' . __('No Schedules', 'maint') . '', $host['id']); - } else { - form_selectable_cell($names, $host['id']); - } - form_selectable_cell(number_format_i18n(is_null($host['graphs']) ? 0 : $host['graphs']), $host['id'], '', 'text-align:right'); - form_selectable_cell(number_format_i18n(is_null($host['data_sources']) ? 0 : $host['data_sources']), $host['id'], '', 'text-align:right'); - form_selectable_cell(number_format_i18n(is_null($host['tholds']) ? 0 : $host['tholds']), $host['id'], '', 'text-align:right'); - form_selectable_cell(get_colored_device_status(($host['disabled'] == 'on' ? true : false), $host['status']), $host['id'], '', 'text-align:center'); - form_selectable_cell(filter_value($host['hostname'], get_request_var('filter')), $host['id']); - form_checkbox_cell($host['description'], $host['id']); - form_end_row(); - } - } else { - if ($schedule_created) { - print "" . __('No Associated Devices Found', 'maint') . ""; - } else { - print "" . __('Schedule must be created before associating', 'maint') . ""; - } - } - - html_end_box(false); - - if (cacti_sizeof($hosts)) { - print $nav; - } - - form_hidden_box('id', get_request_var('id'), ''); - form_hidden_box('save_hosts', '1', ''); - - /* draw the dropdown containing a list of available actions for this form */ - draw_actions_dropdown($assoc_actions); - - form_end(); + [MAINT_HOST_TYPE_HOSTS, $host['id'], get_request_var('id')], + ); + + if (cacti_sizeof($lists)) { + foreach ($lists as $name) { + $names .= (strlen($names) ? ', ' : '') . "" . html_escape($name['name']) . ''; + } + } + + if ($names == '') { + form_selectable_cell('' . __('No Schedules', 'maint') . '', $host['id']); + } else { + form_selectable_cell($names, $host['id']); + } + form_selectable_cell(number_format_i18n(is_null($host['graphs']) ? 0 : $host['graphs']), $host['id'], '', 'text-align:right'); + form_selectable_cell(number_format_i18n(is_null($host['data_sources']) ? 0 : $host['data_sources']), $host['id'], '', 'text-align:right'); + form_selectable_cell(number_format_i18n(is_null($host['tholds']) ? 0 : $host['tholds']), $host['id'], '', 'text-align:right'); + form_selectable_cell(get_colored_device_status(($host['disabled'] == 'on' ? true : false), $host['status']), $host['id'], '', 'text-align:center'); + form_selectable_cell(filter_value($host['hostname'], get_request_var('filter')), $host['id']); + form_checkbox_cell($host['description'], $host['id']); + form_end_row(); + } + } else { + if ($schedule_created) { + print "" . __('No Associated Devices Found', 'maint') . ''; + } else { + print "" . __('Schedule must be created before associating', 'maint') . ''; + } + } + + html_end_box(false); + + if (cacti_sizeof($hosts)) { + print $nav; + } + + form_hidden_box('id', get_request_var('id'), ''); + form_hidden_box('save_hosts', '1', ''); + + // draw the dropdown containing a list of available actions for this form + draw_actions_dropdown($assoc_actions); + + form_end(); } /** - * webseer tab + * Display and manage WebSeer URL associations + * + * Shows a filterable table of WebSeer URLs with options to: + * - Filter by search term + * - Toggle between associated/all URLs + * - Associate/disassociate URLs with the current schedule + * + * @param string $header_label Header label for the current schedule + * + * @return void */ +function webseer_urls(string $header_label): void { + global $assoc_actions, $item_rows; + + $schedule_created = get_request_var('id') ? true : false; + + // ================= input validation and session storage ================= + $filters = [ + 'rows' => [ + 'filter' => FILTER_VALIDATE_INT, + 'pageset' => true, + 'default' => '-1', + ], + 'page' => [ + 'filter' => FILTER_VALIDATE_INT, + 'default' => '1', + ], + 'filter' => [ + 'filter' => FILTER_CALLBACK, + 'pageset' => true, + 'default' => '', + 'options' => ['options' => 'sanitize_search_string'], + ], + 'associated' => [ + 'filter' => FILTER_CALLBACK, + 'default' => 'true', + 'options' => ['options' => 'sanitize_search_string'], + ], + ]; + + validate_store_request_vars($filters, 'sess_maint_ws'); + + // ================= input validation ================= + + // if the number of rows is -1, set it to the default + if (get_request_var('rows') == '-1') { + $rows = read_config_option('num_rows_table'); + } else { + $rows = get_request_var('rows'); + } + + html_start_box(__esc("Associated Web URL's %s", $header_label, 'maint'), '100%', '', '3', 'center', ''); -function webseer_urls($header_label) -{ - global $assoc_actions, $item_rows; - - $schedule_created = get_request_var('id') ? true : false; - - /* ================= input validation and session storage ================= */ - $filters = [ - 'rows' => [ - 'filter' => FILTER_VALIDATE_INT, - 'pageset' => true, - 'default' => '-1', - ], - 'page' => [ - 'filter' => FILTER_VALIDATE_INT, - 'default' => '1', - ], - 'filter' => [ - 'filter' => FILTER_CALLBACK, - 'pageset' => true, - 'default' => '', - 'options' => ['options' => 'sanitize_search_string'], - ], - 'associated' => [ - 'filter' => FILTER_CALLBACK, - 'default' => 'true', - 'options' => ['options' => 'sanitize_search_string'], - ], - ]; - - validate_store_request_vars($filters, 'sess_maint_ws'); - - /* ================= input validation ================= */ - - /* if the number of rows is -1, set it to the default */ - if (get_request_var('rows') == '-1') { - $rows = read_config_option('num_rows_table'); - } else { - $rows = get_request_var('rows'); - } - - html_start_box(__esc("Associated Web URL's %s", $header_label, 'maint'), '100%', '', '3', 'center', ''); - - ?> + ?>
    - + - '> + '> - + - > + > - + - - + +
    - '> - '> + '> + '>
    + ?>
    - + - '> + '> - + - > + > - + - - + +
    - '> - '> + '> + '>
    ' . __('Current Schedule', 'maint') . ''; - } else { - $names = ''; - } + if ($test['associated'] != '') { + $names = '' . __('Current Schedule', 'maint') . ''; + } else { + $names = ''; + } - $lists = db_fetch_assoc_prepared( - "SELECT name + $lists = db_fetch_assoc_prepared( + 'SELECT name FROM plugin_maint_schedules INNER JOIN plugin_maint_hosts ON plugin_maint_schedules.id = plugin_maint_hosts.schedule WHERE type = ? AND host = ? - AND plugin_maint_schedules.id != ?", - [MAINT_HOST_TYPE_SERVCHECK, $test['id'], get_request_var('id')], - ); - - if (cacti_sizeof($lists)) { - foreach ($lists as $name) { - $names .= (strlen($names) ? ', ' : '') . "" . html_escape($name['name']) . ""; - } - } - if ($names == '') { - form_selectable_cell('' . __('No Schedules', 'maint') . '', $test['id']); - } else { - form_selectable_cell($names, $test['id']); - } - - form_selectable_cell(($test['enabled'] == 'on' ? __('Enabled', 'maint') : __('Disabled', 'maint')), $test['id']); - form_selectable_cell(filter_value($test['hostname'], get_request_var('filter')), $test['id']); - form_selectable_cell(filter_value($test['hostname'], get_request_var('filter')), $test['id']); - form_checkbox_cell($test['display_name'], $test['id']); - form_end_row(); - } - } else { - if ($schedule_created) { - print "" . __('No Associated Servcheck Test\'s Found', 'maint') . ""; - } else { - print "" . __('Schedule must be created before associating', 'maint') . ""; - } - } - - html_end_box(false); - - if (cacti_sizeof($tests)) { - print $nav; - } - - form_hidden_box('id', get_request_var('id'), ''); - form_hidden_box('save_servcheck', '1', ''); - - /* draw the dropdown containing a list of available actions for this form */ - draw_actions_dropdown($assoc_actions); - - form_end(); + AND plugin_maint_schedules.id != ?', + [MAINT_HOST_TYPE_SERVCHECK, $test['id'], get_request_var('id')], + ); + + if (cacti_sizeof($lists)) { + foreach ($lists as $name) { + $names .= (strlen($names) ? ', ' : '') . "" . html_escape($name['name']) . ''; + } + } + + if ($names == '') { + form_selectable_cell('' . __('No Schedules', 'maint') . '', $test['id']); + } else { + form_selectable_cell($names, $test['id']); + } + + form_selectable_cell(($test['enabled'] == 'on' ? __('Enabled', 'maint') : __('Disabled', 'maint')), $test['id']); + form_selectable_cell(filter_value($test['hostname'], get_request_var('filter')), $test['id']); + form_selectable_cell(filter_value($test['hostname'], get_request_var('filter')), $test['id']); + form_checkbox_cell($test['display_name'], $test['id']); + form_end_row(); + } + } else { + if ($schedule_created) { + print "" . __('No Associated Servcheck Test\'s Found', 'maint') . ''; + } else { + print "" . __('Schedule must be created before associating', 'maint') . ''; + } + } + + html_end_box(false); + + if (cacti_sizeof($tests)) { + print $nav; + } + + form_hidden_box('id', get_request_var('id'), ''); + form_hidden_box('save_servcheck', '1', ''); + + // draw the dropdown containing a list of available actions for this form + draw_actions_dropdown($assoc_actions); + + form_end(); } diff --git a/setup.php b/setup.php index 4b66894..8b495a9 100644 --- a/setup.php +++ b/setup.php @@ -24,355 +24,446 @@ +-------------------------------------------------------------------------+ */ -function plugin_maint_version() -{ - global $config; +/** + * Get the plugin version information from INFO file + * + * @return array Plugin metadata array containing version, author, homepage, etc. + */ +function plugin_maint_version(): array { + global $config; - $info = parse_ini_file($config['base_path'] . '/plugins/maint/INFO', true); + $info = parse_ini_file($config['base_path'] . '/plugins/maint/INFO', true); - return $info['info']; + return $info['info']; } -function plugin_maint_install() -{ - api_plugin_register_hook('maint', 'config_arrays', 'maint_config_arrays', 'setup.php'); - api_plugin_register_hook('maint', 'draw_navigation_text', 'maint_draw_navigation_text', 'setup.php'); - api_plugin_register_hook('maint', 'device_edit_top_links', 'maint_device_edit_top_links', 'setup.php'); - api_plugin_register_hook('maint', 'is_device_in_maintenance', 'plugin_maint_check_cacti_host', 'functions.php'); - api_plugin_register_hook('maint', 'device_action_array', 'maint_device_action_array', 'setup.php'); - api_plugin_register_hook('maint', 'device_action_prepare', 'maint_device_action_prepare', 'setup.php'); - api_plugin_register_hook('maint', 'device_action_execute', 'maint_device_action_execute', 'setup.php'); - api_plugin_register_realm('maint', 'maint.php', 'Maintenance Schedules', 1); - - maint_setup_database(); +/** + * Install the maintenance plugin + * + * Registers all hooks, realms, and creates database tables. + * + * @return void + */ +function plugin_maint_install(): void { + api_plugin_register_hook('maint', 'config_arrays', 'maint_config_arrays', 'setup.php'); + api_plugin_register_hook('maint', 'draw_navigation_text', 'maint_draw_navigation_text', 'setup.php'); + api_plugin_register_hook('maint', 'device_edit_top_links', 'maint_device_edit_top_links', 'setup.php'); + api_plugin_register_hook('maint', 'is_device_in_maintenance', 'plugin_maint_check_cacti_host', 'functions.php'); + api_plugin_register_hook('maint', 'device_action_array', 'maint_device_action_array', 'setup.php'); + api_plugin_register_hook('maint', 'device_action_prepare', 'maint_device_action_prepare', 'setup.php'); + api_plugin_register_hook('maint', 'device_action_execute', 'maint_device_action_execute', 'setup.php'); + api_plugin_register_realm('maint', 'maint.php', 'Maintenance Schedules', 1); + + maint_setup_database(); } -function plugin_maint_uninstall() {} - -function plugin_maint_check_config() -{ - return true; +/** + * Uninstall the maintenance plugin + * + * @return void + */ +function plugin_maint_uninstall(): void { } -function plugin_maint_upgrade() -{ - return false; +/** + * Check plugin configuration + * + * @return bool Always returns true + */ +function plugin_maint_check_config(): bool { + return true; } -function maint_config_arrays() -{ - global $menu; - - $menu[__('Management')]['plugins/maint/maint.php'] = __('Maintenance Schedules', 'maint'); - - if (function_exists('auth_augment_roles')) { - auth_augment_roles(__('System Administration'), ['maint.php']); - } +/** + * Upgrade the maintenance plugin + * + * @return bool Always returns false (no upgrade needed) + */ +function plugin_maint_upgrade(): bool { + return false; } -function maint_draw_navigation_text($nav) -{ - $nav['maint.php:'] = ['title' => __('Maintenance Schedules', 'maint'), 'mapping' => 'index.php:', 'url' => 'maint.php', 'level' => '1']; - $nav['maint.php:edit'] = ['title' => __('(edit)', 'maint'), 'mapping' => 'index.php:', 'url' => 'maint.php', 'level' => '2']; - $nav['maint.php:actions'] = ['title' => __('(actions)', 'maint'), 'mapping' => 'index.php:', 'url' => 'maint.php', 'level' => '2']; +/** + * Configure plugin menu arrays and augment roles + * + * Adds the maintenance schedules menu item to the Management menu + * and augments System Administration roles if the function exists. + * + * @return void + */ +function maint_config_arrays(): void { + global $menu; + + $menu[__('Management')]['plugins/maint/maint.php'] = __('Maintenance Schedules', 'maint'); + + if (function_exists('auth_augment_roles')) { + auth_augment_roles(__('System Administration'), ['maint.php']); + } +} - return $nav; +/** + * Configure navigation breadcrumb text for maintenance pages + * + * @param array> $nav Navigation array + * + * @return array> Modified navigation array + */ +function maint_draw_navigation_text(array $nav): array { + $nav['maint.php:'] = ['title' => __('Maintenance Schedules', 'maint'), 'mapping' => 'index.php:', 'url' => 'maint.php', 'level' => '1']; + $nav['maint.php:edit'] = ['title' => __('(edit)', 'maint'), 'mapping' => 'index.php:', 'url' => 'maint.php', 'level' => '2']; + $nav['maint.php:actions'] = ['title' => __('(actions)', 'maint'), 'mapping' => 'index.php:', 'url' => 'maint.php', 'level' => '2']; + + return $nav; } // Centralized action labels to avoid duplication if (!defined('MAINT_LABEL_ENABLE_NOW')) { - define('MAINT_LABEL_ENABLE_NOW', __('Enable maintenance for this device (now+1h)', 'maint')); + define('MAINT_LABEL_ENABLE_NOW', __('Enable maintenance for this device (now+1h)', 'maint')); } + if (!defined('MAINT_LABEL_ADD_TO_SCHEDULE')) { - define('MAINT_LABEL_ADD_TO_SCHEDULE', __('Add device(s) to existing maintenance schedule', 'maint')); + define('MAINT_LABEL_ADD_TO_SCHEDULE', __('Add device(s) to existing maintenance schedule', 'maint')); } -function maint_device_edit_top_links() -{ - global $config; - - // Get current device id from edit context - $host_id = get_filter_request_var('id'); - if (empty($host_id)) { - return; - } - - $action_url = $config['url_path'] . 'host.php'; - $uid = 'maint_' . (int) $host_id . '_' . mt_rand(1000, 9999); - - // Hidden form: Enable maintenance (now+1h) using same flow as device list dropdown - print ""; - - // Hidden form: Add device to existing schedule - print ""; - - // Links that submit the forms above, reusing the exact same confirmation UI - print "
    *" . MAINT_LABEL_ENABLE_NOW . "
    "; - print "*" . MAINT_LABEL_ADD_TO_SCHEDULE . ""; +/** + * Add maintenance action links to device edit page + * + * Displays two hidden forms with links: + * 1. Enable maintenance now (now + 1 hour) + * 2. Add device to existing schedule + * + * @return void + */ +function maint_device_edit_top_links(): void { + global $config; + + // Get current device id from edit context + $host_id = get_filter_request_var('id'); + + if (empty($host_id)) { + return; + } + + $action_url = $config['url_path'] . 'host.php'; + $uid = 'maint_' . (int) $host_id . '_' . mt_rand(1000, 9999); + + // Hidden form: Enable maintenance (now+1h) using same flow as device list dropdown + print "'; + + // Hidden form: Add device to existing schedule + print "'; + + // Links that submit the forms above, reusing the exact same confirmation UI + print "
    *" . MAINT_LABEL_ENABLE_NOW . '
    '; + print "*" . MAINT_LABEL_ADD_TO_SCHEDULE . ''; } -function maint_device_action_array($actions) -{ - $actions['maint'] = MAINT_LABEL_ENABLE_NOW; - $actions['maint_add_to_schedule'] = MAINT_LABEL_ADD_TO_SCHEDULE; - - return $actions; +/** + * Add maintenance actions to device action dropdown + * + * @param array $actions Existing device actions + * + * @return array Modified actions array with maintenance options + */ +function maint_device_action_array(array $actions): array { + $actions['maint'] = MAINT_LABEL_ENABLE_NOW; + $actions['maint_add_to_schedule'] = MAINT_LABEL_ADD_TO_SCHEDULE; + + return $actions; } -function maint_device_action_prepare($save) -{ - // Render confirmation details for the maint action - if (isset($save['drp_action']) && $save['drp_action'] == 'maint') { - $now = time(); - $one_hour_later = $now + 3600; - - // Human readable time window - $time_window = date(date_time_format(), $now) . ' - ' . date(date_time_format(), $one_hour_later); - - // Build device list - $host_list = ''; - if (!empty($save['host_array']) && is_array($save['host_array'])) { - foreach ($save['host_array'] as $host_id) { - $row = db_fetch_row_prepared('SELECT description FROM host WHERE id = ?', [(int) $host_id]); - - if (!empty($row)) { - $host_list .= '
  • ' . html_escape($row['description']) . '
  • '; - } - } - } - - // Output a styled summary box with the window and devices - $tz = function_exists('date_default_timezone_get') ? date_default_timezone_get() : ''; - $duration_min = round(($one_hour_later - $now) / 60); - - $summary = "
    "; - $summary .= "
    "; - $summary .= " " . __('Maintenance Window', 'maint') . "(now + " . intval($duration_min) . " min): "; - $summary .= "" . html_escape($time_window) . ""; - $summary .= "
    "; - if (!empty($tz)) { - $summary .= "
    " . __('Timezone', 'maint') . ": " . html_escape($tz) . " • " . __('Duration', 'maint') . ": " . intval($duration_min) . " " . __('min', 'maint') . "
    "; - } - $summary .= "
    "; - - // Maintenance window full-width box - print "" . $summary . ""; - - // Aligned Schedule Name full-width row - $label_name = __('Schedule name', 'maint'); - $ph_name = __esc('e.g. Emergency patching', 'maint'); - - print "" - . "
    " - . "
    " - . "
    " . html_escape($label_name) . ":
    " - . "" - . "
    " - . "
    " - . ""; - - // Aligned Schedule Type full-width row - $one_time_label = __('One Time', 'maint'); - $recurring_label = __('Recurring', 'maint'); - - $every_day = __('Every Day', 'maint'); - $every_week = __('Every Week', 'maint'); - $label_type = __('Schedule Type', 'maint'); - - $row = "
    "; - $row .= "
    "; - $row .= "
    " . html_escape($label_type) . ":
    "; - - $row .= ""; - - $row .= ""; - - $row .= "
    "; - $row .= "
    "; - - print "" . $row . ""; - - // Initialize visibility on load - print ""; - - $devices = "
    "; - $devices .= "" . __('Devices', 'maint') . " (" . count((array) $save['host_array']) . ")"; - $devices .= "
      " . $host_list . "
    "; - $devices .= "
    "; - - print "" . $devices . ""; - } elseif (isset($save['drp_action']) && $save['drp_action'] == 'maint_add_to_schedule') { - // Build device list - $host_list = ''; - - if (!empty($save['host_array']) && is_array($save['host_array'])) { - foreach ($save['host_array'] as $host_id) { - $row = db_fetch_row_prepared('SELECT description FROM host WHERE id = ?', [(int) $host_id]); - - if (!empty($row)) { - $host_list .= '
  • ' . html_escape($row['description']) . '
  • '; - } - } - } - - // Load schedules to choose from - $schedules = db_fetch_assoc('SELECT id, name, enabled, mtype, stime, etime, minterval +/** + * Prepare device action confirmation UI + * + * Renders the confirmation dialog for maintenance actions: + * - Quick maintenance (now + 1 hour) + * - Add devices to existing schedule + * + * @param array $save Action data including drp_action and host_array + * + * @return array Modified save array + */ +function maint_device_action_prepare(array $save): array { + // Render confirmation details for the maint action + if (isset($save['drp_action']) && $save['drp_action'] == 'maint') { + $now = time(); + $one_hour_later = $now + 3600; + + // Human readable time window + $time_window = date(date_time_format(), $now) . ' - ' . date(date_time_format(), $one_hour_later); + + // Build device list + $host_list = ''; + + if (!empty($save['host_array']) && is_array($save['host_array'])) { + foreach ($save['host_array'] as $host_id) { + $row = db_fetch_row_prepared('SELECT description FROM host WHERE id = ?', [(int) $host_id]); + + if (!empty($row)) { + $host_list .= '
  • ' . html_escape($row['description']) . '
  • '; + } + } + } + + // Output a styled summary box with the window and devices + $tz = function_exists('date_default_timezone_get') ? date_default_timezone_get() : ''; + $duration_min = round(($one_hour_later - $now) / 60); + + $summary = "
    "; + $summary .= "
    "; + $summary .= ' ' . __('Maintenance Window', 'maint') . "(now + " . intval($duration_min) . ' min): '; + $summary .= "" . html_escape($time_window) . ''; + $summary .= '
    '; + + if (!empty($tz)) { + $summary .= "
    " . __('Timezone', 'maint') . ': ' . html_escape($tz) . ' • ' . __('Duration', 'maint') . ': ' . intval($duration_min) . ' ' . __('min', 'maint') . '
    '; + } + $summary .= '
    '; + + // Maintenance window full-width box + print "" . $summary . ''; + + // Aligned Schedule Name full-width row + $label_name = __('Schedule name', 'maint'); + $ph_name = __esc('e.g. Emergency patching', 'maint'); + + print "" + . "
    " + . "
    " + . "
    " . html_escape($label_name) . ':
    ' + . "" + . '
    ' + . '
    ' + . ''; + + // Aligned Schedule Type full-width row + $one_time_label = __('One Time', 'maint'); + $recurring_label = __('Recurring', 'maint'); + + $every_day = __('Every Day', 'maint'); + $every_week = __('Every Week', 'maint'); + $label_type = __('Schedule Type', 'maint'); + + $row = "
    "; + $row .= "
    "; + $row .= "
    " . html_escape($label_type) . ':
    '; + + $row .= "'; + + $row .= "'; + + $row .= '
    '; + $row .= '
    '; + + print "" . $row . ''; + + // Initialize visibility on load + print ""; + + $devices = "
    "; + $devices .= '' . __('Devices', 'maint') . ' (' . count((array) $save['host_array']) . ')'; + $devices .= "
      " . $host_list . '
    '; + $devices .= '
    '; + + print "" . $devices . ''; + } elseif (isset($save['drp_action']) && $save['drp_action'] == 'maint_add_to_schedule') { + // Build device list + $host_list = ''; + + if (!empty($save['host_array']) && is_array($save['host_array'])) { + foreach ($save['host_array'] as $host_id) { + $row = db_fetch_row_prepared('SELECT description FROM host WHERE id = ?', [(int) $host_id]); + + if (!empty($row)) { + $host_list .= '
  • ' . html_escape($row['description']) . '
  • '; + } + } + } + + // Load schedules to choose from + $schedules = db_fetch_assoc('SELECT id, name, enabled, mtype, stime, etime, minterval FROM plugin_maint_schedules ORDER BY name'); - $select = "'; - - print "" . __('Select schedule', 'maint') . ":" . $select . ""; - print "
    " . __('Devices', 'maint') . " (" . count((array) $save['host_array']) . ")
      " . $host_list . "
    "; - } - - return $save; + $select = "'; + + print "" . __('Select schedule', 'maint') . ":" . $select . ''; + print "
    " . __('Devices', 'maint') . ' (' . count((array) $save['host_array']) . ")
      " . $host_list . '
    '; + } + + return $save; } -function maint_device_action_execute($action) -{ - if ($action == 'maint') { - // One-hour quick maintenance: create a new schedule and attach devices - $now = time(); - $one_hour_later = $now + 3600; - $name = ''; - - if (isset($_POST['maint_schedule_name'])) { - $name = trim($_POST['maint_schedule_name']); - } - - if ($name === '') { - $name = __('Quick Maintenance', 'maint'); - } - $mtype = isset($_POST['maint_mtype']) ? (int) $_POST['maint_mtype'] : 1; - $minterval = 0; - if ($mtype === 2) { - $minterval = isset($_POST['maint_minterval']) ? (int) $_POST['maint_minterval'] : 86400; - if ($minterval !== 86400 && $minterval !== 604800) { - $minterval = 86400; - } - } - - // Create a new schedule (one-time or recurring) - db_execute_prepared( - 'INSERT INTO plugin_maint_schedules +/** + * Execute device maintenance actions + * + * Handles two types of actions: + * 1. 'maint' - Creates a new schedule and associates devices + * 2. 'maint_add_to_schedule' - Adds devices to existing schedule + * + * @param string $action The action to execute + * + * @return bool True if action was handled, false otherwise + */ +function maint_device_action_execute(string $action): bool { + if ($action == 'maint') { + // One-hour quick maintenance: create a new schedule and attach devices + $now = time(); + $one_hour_later = $now + 3600; + $name = ''; + + if (isset($_POST['maint_schedule_name'])) { + $name = trim($_POST['maint_schedule_name']); + } + + if ($name === '') { + $name = __('Quick Maintenance', 'maint'); + } + $mtype = isset($_POST['maint_mtype']) ? (int) $_POST['maint_mtype'] : 1; + $minterval = 0; + + if ($mtype === 2) { + $minterval = isset($_POST['maint_minterval']) ? (int) $_POST['maint_minterval'] : 86400; + + if ($minterval !== 86400 && $minterval !== 604800) { + $minterval = 86400; + } + } + + // Create a new schedule (one-time or recurring) + db_execute_prepared( + 'INSERT INTO plugin_maint_schedules (enabled, name, mtype, stime, etime, minterval) VALUES ("on", ?, ?, ?, ?, ?)', - [$name, (int) $mtype, (int) $now, (int) $one_hour_later, (int) $minterval], - ); + [$name, (int) $mtype, (int) $now, (int) $one_hour_later, (int) $minterval], + ); - $schedule_id = db_fetch_insert_id(); + $schedule_id = db_fetch_insert_id(); - // Associate selected devices to the new schedule - $associated = 0; + // Associate selected devices to the new schedule + $associated = 0; - if ($schedule_id && isset($_POST['selected_items'])) { - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + if ($schedule_id && isset($_POST['selected_items'])) { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - if (is_array($selected_items)) { - foreach ($selected_items as $host_id) { - db_execute_prepared( - 'REPLACE INTO plugin_maint_hosts + if (is_array($selected_items)) { + foreach ($selected_items as $host_id) { + db_execute_prepared( + 'REPLACE INTO plugin_maint_hosts (type, host, schedule) VALUES (1, ?, ?)', - [(int) $host_id, (int) $schedule_id], - ); - - $associated++; - } - } - } - - // Show info message like other Cacti actions - raise_message('maint_created', __esc("Maintenance schedule '%s' created and %d device(s) associated.", $name, $associated, 'maint'), MESSAGE_LEVEL_INFO); - - return true; - } elseif ($action == 'maint_add_to_schedule') { - $schedule_id = isset($_POST['maint_schedule_id']) ? (int) $_POST['maint_schedule_id'] : 0; - if ($schedule_id <= 0 || !db_fetch_cell_prepared('SELECT id FROM plugin_maint_schedules WHERE id = ?', [$schedule_id])) { - return false; - } - - $added = 0; - if (isset($_POST['selected_items'])) { - $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); - if (is_array($selected_items)) { - foreach ($selected_items as $host_id) { - db_execute_prepared('REPLACE INTO plugin_maint_hosts (type, host, schedule) VALUES (1, ?, ?)', [(int) $host_id, $schedule_id]); - $added++; - } - } - } - - $sname = db_fetch_cell_prepared('SELECT name FROM plugin_maint_schedules WHERE id = ?', [$schedule_id]); - - if ($sname === null || $sname === '') { - $sname = '#' . $schedule_id; - } - - raise_message('maint_added', __esc("%d device(s) added to maintenance schedule '%s'.", $added, $sname, 'maint'), MESSAGE_LEVEL_INFO); - - return true; - } - - return false; + [(int) $host_id, (int) $schedule_id], + ); + + $associated++; + } + } + } + + // Show info message like other Cacti actions + raise_message('maint_created', __esc("Maintenance schedule '%s' created and %d device(s) associated.", $name, $associated, 'maint'), MESSAGE_LEVEL_INFO); + + return true; + } + + if ($action == 'maint_add_to_schedule') { + $schedule_id = isset($_POST['maint_schedule_id']) ? (int) $_POST['maint_schedule_id'] : 0; + + if ($schedule_id <= 0 || !db_fetch_cell_prepared('SELECT id FROM plugin_maint_schedules WHERE id = ?', [$schedule_id])) { + return false; + } + + $added = 0; + + if (isset($_POST['selected_items'])) { + $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); + + if (is_array($selected_items)) { + foreach ($selected_items as $host_id) { + db_execute_prepared('REPLACE INTO plugin_maint_hosts (type, host, schedule) VALUES (1, ?, ?)', [(int) $host_id, $schedule_id]); + $added++; + } + } + } + + $sname = db_fetch_cell_prepared('SELECT name FROM plugin_maint_schedules WHERE id = ?', [$schedule_id]); + + if ($sname === null || $sname === '') { + $sname = '#' . $schedule_id; + } + + raise_message('maint_added', __esc("%d device(s) added to maintenance schedule '%s'.", $added, $sname, 'maint'), MESSAGE_LEVEL_INFO); + + return true; + } + + return false; } -function maint_setup_database() -{ - $data = []; - $data['columns'][] = ['name' => 'id', 'type' => 'int(11)', 'NULL' => false, 'auto_increment' => true]; - $data['columns'][] = ['name' => 'enabled', 'type' => 'varchar(3)', 'NULL' => false, 'default' => 'on']; - $data['columns'][] = ['name' => 'name', 'type' => 'varchar(128)', 'NULL' => true]; - $data['columns'][] = ['name' => 'mtype', 'type' => 'int(11)', 'NULL' => false]; - $data['columns'][] = ['name' => 'stime', 'type' => 'int(22)', 'NULL' => false]; - $data['columns'][] = ['name' => 'etime', 'type' => 'int(22)', 'NULL' => false]; - $data['columns'][] = ['name' => 'minterval', 'type' => 'int(11)', 'NULL' => false]; - $data['primary'] = 'id'; - $data['keys'][] = ['name' => 'mtype', 'columns' => 'mtype']; - $data['keys'][] = ['name' => 'enabled', 'columns' => 'enabled']; - $data['type'] = 'InnoDB'; - $data['comment'] = 'Maintenance Schedules'; - - api_plugin_db_table_create('maint', 'plugin_maint_schedules', $data); - - $data = []; - $data['columns'][] = ['name' => 'type', 'type' => 'int(6)', 'NULL' => false]; - $data['columns'][] = ['name' => 'host', 'type' => 'int(12)', 'NULL' => false]; - $data['columns'][] = ['name' => 'schedule', 'type' => 'int(12)', 'NULL' => false]; - $data['primary'] = 'type`,`schedule`,`host'; - $data['keys'][] = ['name' => 'type', 'columns' => 'type']; - $data['keys'][] = ['name' => 'schedule', 'columns' => 'schedule']; - $data['type'] = 'InnoDB'; - $data['comment'] = 'Maintenance Schedules Hosts'; - - api_plugin_db_table_create('maint', 'plugin_maint_hosts', $data); +/** + * Setup database tables for maintenance plugin + * + * Creates two tables: + * - plugin_maint_schedules: Stores maintenance schedules + * - plugin_maint_hosts: Associates hosts with schedules + * + * @return void + */ +function maint_setup_database(): void { + $data = []; + $data['columns'][] = ['name' => 'id', 'type' => 'int(11)', 'NULL' => false, 'auto_increment' => true]; + $data['columns'][] = ['name' => 'enabled', 'type' => 'varchar(3)', 'NULL' => false, 'default' => 'on']; + $data['columns'][] = ['name' => 'name', 'type' => 'varchar(128)', 'NULL' => true]; + $data['columns'][] = ['name' => 'mtype', 'type' => 'int(11)', 'NULL' => false]; + $data['columns'][] = ['name' => 'stime', 'type' => 'int(22)', 'NULL' => false]; + $data['columns'][] = ['name' => 'etime', 'type' => 'int(22)', 'NULL' => false]; + $data['columns'][] = ['name' => 'minterval', 'type' => 'int(11)', 'NULL' => false]; + $data['primary'] = 'id'; + $data['keys'][] = ['name' => 'mtype', 'columns' => 'mtype']; + $data['keys'][] = ['name' => 'enabled', 'columns' => 'enabled']; + $data['type'] = 'InnoDB'; + $data['comment'] = 'Maintenance Schedules'; + + api_plugin_db_table_create('maint', 'plugin_maint_schedules', $data); + + $data = []; + $data['columns'][] = ['name' => 'type', 'type' => 'int(6)', 'NULL' => false]; + $data['columns'][] = ['name' => 'host', 'type' => 'int(12)', 'NULL' => false]; + $data['columns'][] = ['name' => 'schedule', 'type' => 'int(12)', 'NULL' => false]; + $data['primary'] = 'type`,`schedule`,`host'; + $data['keys'][] = ['name' => 'type', 'columns' => 'type']; + $data['keys'][] = ['name' => 'schedule', 'columns' => 'schedule']; + $data['type'] = 'InnoDB'; + $data['comment'] = 'Maintenance Schedules Hosts'; + + api_plugin_db_table_create('maint', 'plugin_maint_hosts', $data); }