Skip to content

Commit 2f29019

Browse files
fix: add PHPStan level 8 type guards to lib/api_automation.php (Cacti#6758)
* fix: add PHPStan level 8 type guards to lib/api_automation.php Add defensive type checks (is_array, !== false, int casts) to resolve PHPStan level 8 errors. No behavioral changes for normal operation; only error paths (db failure, file open failure) are affected. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: remove redundant is_array() checks alongside cacti_sizeof() in api_automation.php cacti_sizeof() already returns false for non-arrays, so paired is_array() checks are redundant. Flagged by TheWitness review. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: move name substitution outside cacti_sizeof guard Without this, duplicate rules created when $rule is empty get no name, causing sql_save() to insert an incomplete record. Signed-off-by: Thomas Vincent <thomasvincent@users.noreply.github.com> Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Signed-off-by: Thomas Vincent <thomasvincent@users.noreply.github.com> Co-authored-by: TheWitness <thewitness@cacti.net>
1 parent ecd10b1 commit 2f29019

File tree

1 file changed

+78
-35
lines changed

1 file changed

+78
-35
lines changed

lib/api_automation.php

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -955,8 +955,12 @@ function display_new_graphs(array $rule, string $url) : void {
955955

956956
$field_names = get_field_names($rule['snmp_query_id']);
957957

958-
array_unshift($field_names, ['field_name' => 'status']);
959-
array_unshift($field_names, ['field_name' => 'automation_host']);
958+
if (is_array($field_names)) {
959+
array_unshift($field_names, ['field_name' => 'status']);
960+
array_unshift($field_names, ['field_name' => 'automation_host']);
961+
} else {
962+
$field_names = [['field_name' => 'status'], ['field_name' => 'automation_host']];
963+
}
960964

961965
$display_text = [];
962966

@@ -1669,9 +1673,11 @@ function duplicate_automation_graph_rules(int $_id, string $_title) : void {
16691673

16701674
$save = [];
16711675

1672-
foreach ($fields_automation_graph_rules_edit as $field => $array) {
1673-
if (!preg_match('/^hidden/', $array['method'])) {
1674-
$save[$field] = $rule[$field];
1676+
if (cacti_sizeof($rule)) {
1677+
foreach ($fields_automation_graph_rules_edit as $field => $array) {
1678+
if (!preg_match('/^hidden/', $array['method'])) {
1679+
$save[$field] = $rule[$field];
1680+
}
16751681
}
16761682
}
16771683

@@ -1736,14 +1742,17 @@ function duplicate_automation_tree_rules(int $_id, string $_title) : void {
17361742

17371743
$save = [];
17381744

1739-
foreach ($fields_automation_tree_rules_edit as $field => $array) {
1740-
if (!preg_match('/^hidden/', $array['method'])) {
1741-
$save[$field] = $rule[$field];
1745+
if (cacti_sizeof($rule)) {
1746+
foreach ($fields_automation_tree_rules_edit as $field => $array) {
1747+
if (!preg_match('/^hidden/', $array['method'])) {
1748+
$save[$field] = $rule[$field];
1749+
}
17421750
}
17431751
}
17441752

17451753
// substitute the title variable
17461754
$save['name'] = str_replace('<rule_name>', $rule['name'], $_title);
1755+
17471756
// create new rule
17481757
$save['enabled'] = ''; // no new rule accidentally taking action immediately
17491758
$save['id'] = 0;
@@ -2003,9 +2012,9 @@ function build_sort_order(string $index_order, string $default_order = '') : str
20032012
* @param int $rule_type The type of rule to apply.
20042013
* @param string $sql_where Optional SQL WHERE clause to filter the results.
20052014
*
2006-
* @return array - The list of matching hosts.
2015+
* @return array|bool - The list of matching hosts.
20072016
*/
2008-
function get_matching_hosts(array $rule, int $rule_type, string $sql_where = '') : array {
2017+
function get_matching_hosts(array $rule, int $rule_type, string $sql_where = '') : array|bool {
20092018
$function = automation_function_with_pid(__FUNCTION__);
20102019

20112020
cacti_log($function . ' called: ' . json_encode($rule) . ' type: ' . $rule_type, false, 'AUTOM8 TRACE', POLLER_VERBOSITY_HIGH);
@@ -2058,9 +2067,9 @@ function get_matching_hosts(array $rule, int $rule_type, string $sql_where = '')
20582067
* @param int $rule_type The type of rule to apply.
20592068
* @param string $sql_where Optional SQL WHERE clause to further filter the results.
20602069
*
2061-
* @return array The list of matching graphs.
2070+
* @return array|bool The list of matching graphs.
20622071
*/
2063-
function get_matching_graphs(array $rule, int $rule_type, string $sql_where = '') : array {
2072+
function get_matching_graphs(array $rule, int $rule_type, string $sql_where = '') : array|bool {
20642073
$function = automation_function_with_pid(__FUNCTION__);
20652074

20662075
cacti_log($function . ' called: ' . json_encode($rule) . ' type: ' . $rule_type, false, 'AUTOM8 TRACE', POLLER_VERBOSITY_HIGH);
@@ -2274,9 +2283,9 @@ function get_query_fields(string $table, array $excluded_fields) : array {
22742283
*
22752284
* @param string $snmp_query_id The ID of the SNMP query for which to retrieve field names.
22762285
*
2277-
* @return array An array of field names associated with the specified SNMP query ID.
2286+
* @return array|bool An array of field names associated with the specified SNMP query ID.
22782287
*/
2279-
function get_field_names(string $snmp_query_id) : array {
2288+
function get_field_names(string $snmp_query_id) : array|bool {
22802289
$function = automation_function_with_pid(__FUNCTION__);
22812290
cacti_log($function . " called: $snmp_query_id", false, 'AUTOM8 TRACE', POLLER_VERBOSITY_HIGH);
22822291

@@ -2349,12 +2358,12 @@ function array_minus(array $big_array, array $small_array) : array {
23492358
* @param array|string $replace The replacement value that replaces found search values.
23502359
* @param string $target The string being searched and replaced on.
23512360
*
2352-
* @return array The resulting string after the replacements have been made.
2361+
* @return list<string> The resulting string after the replacements have been made.
23532362
*/
23542363
function automation_string_replace(string $search, array|string $replace, string $target) : array {
23552364
$repl = preg_replace('/' . $search . '/i', $replace, $target);
23562365

2357-
return preg_split('/\\\\n/', $repl, -1, PREG_SPLIT_NO_EMPTY);
2366+
return preg_split('/\\\\n/', $repl ?? '', -1, PREG_SPLIT_NO_EMPTY) ?: [];
23582367
}
23592368

23602369
/**
@@ -2415,7 +2424,7 @@ function global_item_edit(int $rule_id, int $rule_item_id, int $rule_type) : voi
24152424

24162425
$_fields_rule_item_edit = $fields_automation_graph_rule_item_edit;
24172426

2418-
$xml_array = get_data_query_array($automation_rule['snmp_query_id']);
2427+
$xml_array = is_array($automation_rule) ? get_data_query_array($automation_rule['snmp_query_id']) : [];
24192428
$fields = [];
24202429

24212430
if (cacti_sizeof($xml_array) && cacti_sizeof($xml_array['fields'])) {
@@ -2448,10 +2457,10 @@ function global_item_edit(int $rule_id, int $rule_item_id, int $rule_type) : voi
24482457
$query_fields += get_query_fields('sites', ['id']);
24492458
$query_fields += get_query_fields('host_snmp_cache', ['host_id', 'snmp_query_id', 'oid', 'present', 'last_updated', 'snmp_index']);
24502459

2451-
if ($automation_rule['leaf_type'] == TREE_ITEM_TYPE_HOST) {
2460+
if (is_array($automation_rule) && $automation_rule['leaf_type'] == TREE_ITEM_TYPE_HOST) {
24522461
$title = __('Device Match Rule');
24532462
$tables = ['host', 'host_templates'];
2454-
} elseif ($automation_rule['leaf_type'] == TREE_ITEM_TYPE_GRAPH) {
2463+
} elseif (is_array($automation_rule) && $automation_rule['leaf_type'] == TREE_ITEM_TYPE_GRAPH) {
24552464
$title = __('Graph Match Rule');
24562465
$tables = ['host', 'host_templates'];
24572466

@@ -2485,10 +2494,10 @@ function global_item_edit(int $rule_id, int $rule_item_id, int $rule_type) : voi
24852494
/* list of allowed header types depends on rule leaf_type
24862495
* e.g. for a Device Rule, only Device-related header types make sense
24872496
*/
2488-
if ($automation_rule['leaf_type'] == TREE_ITEM_TYPE_HOST) {
2497+
if (is_array($automation_rule) && $automation_rule['leaf_type'] == TREE_ITEM_TYPE_HOST) {
24892498
$title = __('Create Tree Rule (Device)');
24902499
$tables = ['host', 'host_templates'];
2491-
} elseif ($automation_rule['leaf_type'] == TREE_ITEM_TYPE_GRAPH) {
2500+
} elseif (is_array($automation_rule) && $automation_rule['leaf_type'] == TREE_ITEM_TYPE_GRAPH) {
24922501
$title = __('Create Tree Rule (Graph)');
24932502
$tables = ['host', 'host_templates'];
24942503

@@ -2535,9 +2544,9 @@ function global_item_edit(int $rule_id, int $rule_item_id, int $rule_type) : voi
25352544
}
25362545
}
25372546

2538-
$header_label = __esc('Rule Item [edit rule item for %s: %s]', $title, $automation_rule['name']);
2547+
$header_label = __esc('Rule Item [edit rule item for %s: %s]', $title, is_array($automation_rule) ? $automation_rule['name'] : '');
25392548
} else {
2540-
$header_label = __esc('Rule Item [new rule item for %s: %s]', $title, $automation_rule['name']);
2549+
$header_label = __esc('Rule Item [new rule item for %s: %s]', $title, is_array($automation_rule) ? $automation_rule['name'] : '');
25412550

25422551
$automation_item = [];
25432552

@@ -3313,7 +3322,7 @@ function create_all_header_nodes(int $item_id, array $rule) : int {
33133322

33143323
cacti_log($function . ' Item ' . $item_id . ' - sql: ' . str_replace("\m",'',$sql) . ' matches: ' . $target, false, 'AUTOM8 TRACE', POLLER_VERBOSITY_DEBUG);
33153324

3316-
$parent_tree_item_id = create_multi_header_node($target, $rule, $tree_item, $parent_tree_item_id);
3325+
$parent_tree_item_id = (int) create_multi_header_node($target, $rule, $tree_item, $parent_tree_item_id);
33173326
}
33183327
}
33193328

@@ -3338,7 +3347,7 @@ function create_multi_header_node(string $object, array $rule, array $tree_item,
33383347
cacti_log($function . " - object: '" . $object . "', Header: '" . $tree_item['search_pattern'] . "', parent: " . $parent_tree_item_id, false, 'AUTOM8 TRACE', POLLER_VERBOSITY_HIGH);
33393348

33403349
if ($tree_item['field'] === AUTOMATION_TREE_ITEM_TYPE_STRING) {
3341-
$parent_tree_item_id = create_header_node($tree_item['search_pattern'], $rule, $tree_item, $parent_tree_item_id);
3350+
$parent_tree_item_id = (int) create_header_node($tree_item['search_pattern'], $rule, $tree_item, $parent_tree_item_id);
33423351
cacti_log($function . " called - object: '" . $object . "', Header: '" . $tree_item['search_pattern'] . "', hooked at: " . $parent_tree_item_id, false, 'AUTOM8 TRACE', POLLER_VERBOSITY_HIGH);
33433352
} else {
33443353
$replacement = automation_string_replace($tree_item['search_pattern'], $tree_item['replace_pattern'], $object);
@@ -3347,7 +3356,7 @@ function create_multi_header_node(string $object, array $rule, array $tree_item,
33473356

33483357
for ($j = 0; cacti_sizeof($replacement); $j++) {
33493358
$title = array_shift($replacement);
3350-
$parent_tree_item_id = create_header_node($title, $rule, $tree_item, $parent_tree_item_id);
3359+
$parent_tree_item_id = (int) create_header_node($title ?? '', $rule, $tree_item, $parent_tree_item_id);
33513360
cacti_log($function . " - object: '" . $object . "', Header: '" . $title . "', hooked at: " . $parent_tree_item_id, false, 'AUTOM8 TRACE', POLLER_VERBOSITY_HIGH);
33523361
}
33533362
}
@@ -3632,7 +3641,7 @@ function automation_add_tree(int $host_id, int $tree) : void {
36323641
* @param string $sysObject The system object identifier.
36333642
* @param string $sysName The system name.
36343643
*
3635-
* @return array|false The identified operating system.
3644+
* @return array|false The matched automation template row, or false if none found.
36363645
*/
36373646
function automation_find_os(string $sysDescr, string $sysObject, string $sysName) : array|false {
36383647
$sql_where = '';
@@ -3837,7 +3846,7 @@ function automation_get_network_info(string $range) : array|false {
38373846
if (cacti_sizeof($mask)) {
38383847
$network = automation_get_valid_ip($range_parts[0]);
38393848

3840-
if ($mask['cidr'] != 0) {
3849+
if ($network !== false && $mask['cidr'] != 0) {
38413850
$dec = ip2long($network) & ip2long($mask['subnet']);
38423851
$count = $mask['cidr'] == 32 ? 0 : $mask['count'];
38433852
$network = long2ip($dec);
@@ -4041,6 +4050,10 @@ function automation_primeIPAddressTable(int $network_id) : void {
40414050

40424051
$start = automation_calculate_start($subNet);
40434052

4053+
if ($start === false) {
4054+
continue;
4055+
}
4056+
40444057
if ($start != '' && !in_array($start, $ignore_ips, true)) {
40454058
$sql[] = "('$start', '', $network_id, '0', '0', '0')";
40464059
}
@@ -4134,7 +4147,7 @@ function automation_valid_snmp_device(array &$device) : bool {
41344147
// Community string is not used for v3
41354148
$snmp_sysObjectID = cacti_snmp_session_get($session, '.1.3.6.1.2.1.1.2.0');
41364149

4137-
if ($snmp_sysObjectID != 'U') {
4150+
if (is_string($snmp_sysObjectID) && $snmp_sysObjectID != 'U') {
41384151
$snmp_sysObjectID = str_replace('enterprises', '.1.3.6.1.4.1', $snmp_sysObjectID);
41394152
$snmp_sysObjectID = str_replace('OID: ', '', $snmp_sysObjectID);
41404153
$snmp_sysObjectID = str_replace('.iso', '.1', $snmp_sysObjectID);
@@ -4272,6 +4285,10 @@ function automation_get_dns_from_ip(string $ip, string $dns, int $timeout = 1000
42724285
// create UDP socket
42734286
$handle = @fsockopen("udp://$dns", 53);
42744287

4288+
if ($handle === false) {
4289+
return cacti_strtoupper($ip);
4290+
}
4291+
42754292
@stream_set_timeout($handle, intval(floor($timeout / 1000)), ($timeout * 1000) % 1000000);
42764293
@stream_set_blocking($handle, true);
42774294

@@ -4314,6 +4331,10 @@ function automation_get_dns_from_ip(string $ip, string $dns, int $timeout = 1000
43144331
// get segment size
43154332
$len = unpack('c', substr($response, $position));
43164333

4334+
if (!is_array($len)) {
4335+
break;
4336+
}
4337+
43174338
// null terminated string, so length 0 = finished
43184339
if ($len[1] == 0) {
43194340
// return the hostname, without the trailing '.'
@@ -4690,6 +4711,10 @@ function automation_network_export(mixed $network_ids) : array {
46904711
WHERE id = ?',
46914712
[$id]);
46924713

4714+
if (!is_array($network)) {
4715+
continue;
4716+
}
4717+
46934718
$snmp_id = $network['snmp_id'];
46944719

46954720
$network['snmp_id'] = db_fetch_cell_prepared('SELECT hash
@@ -4792,6 +4817,10 @@ function automation_device_rule_export(mixed $template_ids) : array {
47924817
WHERE id = ?',
47934818
[$id]);
47944819

4820+
if (!is_array($device)) {
4821+
continue;
4822+
}
4823+
47954824
$device['host_template'] = db_fetch_cell_prepared('SELECT hash
47964825
FROM host_template
47974826
WHERE id = ?',
@@ -4965,6 +4994,10 @@ function automation_device_rule_export(mixed $template_ids) : array {
49654994
WHERE id = ?',
49664995
[$r['thold_template_id']]);
49674996

4997+
if (!is_array($tt_details)) {
4998+
continue;
4999+
}
5000+
49685001
$data_source_hash = db_fetch_cell_prepared('SELECT hash
49695002
FROM data_template_rrd
49705003
WHERE id = ?',
@@ -5146,6 +5179,10 @@ function automation_graph_rule_export(mixed $graph_rule_ids) : array {
51465179
WHERE id = ?',
51475180
[$rule_id]);
51485181

5182+
if (!is_array($graph_rule)) {
5183+
continue;
5184+
}
5185+
51495186
$graph_rule['snmp_query_id'] = db_fetch_cell_prepared('SELECT hash
51505187
FROM snmp_query
51515188
WHERE id = ?',
@@ -5410,7 +5447,13 @@ function automation_validate_upload() : array|bool {
54105447
return false;
54115448
}
54125449

5413-
return json_decode(file_get_contents($_FILES['import_file']['tmp_name']), true);
5450+
$content = file_get_contents($_FILES['import_file']['tmp_name']);
5451+
5452+
if ($content === false) {
5453+
return false;
5454+
}
5455+
5456+
return json_decode($content, true);
54145457
}
54155458

54165459
raise_message('nfu2', __('No file uploaded.'), MESSAGE_LEVEL_ERROR);
@@ -5514,7 +5557,7 @@ function automation_network_import(mixed $json_data) : array {
55145557
* all the way up the rules. So, in this case we will import the snmp_options first
55155558
* followed by the network's.
55165559
*/
5517-
if (is_array($json_data) && cacti_sizeof($json_data) && isset($json_data['network'])) {
5560+
if (cacti_sizeof($json_data) && isset($json_data['network'])) {
55185561
$error = false;
55195562
$save = [];
55205563

@@ -5556,7 +5599,7 @@ function automation_network_import(mixed $json_data) : array {
55565599
WHERE hash = ?',
55575600
[$coldata]);
55585601

5559-
if (!cacti_sizeof($save['host_template'])) {
5602+
if (!cacti_sizeof($template)) {
55605603
$debug_data['errors'][] = __('The Device Template related to the Network Rule is not loaded in this Cacti System. Please edit this Network and set the Device Template, or Import the Device Template and then re-import this Automation Rule.');
55615604

55625605
$error = true;
@@ -5621,7 +5664,7 @@ function automation_graph_rule_import(mixed $json_data) : array {
56215664
* Once we have verified all the hashes to id's we will commence with the import from
56225665
* top to bottom in the JSON array.
56235666
*/
5624-
if (is_array($json_data) && cacti_sizeof($json_data) && isset($json_data['graph_rules'])) {
5667+
if (cacti_sizeof($json_data) && isset($json_data['graph_rules'])) {
56255668
$error = false;
56265669
$save = [];
56275670
$sqids = [];
@@ -5776,7 +5819,7 @@ function automation_tree_rule_import(mixed $json_data, bool $tree_branches = fal
57765819
* Once we have verified all the hashes to id's we will commence with the import from
57775820
* top to bottom in the JSON array.
57785821
*/
5779-
if (is_array($json_data) && cacti_sizeof($json_data) && isset($json_data['tree_rules'])) {
5822+
if (cacti_sizeof($json_data) && isset($json_data['tree_rules'])) {
57805823
$error = false;
57815824
$save = [];
57825825

@@ -5909,7 +5952,7 @@ function automation_template_import(mixed $json_data, bool $tree_branches = fals
59095952
* Once we have verified all the hashes to id's we will commence with the import from
59105953
* top to bottom in the JSON array.
59115954
*/
5912-
if (is_array($json_data) && cacti_sizeof($json_data) && isset($json_data['device'])) {
5955+
if (cacti_sizeof($json_data) && isset($json_data['device'])) {
59135956
$error = false;
59145957
$save = [];
59155958
$htids = [];

0 commit comments

Comments
 (0)