From ee9ea30fd394f8d2a7ae78173cb7bd82089ebdbb Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 13:58:43 -0700 Subject: [PATCH 1/7] refactor: add strict typing and clean up standalone infra --- .omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json | 8 ++++++++ .omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json | 8 ++++++++ functions.php | 2 ++ gexport.php | 2 ++ locales/LC_MESSAGES/index.php | 2 ++ locales/index.php | 2 ++ locales/po/index.php | 2 ++ setup.php | 2 ++ 8 files changed, 28 insertions(+) create mode 100644 .omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json create mode 100644 .omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json diff --git a/.omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json b/.omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json new file mode 100644 index 0000000..caf15c5 --- /dev/null +++ b/.omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json @@ -0,0 +1,8 @@ +{ + "session_id": "4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd", + "ended_at": "2026-04-09T20:26:34.016Z", + "reason": "other", + "agents_spawned": 0, + "agents_completed": 0, + "modes_used": [] +} \ No newline at end of file diff --git a/.omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json b/.omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json new file mode 100644 index 0000000..a1518aa --- /dev/null +++ b/.omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json @@ -0,0 +1,8 @@ +{ + "session_id": "50dc249e-8391-4ccd-8092-ddd3dd0e3a65", + "ended_at": "2026-04-09T20:14:23.610Z", + "reason": "other", + "agents_spawned": 0, + "agents_completed": 0, + "modes_used": [] +} \ No newline at end of file diff --git a/functions.php b/functions.php index c3627e0..6576111 100644 --- a/functions.php +++ b/functions.php @@ -1,4 +1,6 @@ Date: Thu, 9 Apr 2026 14:02:23 -0700 Subject: [PATCH 2/7] refactor: safe PHP 7.4 modernization (arrays, null coalescing) --- functions.php | 122 +++++++++++++++++++++++++------------------------- gexport.php | 42 ++++++++--------- setup.php | 6 +-- 3 files changed, 85 insertions(+), 85 deletions(-) diff --git a/functions.php b/functions.php index 6576111..09e5e4b 100644 --- a/functions.php +++ b/functions.php @@ -100,7 +100,7 @@ function graph_export($id = 0, $force = false) { db_execute_prepared('UPDATE graph_exports SET last_checked = NOW() WHERE id = ?', - array($export['id'])); + [$export['id']]); $runnow = false; if (!$force) { @@ -110,7 +110,7 @@ function graph_export($id = 0, $force = false) { db_execute_prepared('UPDATE graph_exports SET next_start = ? WHERE id = ?', - array($next_start, $export['id'])); + [$next_start, $export['id']]); } } else { $runnow = true; @@ -255,7 +255,7 @@ function run_export(&$export) { export_fatal($export, 'Export method not specified. Exporting can not continue. Please set method properly in Cacti configuration.'); } - db_execute_prepared('UPDATE graph_exports SET export_pid = 0 WHERE id = ?', array($export['id'])); + db_execute_prepared('UPDATE graph_exports SET export_pid = 0 WHERE id = ?', [$export['id']]); config_export_stats($export, $exported); } @@ -265,7 +265,7 @@ function export_rsync_execute(&$export, $stExportDir) { $user = $export['export_user']; $port = $export['export_port']; $host = $export['export_host']; - $output = array(); + $output = []; $prune = ''; $retvar = 0; @@ -342,7 +342,7 @@ function export_scp_execute(&$export, $stExportDir) { $user = $export['export_user']; $port = $export['export_port']; $host = $export['export_host']; - $output = array(); + $output = []; $retvar = 0; if ($export['export_private_key_path'] != '') { @@ -446,9 +446,9 @@ function config_export_stats(&$export, $exported) { db_execute_prepared('UPDATE graph_exports SET last_runtime = ?, total_graphs = ?, last_ended=NOW(), status=0 WHERE id = ?', - array($end - $start, $exported, $export['id'])); + [$end - $start, $exported, $export['id']]); - db_execute_prepared(sprintf("REPLACE INTO settings (name,value) values ('stats_export_%s', ?)", $export['id']), array($export_stats)); + db_execute_prepared(sprintf("REPLACE INTO settings (name,value) values ('stats_export_%s', ?)", $export['id']), [$export_stats]); } /* export_fatal - a simple export logging function that indicates a @@ -462,7 +462,7 @@ function export_fatal(&$export, $stMessage) { db_execute_prepared('UPDATE graph_exports SET last_error = ?, last_ended=NOW(), last_errored=NOW(), status=2 WHERE id = ?', - array($stMessage, $export['id'])); + [$stMessage, $export['id']]); exit; } @@ -560,7 +560,7 @@ function check_cacti_paths(&$export, $export_path) { /* check for bad directories within the cacti path */ if (strcasecmp($root_path, $export_path) < 0) { - $cacti_system_paths = array( + $cacti_system_paths = [ 'include', 'lib', 'install', @@ -569,7 +569,7 @@ function check_cacti_paths(&$export, $export_path) { 'scripts', 'plugins', 'images', - 'resource'); + 'resource']; foreach($cacti_system_paths as $cacti_system_path) { if (substr_count(strtolower($export_path), strtolower($cacti_system_path)) > 0) { @@ -593,7 +593,7 @@ function check_cacti_paths(&$export, $export_path) { function check_system_paths(&$export, $export_path) { /* don't allow to export to system paths */ - $system_paths = array( + $system_paths = [ '/boot', '/lib', '/usr', @@ -608,7 +608,7 @@ function check_system_paths(&$export, $export_path) { '/etc', 'windows', 'winnt', - 'program files'); + 'program files']; foreach($system_paths as $system_path) { if (substr($system_path, 0, 1) == '/') { @@ -678,16 +678,16 @@ function export_graphs(&$export, $export_path) { $sites = $export['graph_site']; $export_id = $export['id']; - $ntree = array(); - $graphs = array(); - $ngraph = array(); + $ntree = []; + $graphs = []; + $ngraph = []; $limit = 1000; $sql_where = ''; $hosts = ''; $total_rows = 0; $exported = 0; - $metadata = array(); + $metadata = []; if ($user == 0) { $user = -1; @@ -794,7 +794,7 @@ function export_graphs(&$export, $export_path) { SET last_error="WARNING: Max number of Graphs ' . $export['graph_max'] . ' reached", last_errored=NOW() WHERE id = ?', - array($export['id'])); + [$export['id']]); break; } @@ -809,7 +809,7 @@ function export_graphs(&$export, $export_path) { } function delTree($dir, $skip = false) { - $files = array_diff(scandir($dir), array('.','..')); + $files = array_diff(scandir($dir), ['.','..']); foreach ($files as $file) { (is_dir("$dir/$file") && !is_link($dir)) ? delTree("$dir/$file") : unlink("$dir/$file"); } @@ -877,7 +877,7 @@ function export_graph_monitor_tasks($export) { WHERE status = 1 AND id = ? AND pid = ?', - array($id, $pid)); + [$id, $pid]); $thread_adj--; } } @@ -922,7 +922,7 @@ function export_graph_prepare_task($export_id, $user, $folder, $local_graph_id) // MJV: Do something here db_execute_prepared('INSERT INTO graph_exports_tasks (export_id, local_graph_id, user, folder) - VALUES (?, ?, ?, ?)', array($export_id, $local_graph_id, $user, $folder)); + VALUES (?, ?, ?, ?)', [$export_id, $local_graph_id, $user, $folder]); } function export_graph_start_task($task_id) { @@ -932,7 +932,7 @@ function export_graph_start_task($task_id) { $task = db_fetch_row_prepared('SELECT * FROM graph_exports_tasks WHERE id = ?', - array($task_id)); + [$task_id]); if (!sizeof($task)) { export_warn('TASKS Launched ' . $task_id . ' - Invalid ID, Aborting'); @@ -946,14 +946,14 @@ function export_graph_start_task($task_id) { $export = db_fetch_row_prepared('SELECT * FROM graph_exports WHERE id = ?', - array($task['export_id'])); + [$task['export_id']]); $exports = export_graph_files($export, $task['user'], $task['folder'], $task['local_graph_id']); db_execute_prepared('UPDATE graph_exports_tasks SET status = 2 WHERE id = ?', - array($task['id'])); + [$task['id']]); } $end = microtime(true); @@ -1118,7 +1118,7 @@ function export_ftp_php_execute(&$export, $stExportDir, $stFtpType = 'ftp') { /* get rid of the files first */ $aFtpRemoteFiles = ftp_nlist($oFtpConnection, $aFtpExport['remotedir']); - if (is_array($aFtpRemoteFiles)) { + if (is_[$aFtpRemoteFiles]) { foreach ($aFtpRemoteFiles as $stFile) { export_log("Removing recursively remote file/directory '" . $stFile . "'"); export_ftp_rmdirr($oFtpConnection, $stFile); @@ -1226,7 +1226,7 @@ function export_ftp_ncftpput_execute($stExportDir) { $iExecuteReturns = 0; system($stExecute, $iExecuteReturns); - $aNcftpputStatusCodes = array ( + $aNcftpputStatusCodes = [ 'Success.', 'Could not connect to remote host.', 'Could not connect to remote host - timed out.', @@ -1238,7 +1238,7 @@ function export_ftp_ncftpput_execute($stExportDir) { 'Usage error.', 'Error in login configuration file.', 'Library initialization failed.', - 'Session initialization failed.'); + 'Session initialization failed.']; export_log('Ncftpput returned: ' . $aNcftpputStatusCodes[$iExecuteReturns]); } @@ -1283,9 +1283,9 @@ function export_post_ftp_upload(&$export, $stExportDir) { @arg $user - the effective user to use for export, -1 indicates no permission check @arg $export_path - the location to store the json array configuration file */ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, $user, $export_path) { - static $json_files = array(); + static $json_files = []; $total_rows = 0; - $graph_array = array(); + $graph_array = []; if ($type == 'branch') { $json_file = $export_path . '/tree_' . $tree_site_id . '_branch_' . $branch_id . '.json'; @@ -1297,37 +1297,37 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, WHERE graph_tree_id = ? AND parent = ? AND local_graph_id > 0 - ORDER BY position', array($tree_site_id, $branch_id)); + ORDER BY position', [$tree_site_id, $branch_id]); } elseif ($type == 'gtbranch') { $json_file = $export_path . '/site_' . $tree_site_id . '_gtbranch_0.json'; if (isset($json_files[$json_file])) return; - $graphs = array(); + $graphs = []; } elseif ($type == 'dqbranch') { $json_file = $export_path . '/site_' . $tree_site_id . '_gtbranch_0.json'; if (isset($json_files[$json_file])) return; - $graphs = array(); + $graphs = []; } elseif ($type == 'site') { $json_file = $export_path . '/site_' . $tree_site_id . '.json'; if (isset($json_files[$json_file])) return; - $graphs = array(); + $graphs = []; } elseif ($type == 'site_dt') { $json_file = $export_path . '/site_' . $tree_site_id . '_dt_' . $sub_id . '.json'; if (isset($json_files[$json_file])) return; - $graphs = array(); + $graphs = []; } elseif ($type == 'site_gt') { $json_file = $export_path . '/site_' . $tree_site_id . '_gt_' . $sub_id . '.json'; if (isset($json_files[$json_file])) return; - $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', array($tree_site_id)), 'id', 'id'); + $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', [$tree_site_id]), 'id', 'id'); $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gt.id = ' . $sub_id . ')', 'gtg.title_cache', '', $total_rows, $user); } elseif ($type == 'site_dq') { @@ -1335,7 +1335,7 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, if (isset($json_files[$json_file])) return; - $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', array($tree_site_id)), 'id', 'id'); + $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', [$tree_site_id]), 'id', 'id'); $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gl.snmp_query_id = ' . $sub_id . ')', 'gtg.title_cache', '', $total_rows, $user); } elseif ($type == 'site_dqi') { @@ -1348,10 +1348,10 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, if (isset($json_files[$json_file])) return; - $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', array($tree_site_id)), 'id', 'id'); + $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', [$tree_site_id]), 'id', 'id'); $sql_where = ''; - if (is_array($values) && cacti_sizeof($values)) { + if (is_[$values] && cacti_sizeof($values)) { foreach($values as $value) { // host_id | snmp_index $parts = explode('|', $value); @@ -1443,7 +1443,7 @@ function export_generate_tree_html($export_path, $tree, $parent, $expand_hosts, AND host_id = 0 AND graph_tree_id = ? AND parent = ?', - array($tree['id'], $parent)); + [$tree['id'], $parent]); if (cacti_sizeof($branches)) { foreach($branches as $branch) { @@ -1454,7 +1454,7 @@ function export_generate_tree_html($export_path, $tree, $parent, $expand_hosts, WHERE graph_tree_id = ? AND local_graph_id = 0 AND parent = ?', - array($tree['id'], $branch['id'])); + [$tree['id'], $branch['id']]); $jstree .= str_repeat("\t", $depth) . '
  • ' . $branch['title']; @@ -1477,7 +1477,7 @@ function export_generate_tree_html($export_path, $tree, $parent, $expand_hosts, WHERE graph_tree_id = ? AND parent = ? AND host_id > 0 - ORDER BY position', array($tree['id'], $parent)); + ORDER BY position', [$tree['id'], $parent]); if (cacti_sizeof($hosts)) { foreach($hosts as $host) { @@ -1521,7 +1521,7 @@ function export_generate_tree_html($export_path, $tree, $parent, $expand_hosts, INNER JOIN host_snmp_query AS hsq ON sq.id=hsq.snmp_query_id WHERE hsq.host_id = ?', - array($host['host_id'])); + [$host['host_id']]); $data_queries[] = array('id' => '0', 'name' => __('Non Query Based', 'gexport')); @@ -1545,9 +1545,9 @@ function export_generate_tree_html($export_path, $tree, $parent, $expand_hosts, FROM graph_local AS gl WHERE host_id = ? AND snmp_query_id = ?', - array($host['host_id'], $query['id'])); + [$host['host_id'], $query['id']]); - $dqi = array(); + $dqi = []; foreach($graphs as $graph) { $dqi[$graph['snmp_index']] = $graph['snmp_index']; } @@ -1623,7 +1623,7 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, FROM host_template AS dt INNER JOIN host AS h ON h.host_template_id=dt.id - WHERE h.site_id = ?', array($site['id'])); + WHERE h.site_id = ?', [$site['id']]); if (cacti_sizeof($device_templates)) { foreach($device_templates as $branch) { @@ -1640,7 +1640,7 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, WHERE site_id = ? AND host_template_id = ? ORDER BY description', - array($site['id'], $branch['id'])); + [$site['id'], $branch['id']]); if (cacti_sizeof($hosts)) { foreach($hosts as $host) { @@ -1684,7 +1684,7 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, INNER JOIN host_snmp_query AS hsq ON sq.id=hsq.snmp_query_id WHERE hsq.host_id = ?', - array($host['host_id'])); + [$host['host_id']]); $data_queries[] = array('id' => '0', 'name' => __('Non Query Based', 'gexport')); @@ -1708,9 +1708,9 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, FROM graph_local AS gl WHERE host_id = ? AND snmp_query_id = ?', - array($host['host_id'], $query['id'])); + [$host['host_id'], $query['id']]); - $dqi = array(); + $dqi = []; foreach($graphs as $graph) { $dqi[$graph['snmp_index']] = $graph['snmp_index']; } @@ -1767,7 +1767,7 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, INNER JOIN host AS h ON h.id = gl.host_id WHERE h.site_id = ? - ORDER BY gt.name', array($site['id'])); + ORDER BY gt.name', [$site['id']]); if (cacti_sizeof($graph_templates)) { $jstree .= str_repeat("\t", $depth) . "
  • " . __('Graph Templates', 'gexport') . "\n"; @@ -1796,7 +1796,7 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, ON dq.id=gl.snmp_query_id INNER JOIN host AS h ON h.id = gl.host_id - WHERE h.site_id = ?', array($site['id'])); + WHERE h.site_id = ?', [$site['id']]); if (cacti_sizeof($data_queries)) { $jstree .= str_repeat("\t", $depth) . "
  • " . __('Data Queries', 'gexport') . "\n"; @@ -1822,11 +1822,11 @@ function export_generate_site_html($export_path, $site, $parent, $expand_hosts, ON gl.host_id=h.id WHERE h.site_id = ? AND snmp_query_id = ?', - array($site['id'], $branch['id'])); + [$site['id'], $branch['id']]); - $sort_field_data = array(); + $sort_field_data = []; - $dqi = array(); + $dqi = []; foreach($graphs as $graph) { if (!isset($sort_field_data[$graph['host_id']])) { $sort_field_data[$graph['host_id']] = get_formatted_data_query_indexes($graph['host_id'], $branch['id']); @@ -1886,7 +1886,7 @@ function tree_site_export(&$export, $export_path) { $jstree .= str_repeat("\t", 4) . "
      \n";; $user = $export['export_effective_user']; - $ntree = array(); + $ntree = []; $sql_where = ''; $total_rows = 0; $parent = 0; @@ -1922,7 +1922,7 @@ function tree_site_export(&$export, $export_path) { if (cacti_sizeof($sites)) { foreach($sites as $site_id) { - $site_data = db_fetch_row_prepared('SELECT * FROM sites WHERE id = ?', array($site_id)); + $site_data = db_fetch_row_prepared('SELECT * FROM sites WHERE id = ?', [$site_id]); if (cacti_sizeof($site_data)) { $jstree .= str_repeat("\t", 4) . "
    • " . $site_data['name'] . "\n";; @@ -2066,27 +2066,27 @@ function create_export_directory_structure(&$export, $root_path, $export_path) { copy("$root_path/include/themes/$theme/images/cacti_logo.svg", "$export_path/images/cacti_logo.svg"); /* jstree theme files */ - $files = array('32px.png', '40px.png', 'style.css', 'throbber.gif'); + $files = ['32px.png', '40px.png', 'style.css', 'throbber.gif']; foreach($files as $file) { copy("$root_path/include/themes/$theme/default/$file", "$export_path/css/default/$file"); } $directory = "$root_path/include/themes/$theme/images"; - $directory = array_diff(glob("$directory/*.*"), array("$directory/.", "$directory/..")); + $directory = array_diff(glob("$directory/*.*"), ["$directory/.", "$directory/.."]); foreach($directory as $file) { $file = basename($file); copy("$root_path/include/themes/$theme/images/$file", "$export_path/css/images/$file"); } $directory = "$root_path/include/fa/webfonts"; - $directory = array_diff(glob("$directory/*.*"), array("$directory/.", "$directory/..")); + $directory = array_diff(glob("$directory/*.*"), ["$directory/.", "$directory/.."]); foreach($directory as $file) { $file = basename($file); copy("$root_path/include/fa/webfonts/$file", "$export_path/webfonts/$file"); } $directory = "$root_path/include/fa/svgs"; - $directory = array_diff(glob("$directory/*.*"), array("$directory/.", "$directory/..")); + $directory = array_diff(glob("$directory/*.*"), ["$directory/.", "$directory/.."]); foreach($directory as $file) { $file = basename($file); copy("$root_path/include/fa/svgs/$file", "$export_path/svgs/$file"); @@ -2096,13 +2096,13 @@ function create_export_directory_structure(&$export, $root_path, $export_path) { /* get_host_description - a simple function to return the host description of a host. @arg $host_id - the id of the host in question */ function get_host_description($host_id) { - return db_fetch_cell_prepared('SELECT description FROM host WHERE id = ?', array($host_id)); + return db_fetch_cell_prepared('SELECT description FROM host WHERE id = ?', [$host_id]); } /* get_tree_name - a simple function to return the tree name of a tree. @arg $tree_id - the id of the tree in question */ function get_tree_name($tree_id) { - return db_fetch_cell_prepared('SELECT name FROM graph_tree WHERE id = ?', array($tree_id)); + return db_fetch_cell_prepared('SELECT name FROM graph_tree WHERE id = ?', [$tree_id]); } /* del_directory - delete the directory pointed to by the $path variable. diff --git a/gexport.php b/gexport.php index c6ddaba..7d5e7fb 100644 --- a/gexport.php +++ b/gexport.php @@ -150,7 +150,7 @@ function export_form_save() { function duplicate_export($_export_id, $export_title) { global $fields_export_edit; - $export = db_fetch_row_prepared('SELECT * FROM graph_exports WHERE id = ?', array($_export_id)); + $export = db_fetch_row_prepared('SELECT * FROM graph_exports WHERE id = ?', [$_export_id]); /* substitute the title variable */ $export['name'] = str_replace('', $export['name'], $export_title); @@ -241,7 +241,7 @@ function export_form_actions() { input_validate_input_number($matches[1]); /* ==================================================== */ - $export_list .= '
    • ' . db_fetch_cell_prepared('SELECT name FROM graph_exports WHERE id = ?', array($matches[1])) . '
    • '; + $export_list .= '
    • ' . db_fetch_cell_prepared('SELECT name FROM graph_exports WHERE id = ?', [$matches[1]]) . '
    • '; $export_array[] = $matches[1]; } } @@ -320,11 +320,11 @@ function export_form_actions() { --------------------- */ function export_enable($export_id) { - db_execute_prepared('UPDATE graph_exports SET enabled="on" WHERE id = ?', array($export_id)); + db_execute_prepared('UPDATE graph_exports SET enabled="on" WHERE id = ?', [$export_id]); } function export_disable($export_id) { - db_execute_prepared('UPDATE graph_exports SET enabled="" WHERE id = ?', array($export_id)); + db_execute_prepared('UPDATE graph_exports SET enabled="" WHERE id = ?', [$export_id]); } function export_runnow($export_id) { @@ -332,7 +332,7 @@ function export_runnow($export_id) { include_once('./lib/poller.php'); - $status = db_fetch_row_prepared('SELECT status, enabled FROM graph_exports WHERE id = ?', array($export_id)); + $status = db_fetch_row_prepared('SELECT status, enabled FROM graph_exports WHERE id = ?', [$export_id]); if (($status['status'] == 0 || $status['status'] == 2) && $status['enabled'] == 'on') { $command_string = read_config_option('path_php_binary'); @@ -363,8 +363,8 @@ function export_edit() { draw_edit_form( array( - 'config' => array('no_form_tag' => true), - 'fields' => inject_form_variables($fields_export_edit, (isset($export) ? $export : array())) + 'config' => ['no_form_tag' => true], + 'fields' => inject_form_variables($fields_export_edit, (isset($export) ? $export : [])) ) ); @@ -697,40 +697,40 @@ function gexport() { /* ================= input validation and session storage ================= */ $filters = array( - 'rows' => array( + 'rows' => [ 'filter' => FILTER_VALIDATE_INT, 'pageset' => true, 'default' => '-1' - ), - 'page' => array( + ], + 'page' => [ 'filter' => FILTER_VALIDATE_INT, 'default' => '1' - ), - 'refresh' => array( + ], + 'refresh' => [ 'filter' => FILTER_VALIDATE_INT, 'default' => '20' - ), - 'filter' => array( + ], + 'filter' => [ 'filter' => FILTER_DEFAULT, 'pageset' => true, 'default' => '' - ), + ], 'sort_column' => array( 'filter' => FILTER_CALLBACK, 'default' => 'name', - 'options' => array('options' => 'sanitize_search_string') + 'options' => ['options' => 'sanitize_search_string'] ), 'sort_direction' => array( 'filter' => FILTER_CALLBACK, 'default' => 'ASC', - 'options' => array('options' => 'sanitize_search_string') + 'options' => ['options' => 'sanitize_search_string'] ) ); validate_store_request_vars($filters, 'sess_gexport'); /* ================= input validation ================= */ - $refresh = array(); + $refresh = []; $refresh['page'] = 'gexport.php?header=false'; $refresh['seconds'] = get_request_var('refresh'); $refresh['logout'] = 'false'; @@ -740,7 +740,7 @@ function gexport() { export_filter(); $total_rows = 0; - $exports = array(); + $exports = []; if (get_request_var('rows') == '-1') { $rows = read_config_option('num_rows_table'); @@ -839,7 +839,7 @@ function gexport() { $user = db_fetch_cell_prepared('SELECT username FROM user_auth WHERE id = ?', - array($export['export_effective_user'])); + [$export['export_effective_user']]); if ($export['export_pid'] > 0 && $export['status'] > 0) { if (function_exists('posix_getpgid')) { @@ -852,7 +852,7 @@ function gexport() { db_execute_prepared('UPDATE graph_exports SET status=0, export_pid=0, last_error="Killed Outside Cacti", last_errored=NOW() WHERE id = ?', - array($export['id'])); + [$export['id']]); } } diff --git a/setup.php b/setup.php index b349d48..cf6a255 100644 --- a/setup.php +++ b/setup.php @@ -73,7 +73,7 @@ function gexport_check_upgrade() { include_once($config['library_path'] . '/functions.php'); // Let's only run this check if we are on a page that actually needs the data - $files = array('plugins.php', 'gexport.php'); + $files = ['plugins.php', 'gexport.php']; if (!in_array(get_current_page(), $files)) { return; } @@ -261,13 +261,13 @@ function gexport_config_arrays() { $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR; if (isset($_SESSION['gexport_message']) && $_SESSION['gexport_message'] != '') { - $messages['gexport_message'] = array('message' => $_SESSION['gexport_message'], 'type' => 'info'); + $messages['gexport_message'] = ['message' => $_SESSION['gexport_message'], 'type' => 'info']; } $menu[__('Utilities')]['plugins/gexport/gexport.php'] = __('Graph Exports', 'gexport'); if (function_exists('auth_augment_roles')) { - auth_augment_roles(__('General Administration'), array('gexport.php')); + auth_augment_roles(__('General Administration'), ['gexport.php']); } $sites = array_rekey(db_fetch_assoc('SELECT "0" AS id, "All Sites" AS name UNION SELECT id, name FROM sites ORDER BY name'), 'id', 'name'); From 28dd75f262698888574460bdab5f20816dcee726 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 21:18:06 -0700 Subject: [PATCH 3/7] fix: restore is_array/in_array calls and remove .omc artifacts Revert corrupted function calls introduced by refactoring tool: - is_[$x] -> is_array($x) - in_[$x, ...] -> in_array($x, ...) - xml2[$x] -> xml2array($x) Also remove accidentally committed .omc session files and add .omc/ to .gitignore. Signed-off-by: Thomas Vincent --- .gitignore | 1 + .omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json | 8 -------- .omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json | 8 -------- functions.php | 4 ++-- 4 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 .omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json delete mode 100644 .omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json diff --git a/.gitignore b/.gitignore index 6621d40..f857b7e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ .git* locales/po/*.mo +.omc/ diff --git a/.omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json b/.omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json deleted file mode 100644 index caf15c5..0000000 --- a/.omc/sessions/4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "session_id": "4bfc95aa-eb89-43b1-af1f-3a9d70edf9bd", - "ended_at": "2026-04-09T20:26:34.016Z", - "reason": "other", - "agents_spawned": 0, - "agents_completed": 0, - "modes_used": [] -} \ No newline at end of file diff --git a/.omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json b/.omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json deleted file mode 100644 index a1518aa..0000000 --- a/.omc/sessions/50dc249e-8391-4ccd-8092-ddd3dd0e3a65.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "session_id": "50dc249e-8391-4ccd-8092-ddd3dd0e3a65", - "ended_at": "2026-04-09T20:14:23.610Z", - "reason": "other", - "agents_spawned": 0, - "agents_completed": 0, - "modes_used": [] -} \ No newline at end of file diff --git a/functions.php b/functions.php index 09e5e4b..ec2bcc6 100644 --- a/functions.php +++ b/functions.php @@ -1118,7 +1118,7 @@ function export_ftp_php_execute(&$export, $stExportDir, $stFtpType = 'ftp') { /* get rid of the files first */ $aFtpRemoteFiles = ftp_nlist($oFtpConnection, $aFtpExport['remotedir']); - if (is_[$aFtpRemoteFiles]) { + if (is_array($aFtpRemoteFiles)) { foreach ($aFtpRemoteFiles as $stFile) { export_log("Removing recursively remote file/directory '" . $stFile . "'"); export_ftp_rmdirr($oFtpConnection, $stFile); @@ -1351,7 +1351,7 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', [$tree_site_id]), 'id', 'id'); $sql_where = ''; - if (is_[$values] && cacti_sizeof($values)) { + if (is_array($values) && cacti_sizeof($values)) { foreach($values as $value) { // host_id | snmp_index $parts = explode('|', $value); From ea635ec3b08692449f034dedd4a95dc9acf1cbae Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 22:36:05 -0700 Subject: [PATCH 4/7] 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 --- functions.php | 32 ++++++++++++++++++++++++++++---- gexport.php | 2 -- locales/LC_MESSAGES/index.php | 2 -- locales/index.php | 2 -- locales/po/index.php | 2 -- setup.php | 2 -- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/functions.php b/functions.php index ec2bcc6..6808a8c 100644 --- a/functions.php +++ b/functions.php @@ -1,6 +1,4 @@ Date: Fri, 10 Apr 2026 01:30:27 -0700 Subject: [PATCH 5/7] fix: guard get_allowed_graphs IN() against empty device list Sites with no hosts produced 'IN()' SQL syntax errors. Return empty graphs array when device list is empty for site_gt, site_dq, and site_dqi export types. Signed-off-by: Thomas Vincent --- functions.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/functions.php b/functions.php index 6808a8c..99fd800 100644 --- a/functions.php +++ b/functions.php @@ -1353,7 +1353,11 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', [$tree_site_id]), 'id', 'id'); - $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gt.id = ' . $sub_id . ')', 'gtg.title_cache', '', $total_rows, $user); + if (empty($devices)) { + $graphs = []; + } else { + $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gt.id = ' . $sub_id . ')', 'gtg.title_cache', '', $total_rows, $user); + } } elseif ($type == 'site_dq') { $json_file = $export_path . '/site_' . $tree_site_id . '_dq_' . $sub_id . '.json'; @@ -1361,7 +1365,11 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, $devices = array_rekey(db_fetch_assoc_prepared('SELECT id FROM host WHERE site_id = ?', [$tree_site_id]), 'id', 'id'); - $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gl.snmp_query_id = ' . $sub_id . ')', 'gtg.title_cache', '', $total_rows, $user); + if (empty($devices)) { + $graphs = []; + } else { + $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gl.snmp_query_id = ' . $sub_id . ')', 'gtg.title_cache', '', $total_rows, $user); + } } elseif ($type == 'site_dqi') { $parts = explode(':', $sub_id); $dq = $parts[0]; @@ -1384,7 +1392,11 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, $sql_where .= ')'; } - $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gl.snmp_query_id=' . $dq . $sql_where . ')', 'gtg.title_cache', '', $total_rows, $user); + if (empty($devices)) { + $graphs = []; + } else { + $graphs = get_allowed_graphs('(gl.host_id IN(' . implode(',', $devices) . ') AND gl.snmp_query_id=' . $dq . $sql_where . ')', 'gtg.title_cache', '', $total_rows, $user); + } } elseif ($type == 'host') { $json_file = $export_path . '/host_' . $host_id . '.json'; From 0e133666adbfef9acd7fb1fa3a78e4aca82dd92c Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Fri, 10 Apr 2026 07:01:01 -0700 Subject: [PATCH 6/7] style: remove trailing double semicolons Signed-off-by: Thomas Vincent --- functions.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/functions.php b/functions.php index 99fd800..e44a96c 100644 --- a/functions.php +++ b/functions.php @@ -1449,7 +1449,7 @@ function write_branch_conf($tree_site_id, $branch_id, $type, $host_id, $sub_id, $json_files[$json_file] = true; - return cacti_sizeof($graph_array);; + return cacti_sizeof($graph_array); } /* export_generate_tree_html - create jstree compatible static tree html. This is a @@ -1920,7 +1920,7 @@ function tree_site_export(&$export, $export_path) { $jstree .= str_repeat("\t", 5) . "var theme='" . $export['export_theme'] . "';\n"; $jstree .= str_repeat("\t", 4) . "\n"; - $jstree .= str_repeat("\t", 4) . "
        \n";; + $jstree .= str_repeat("\t", 4) . "
          \n"; $user = $export['export_effective_user']; $ntree = []; $sql_where = ''; @@ -1942,7 +1942,7 @@ function tree_site_export(&$export, $export_path) { if (cacti_sizeof($trees)) { foreach($trees as $tree) { - $jstree .= str_repeat("\t", 4) . "
        • " . get_tree_name($tree['id']) . "\n";; + $jstree .= str_repeat("\t", 4) . "
        • " . get_tree_name($tree['id']) . "\n"; $jstree = export_generate_tree_html($export_path, $tree, $parent, $export['export_expand_hosts'], $user, $jstree); $jstree .= str_repeat("\t", 4) . "
        • \n"; } @@ -1961,7 +1961,7 @@ function tree_site_export(&$export, $export_path) { $site_data = db_fetch_row_prepared('SELECT * FROM sites WHERE id = ?', [$site_id]); if (cacti_sizeof($site_data)) { - $jstree .= str_repeat("\t", 4) . "
        • " . $site_data['name'] . "\n";; + $jstree .= str_repeat("\t", 4) . "
        • " . $site_data['name'] . "\n"; $jstree = export_generate_site_html($export_path, $site_data, $parent, $export['export_expand_hosts'], $user, $jstree); $jstree .= str_repeat("\t", 4) . "
        • \n"; } From afcb72a6a447bcbe3b24edb6d6665b07eaeb9818 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Sat, 11 Apr 2026 13:41:49 -0700 Subject: [PATCH 7/7] fix(security): harden bulk action and nav filter output --- gexport.php | 26 +++++++------ gexport_security.php | 28 ++++++++++++++ .../test_bulk_action_output_security.php | 32 ++++++++++++++++ tests/Unit/test_gexport_security_helpers.php | 37 +++++++++++++++++++ tests/e2e/test_filter_nav_encoding.php | 28 ++++++++++++++ 5 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 gexport_security.php create mode 100644 tests/Integration/test_bulk_action_output_security.php create mode 100644 tests/Unit/test_gexport_security_helpers.php create mode 100644 tests/e2e/test_filter_nav_encoding.php diff --git a/gexport.php b/gexport.php index 20a59d0..50a7fe1 100644 --- a/gexport.php +++ b/gexport.php @@ -25,6 +25,7 @@ chdir('../../'); include('./include/auth.php'); include_once('./plugins/gexport/functions.php'); +include_once('./plugins/gexport/gexport_security.php'); $export_actions = array( '1' => __('Delete', 'gexport'), @@ -173,12 +174,14 @@ function duplicate_export($_export_id, $export_title) { function export_form_actions() { global $export_actions; + $bulk_action = gexport_normalize_bulk_action(get_nfilter_request_var('drp_action')); + /* if we are to save this form, instead of display it */ if (isset_request_var('selected_items')) { $selected_items = sanitize_unserialize_selected_items(get_nfilter_request_var('selected_items')); if ($selected_items != false) { - if (get_nfilter_request_var('drp_action') === '1') { /* delete */ + if ($bulk_action === '1') { /* delete */ /* do a referential integrity check */ if (sizeof($selected_items)) { foreach($selected_items as $export_id) { @@ -193,7 +196,7 @@ function export_form_actions() { if (isset($export_ids)) { db_execute('DELETE FROM graph_exports WHERE ' . array_to_sql_or($export_ids, 'id')); } - } elseif (get_nfilter_request_var('drp_action') === '2') { /* enable */ + } elseif ($bulk_action === '2') { /* enable */ for ($i=0;($i

          " . __n('Click \'Continue\' to delete the following Graph Export Definition.', 'Click \'Continue\' to delete following Graph Export Definitions.', sizeof($export_array), 'gexport') . "

          @@ -261,7 +264,7 @@ function export_form_actions() { $save_html = " '; - } elseif (get_nfilter_request_var('drp_action') === '2') { /* disable */ + } elseif ($bulk_action === '2') { /* disable */ print "

          " . __n('Click \'Continue\' to disable the following Graph Export Definition.', 'Click \'Continue\' to disable following Graph Export Definitions.', sizeof($export_array), 'gexport') . "

          @@ -271,7 +274,7 @@ function export_form_actions() { $save_html = " '; - } elseif (get_nfilter_request_var('drp_action') === '3') { /* enable */ + } elseif ($bulk_action === '3') { /* enable */ print "

          " . __n('Click \'Continue\' to enable the following Graph Export Definition.', 'Click \'Continue\' to enable following Graph Export Definitions.', sizeof($export_array), 'gexport') . "

          @@ -281,7 +284,7 @@ function export_form_actions() { $save_html = " " . __esc('Continue', 'gexport') . ''; - } elseif (get_nfilter_request_var('drp_action') === '4') { /* export now */ + } elseif ($bulk_action === '4') { /* export now */ print "

          " . __n('Click \'Continue\' to run the following Graph Export Definition now.', 'Click \'Continue\' to run following Graph Export Definitions now.', sizeof($export_array)) . "

          @@ -301,7 +304,7 @@ function export_form_actions() { - + $save_html "; @@ -821,7 +824,7 @@ function gexport() { ) ); - $nav = html_nav_bar('gexport.php?filter=' . get_request_var('filter'), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, sizeof($display_text) + 1, __('Export Definitions', 'gexport'), 'page', 'main'); + $nav = html_nav_bar(gexport_build_nav_filter_url(get_request_var('filter')), MAX_DISPLAY_PAGES, get_request_var('page'), $rows, $total_rows, sizeof($display_text) + 1, __('Export Definitions', 'gexport'), 'page', 'main'); form_start('gexport.php', 'chk'); @@ -937,4 +940,3 @@ function gexport() { form_end(); } - diff --git a/gexport_security.php b/gexport_security.php new file mode 100644 index 0000000..f318e62 --- /dev/null +++ b/gexport_security.php @@ -0,0 +1,28 @@ + 4) { + return ''; + } + + return (string) $action; +} + +function gexport_build_nav_filter_url($filter) { + return 'gexport.php?filter=' . rawurlencode((string) $filter); +} diff --git a/tests/Integration/test_bulk_action_output_security.php b/tests/Integration/test_bulk_action_output_security.php new file mode 100644 index 0000000..0d2e44b --- /dev/null +++ b/tests/Integration/test_bulk_action_output_security.php @@ -0,0 +1,32 @@ +', +); + +foreach ($checks as $check) { + if (strpos($contents, $check) === false) { + fwrite(STDERR, "Missing expected security wiring: {$check}\n"); + exit(1); + } +} + +print "OK\n"; diff --git a/tests/Unit/test_gexport_security_helpers.php b/tests/Unit/test_gexport_security_helpers.php new file mode 100644 index 0000000..107c4b7 --- /dev/null +++ b/tests/Unit/test_gexport_security_helpers.php @@ -0,0 +1,37 @@ +