mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
* PHPStan Level 8 - Phase 1 Conversion
Reduces the real (no-baseline) PHPStan level 8 error count from ~2,724 to 425
and regenerates phpstan-baseline.neon accordingly (8,941 -> 1,921 lines).
Core type-safety changes
- lib/database.php: narrow db_fetch_row()/db_fetch_row_prepared() to return
array (was bool|array) and db_fetch_assoc()/db_fetch_assoc_prepared() to
return array (was mixed); the _prepared workers coerce the connection-failure
path to an empty array so the declared type is honest. empty()/cacti_sizeof()
checks are unaffected.
- lib/database.php: narrow $db_conn guards from is_object() to instanceof PDO in
the transaction/quote/insert-id helpers so the PDO calls resolve.
- lib/mib_cache.php: the fluent MibCache methods mib()/object()/table()/row()
now return self instead of a bare object so chained calls type-check.
Type-safety casts and guards (mechanical, behaviour-preserving)
- Cast string|false/|null builtin results feeding string parameters
(json_decode, base64_encode, trim, htmle, preg_replace, explode, etc.).
- Cast int|string / int|false values feeding int id/pid parameters.
- Guard resource|false fopen()/popen() handles before fwrite/fclose/fread, and
wrap foreach over array|false/list|false results with `?: []`.
Genuine bugs found and fixed
- cli/add_tree.php / lib/api_tree.php: site tree nodes could not be created via
the CLI - the --site-id option was never parsed, $nodeTypes had no 'site'
entry (passing null to api_tree_item_save(int $type) -> TypeError), and
api_tree_item_save() never persisted site_id. All three fixed.
- tree.php: leaves_exist() is typed : int but returned db_fetch_assoc_prepared()
(an array of rows) for a SELECT COUNT(*); now uses db_fetch_cell_prepared()
and returns the scalar count.
- lib/mib_cache.php: $entries_per_object was a float (division / ceil) used as
an array key (PHP 8.1 implicit float-to-int key deprecation); now cast to int.
Config
- .phpstan.neon: add notIdentical.alwaysTrue and booleanAnd.alwaysFalse to the
existing always-true/false ignore family (same "PHPStan gets confused"
dynamic-constant cause already covered by identical.alwaysTrue,
notEqual.alwaysTrue, booleanAnd.leftAlwaysTrue, etc.).
- phpstan-baseline.neon: regenerated; the remaining 425 entries are the
untouched long tail for Phase 2.
* Remove redundant @phpstan-ignore comments
Follow-up cleanup for the level 8 Phase 1 work. Removes 14 inline
@phpstan-ignore comments that no longer suppress any reported error:
- cli/repair_database.php: `@phpstan-ignore booleanAnd.alwaysFalse` is now
covered by the global booleanAnd.alwaysFalse ignore added to .phpstan.neon.
- 13 others (host.php, include/global.php, include/session.php,
lib/api_automation.php x2, lib/functions.php x2, lib/html_utility.php x2,
lib/installer.php, lib/package.php, lib/reports.php, lib/rrd.php) no longer
match any reported error after the type-safety fixes (e.g. the
db_fetch_row_prepared() return-type narrowing removed the offset-assign
errors the api_automation ignores were suppressing).
Verified with reportUnmatchedIgnoredErrors enabled: 0 unmatched ignores
remain and the full level 8 run still reports 0 errors.
* ci: satisfy validator ratchet and refresh security sink baselines
- lib/api_tree.php: give the new tree-item site_id a real numeric
constraint ('^[-0-9]+$', matching html_reports.php) instead of an
empty regex, so the validator constraint ratchet passes without
bumping its baseline.
- tests/security/baselines: refresh the three sink/hotspot signatures
whose code text changed from the (string) casts added in this branch
(lib/poller.php setsid, cli/rrdresize.php json_encode, lib/import.php
simplexml_load_string).
* ci(js): allow DOMPurify 3.4.16 and update sync_js pin test
The dependabot bump (#8134) moved package.json/package-lock.json to
DOMPurify 3.4.16 on develop but left tests/js/sync_js.test.mjs pinned to
3.4.15, so the PR merge commit fails the 'DOMPurify is pinned' contract.
Bring package.json/package-lock.json to 3.4.16 (matching develop) and
update the pin test to expect 3.4.16.
* test: update sql.php null-handling assertion for the (string) cast
This branch changed scripts/sql.php from trim(\ ?? '') to
trim((string) \) (the phpstan-safe form that coerces both null and
false from shell_exec()). Update SqlScriptsTest's null-return assertion
to match the new cast instead of the removed '?? ''.
* fix: address Copilot review - ownership IDs, fopen guards, mib_cache bound
- chown()/chgrp(): pass integer UID/GID from fileowner()/filegroup()
(guarding the false lookup) instead of string-casting them, which made
them resolve account/group names like '1001' and fail. Covers
lib/api_device.php, lib/boost.php and poller_maintenance.php (x2).
- fopen() failure now short-circuits instead of reporting success:
lib/package.php returns false, lib/clog_webapi.php and utilities.php
render an error, and snmpagent_mibcachechild.php skips the cache write
and sentinel unlink so a concurrent writer's lock is preserved.
- lib/mib_cache.php: keep the float quotient as the loop bound and cast
only the array offset, so a non-even entries/columns split no longer
truncates the loop and drops a cached row.
* revert: keep db_fetch_* returning false on error; baseline Level 8 instead
Per review, restoring the intentional fail-safe: db_fetch_row[_prepared]()
return bool|array and db_fetch_assoc[_prepared]() return mixed again, so a
genuine SQL error (not just a missing connection) is signalled as false and
the === false checks in cdef_is_in_use(), boost_process_* and aggregate.php
work as before. Dropped the is_array() ? : [] coercion.
Level 8 stays green by regenerating phpstan-baseline.neon to capture the
re-introduced bool|array/mixed call-site errors (the ~1,850 the narrowing
had eliminated). Tightening those callers to drop them from the baseline is
Phase 2 work.
---------
Co-authored-by: TheWitness <copilot@users.noreply.github.com>
1200 lines
40 KiB
PHP
1200 lines
40 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
| This code is designed, written, and maintained by the Cacti Group. See |
|
|
| about.php and/or the AUTHORS file for specific developer information. |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* Builds a URL for the children graphs of an aggregate graph.
|
|
*
|
|
* This function constructs a URL that links to the children graphs of a specified aggregate graph.
|
|
* It first prunes the graphs associated with the given local graph ID, then fetches the aggregate
|
|
* graph data and its associated child graph IDs. If child graph IDs are found, it constructs a URL
|
|
* with the appropriate query parameters and returns it as a hyperlink.
|
|
*
|
|
* @param int $local_graph_id The ID of the local graph.
|
|
* @param int $graph_start Optional. The start time for the graph. Default is -1.
|
|
* @param int $graph_end Optional. The end time for the graph. Default is -1.
|
|
* @param int $rra_id Optional. The RRA ID for the graph. Default is -1.
|
|
*
|
|
* @return string The constructed URL as a hyperlink.
|
|
*/
|
|
function aggregate_build_children_url(int $local_graph_id, int $graph_start = -1, int $graph_end = -1, int $rra_id = -1) : string {
|
|
aggregate_prune_graphs($local_graph_id);
|
|
|
|
$aggregate_data = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (cacti_sizeof($aggregate_data)) {
|
|
$local_graph_ids = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT local_graph_id
|
|
FROM aggregate_graphs_items
|
|
WHERE aggregate_graph_id = ?',
|
|
[$aggregate_data['id']]
|
|
), 'local_graph_id', 'local_graph_id'
|
|
);
|
|
|
|
if (cacti_sizeof($local_graph_ids)) {
|
|
$graph_select = 'graph_add=';
|
|
|
|
foreach ($local_graph_ids as $graph) {
|
|
$graph_select .= $graph . '%2C';
|
|
}
|
|
|
|
return "<a class='hyperLink aggregates' href='" . htmle(CACTI_PATH_URL . 'graph_view.php?reset=1&page=1&graph_template_id=-1&host_id=-1&filter=&style=selective&action=preview' . ($graph_start >= 0 ? '&graph_start=' . $graph_start : '') . ($graph_end >= 0 ? '&graph_end=' . $graph_end : '') . ($rra_id >= 0 ? '&rra_id=' . $rra_id : '') . '&' . $graph_select) . "'><i class='drillDown ti ti-sitemap-filled expandAggregate' title='" . __esc('Display Graphs from this Aggregate') . "'></i></a><br>" . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Converts a list of graphs to use an aggregate template.
|
|
*
|
|
* This function takes an array of graph IDs and applies an aggregate template to each graph.
|
|
* It saves the new aggregate graph configuration to the database and updates the aggregate graph items.
|
|
*
|
|
* @param array $graphs An array of graph IDs to be converted.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_aggregate_convert_template(array $graphs) : void {
|
|
$aggregate_template_id = gfrv('aggregate_template_id');
|
|
$aggregate_template = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graph_templates
|
|
WHERE id = ?',
|
|
[$aggregate_template_id]);
|
|
|
|
// without a valid template the saves below would write null template fields
|
|
if (!cacti_sizeof($aggregate_template)) {
|
|
raise_message('aggregate_template_error', __('The selected Aggregate Template was not found! No graphs were migrated.'), MESSAGE_LEVEL_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
foreach ($graphs as $graph) {
|
|
$save = [];
|
|
$save['id'] = '';
|
|
$save['local_graph_id'] = $graph;
|
|
$save['aggregate_template_id'] = $aggregate_template_id;
|
|
$save['template_propogation'] = 'on';
|
|
$save['title_format'] = db_fetch_cell_prepared('SELECT title_cache FROM graph_templates_graph WHERE local_graph_id = ?', [$graph]);
|
|
$save['graph_template_id'] = $aggregate_template['graph_template_id'];
|
|
$save['gprint_prefix'] = $aggregate_template['gprint_prefix'];
|
|
$save['graph_type'] = $aggregate_template['graph_type'];
|
|
$save['total'] = $aggregate_template['total'];
|
|
$save['total_type'] = $aggregate_template['total_type'];
|
|
$save['total_prefix'] = $aggregate_template['total_prefix'];
|
|
$save['order_type'] = $aggregate_template['order_type'];
|
|
|
|
$id = sql_save($save, 'aggregate_graphs');
|
|
|
|
$task_items = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT DISTINCT task_item_id
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
ORDER BY sequence',
|
|
[$graph]
|
|
), 'task_item_id', 'task_item_id'
|
|
);
|
|
|
|
$task_items = implode(',', $task_items);
|
|
$member_graphs = array_rekey(db_fetch_assoc("SELECT DISTINCT local_graph_id
|
|
FROM graph_templates_item
|
|
WHERE task_item_id IN ($task_items)
|
|
AND graph_template_id>0"), 'local_graph_id', 'local_graph_id');
|
|
|
|
$sequence = 1;
|
|
|
|
foreach ($member_graphs as $mg) {
|
|
db_execute_prepared('REPLACE INTO aggregate_graphs_items
|
|
(aggregate_graph_id, local_graph_id, sequence)
|
|
VALUES (?, ?, ?)',
|
|
[$id, $mg, $sequence]);
|
|
$sequence++;
|
|
}
|
|
|
|
if (!push_out_aggregates($aggregate_template_id, $graph)) {
|
|
cacti_log('ERROR: Failed to rebuild aggregate graph after template conversion.', false, 'AGGREGATE');
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Save the last time a aggregate was altered/updated
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_aggregate_graph', time());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Associates a list of graphs with a local graph in the aggregate graphs.
|
|
*
|
|
* This function retrieves the aggregate template and aggregate ID for the given local graph ID.
|
|
* If an aggregate ID is found, it determines the maximum sequence number for the aggregate graph items.
|
|
* It then iterates over the provided graphs and inserts or replaces each graph into the aggregate graph items
|
|
* with the appropriate sequence number. Finally, it pushes out the aggregates using the aggregate template and local graph ID.
|
|
*
|
|
* @param int $local_graph_id The ID of the local graph to associate with.
|
|
* @param array $graphs An array of graph IDs to associate with the local graph.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_aggregate_associate(int $local_graph_id, array $graphs) : void {
|
|
$aggregate_template = db_fetch_cell_prepared('SELECT aggregate_template_id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$aggregate_id = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (!empty($aggregate_id)) {
|
|
$max_sequence = db_fetch_cell_prepared('SELECT MAX(sequence)
|
|
FROM aggregate_graphs_items
|
|
WHERE aggregate_graph_id = ?',
|
|
[$aggregate_id]);
|
|
|
|
if ($max_sequence == '') {
|
|
$max_sequence = 1;
|
|
}
|
|
|
|
foreach ($graphs as $graph) {
|
|
db_execute_prepared('REPLACE INTO aggregate_graphs_items
|
|
(aggregate_graph_id, local_graph_id, sequence)
|
|
VALUES (?, ?, ?)',
|
|
[$aggregate_id, $graph, $max_sequence]);
|
|
|
|
$max_sequence++;
|
|
}
|
|
|
|
if (!push_out_aggregates($aggregate_template, $local_graph_id)) {
|
|
cacti_log('ERROR: Failed to rebuild aggregate graph after association.', false, 'AGGREGATE');
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Save the last time a aggregate was altered/updated
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_aggregate_graph', time());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disassociates a list of graphs from an aggregate graph.
|
|
*
|
|
* This function removes the association between a specified local graph and a list of graphs
|
|
* from the aggregate graph. It first retrieves the aggregate template ID and aggregate ID
|
|
* for the given local graph ID. If an aggregate ID is found, it iterates through the list of
|
|
* graphs and deletes each association from the `aggregate_graphs_items` table. Finally, it
|
|
* calls the `push_out_aggregates` function to update the aggregate template.
|
|
*
|
|
* @param int $local_graph_id The ID of the local graph to disassociate.
|
|
* @param array $graphs An array of graph IDs to be disassociated from the aggregate graph.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_aggregate_disassociate(int $local_graph_id, array $graphs) : void {
|
|
$aggregate_template = db_fetch_cell_prepared('SELECT aggregate_template_id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$aggregate_id = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (!empty($aggregate_id)) {
|
|
foreach ($graphs as $graph) {
|
|
db_execute_prepared('DELETE FROM aggregate_graphs_items
|
|
WHERE aggregate_graph_id = ?
|
|
AND local_graph_id = ?',
|
|
[$aggregate_id, $graph]);
|
|
}
|
|
|
|
if (!push_out_aggregates($aggregate_template, $local_graph_id)) {
|
|
cacti_log('ERROR: Failed to rebuild aggregate graph after disassociation.', false, 'AGGREGATE');
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Save the last time a aggregate was altered/updated
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_aggregate_graph', time());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an aggregate graph from a list of graphs.
|
|
*
|
|
* @param string $aggregate_name The name of the aggregate graph to be created.
|
|
* @param array $graphs An array of graph IDs to be included in the aggregate graph.
|
|
* @param int $agg_template_id Optional. The ID of the aggregate template to use. Default is 0.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_aggregate_create(string $aggregate_name, array $graphs, int $agg_template_id = 0) : void {
|
|
$graph_items = [];
|
|
|
|
// get the first aggregate graph
|
|
if ($agg_template_id == 0) {
|
|
$agg_template = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$graphs[0]]);
|
|
|
|
// get graph items
|
|
$graph_items = db_fetch_assoc('SELECT DISTINCT local_graph_id
|
|
FROM aggregate_graphs_items
|
|
WHERE aggregate_graph_id IN(
|
|
SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE ' . array_to_sql_or($graphs, 'local_graph_id') . ')');
|
|
} else {
|
|
$agg_template = db_fetch_row_prepared('SELECT *
|
|
FROM aggregate_graph_templates
|
|
WHERE id = ?',
|
|
[$agg_template_id]);
|
|
|
|
// unset when dealing with a template
|
|
unset($agg_template['name']);
|
|
|
|
$agg_template['aggregate_template_id'] = $agg_template_id;
|
|
$agg_template['template_propogation'] = 'on';
|
|
|
|
// get graph items
|
|
foreach ($graphs as $graph) {
|
|
$graph_items[]['local_graph_id'] = $graph;
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($agg_template)) {
|
|
// create new graph in cacti tables
|
|
$graph_template_graph = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE local_graph_id = ?',
|
|
[$graphs[0]]);
|
|
|
|
$graph_template_id = $graph_template_graph['graph_template_id'];
|
|
|
|
$local_graph_id = aggregate_graph_save(0, $graph_template_id, $aggregate_name, $agg_template_id);
|
|
|
|
// create new graph in aggregate table
|
|
$save = [];
|
|
$save = $agg_template;
|
|
$save['id'] = 0;
|
|
$save['local_graph_id'] = $local_graph_id;
|
|
$save['title_format'] = $aggregate_name;
|
|
|
|
$agg_id = sql_save($save, 'aggregate_graphs');
|
|
|
|
if (cacti_sizeof($graph_items)) {
|
|
$aggs = 1;
|
|
$sql = '';
|
|
|
|
foreach ($graph_items as $i) {
|
|
$sql .= ($aggs > 1 ? ',' : '') . "($agg_id, " . $i['local_graph_id'] . ", $aggs)";
|
|
$aggs++;
|
|
}
|
|
|
|
db_execute("INSERT INTO aggregate_graphs_items
|
|
(aggregate_graph_id, local_graph_id, sequence) VALUES $sql");
|
|
}
|
|
|
|
// update title cache
|
|
if (!empty($local_graph_id)) {
|
|
update_graph_title_cache($local_graph_id);
|
|
}
|
|
|
|
if (!push_out_aggregates($agg_template['aggregate_template_id'], $local_graph_id)) {
|
|
cacti_log('ERROR: Failed to rebuild newly created aggregate graph.', false, 'AGGREGATE');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* aggregate_error_handler - PHP error handler
|
|
*
|
|
* @param int $errno error id
|
|
* @param string $errmsg error message
|
|
* @param string $filename file name
|
|
* @param int $linenum line of error
|
|
* @param array $vars additional variables
|
|
*
|
|
* @return bool
|
|
*/
|
|
function aggregate_error_handler(int $errno, string $errmsg, string $filename, int $linenum, array $vars = []) : bool {
|
|
$errno &= error_reporting();
|
|
|
|
// return if error handling disabled by @
|
|
if ($errno == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (error_reporting() == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (read_config_option('log_verbosity') >= POLLER_VERBOSITY_DEBUG) {
|
|
// define all error types
|
|
$errortype = [
|
|
E_ERROR => 'Error',
|
|
E_WARNING => 'Warning',
|
|
E_PARSE => 'Parsing Error',
|
|
E_NOTICE => 'Notice',
|
|
E_CORE_ERROR => 'Core Error',
|
|
E_CORE_WARNING => 'Core Warning',
|
|
E_COMPILE_ERROR => 'Compile Error',
|
|
E_COMPILE_WARNING => 'Compile Warning',
|
|
E_USER_ERROR => 'User Error',
|
|
E_USER_WARNING => 'User Warning',
|
|
E_USER_NOTICE => 'User Notice',
|
|
E_RECOVERABLE_ERROR => 'Catchable Fatal Error'
|
|
];
|
|
|
|
// create an error string for the log
|
|
$err = "ERRNO:'" . $errno . "' TYPE:'" . $errortype[$errno] .
|
|
"' MESSAGE:'" . $errmsg . "' IN FILE:'" . $filename .
|
|
"' LINE NO:'" . $linenum . "'";
|
|
|
|
// let's ignore some lesser issues
|
|
if (substr_count($errmsg, 'date_default_timezone')) {
|
|
return true;
|
|
}
|
|
|
|
if (substr_count($errmsg, 'Only variables')) {
|
|
return true;
|
|
}
|
|
|
|
// log the error to the Cacti log
|
|
cacti_log('PROGERR: ' . $err, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
print('PROGERR: ' . $err . '<br><pre>');
|
|
|
|
// backtrace, if available
|
|
cacti_debug_backtrace('AGGREGATE', true);
|
|
|
|
if (isset($GLOBALS['error_fatal'])) {
|
|
if ($GLOBALS['error_fatal'] & $errno) {
|
|
die("Fatal error $errno" . PHP_EOL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_next_sequence - returns the next available sequence id
|
|
*
|
|
* @param int $id The current id
|
|
* @param string $field The field name that contains the target id
|
|
* @param string $table_name The table name that contains the target id
|
|
* @param string $group_query An SQL 'where' clause to limit the query
|
|
* @param string $key_field
|
|
*
|
|
* @return int The next available sequence id
|
|
*/
|
|
function get_next_sequence(int $id, string $field, string $table_name, string $group_query, string $key_field = 'id') : int {
|
|
cacti_log(__FUNCTION__ . ' called. Id: ' . $id . ' field: ' . $field . ' table: ' . $table_name, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
if (empty($id)) {
|
|
$data = db_fetch_row("SELECT max($field)+1 AS seq FROM $table_name WHERE $group_query");
|
|
|
|
if ($data['seq'] == '') {
|
|
return 1;
|
|
} else {
|
|
return $data['seq'];
|
|
}
|
|
} else {
|
|
$data = db_fetch_row("SELECT $field FROM $table_name WHERE $key_field = id");
|
|
|
|
return (int) $data[$field];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* find out, if this is a pure STACKed graph
|
|
*
|
|
* @param int $_local_graph_id graph to be examined
|
|
*
|
|
* @return bool true, if pure STACKed graph
|
|
*/
|
|
function aggregate_is_pure_stacked_graph(int $_local_graph_id) : bool {
|
|
cacti_log(__FUNCTION__ . ' local_graph: ' . $_local_graph_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
$_pure_stacked_graph = false;
|
|
|
|
if (!empty($_local_graph_id)) {
|
|
// fetch all AREA graph items
|
|
$_count = db_fetch_cell_prepared('SELECT COUNT(id)
|
|
FROM graph_templates_item
|
|
WHERE graph_templates_item.local_graph_id = ?
|
|
AND graph_templates_item.graph_type_id IN (?, ?, ?, ?)',
|
|
[$_local_graph_id, GRAPH_ITEM_TYPE_AREA, GRAPH_ITEM_TYPE_LINE1, GRAPH_ITEM_TYPE_LINE2, GRAPH_ITEM_TYPE_LINE3]);
|
|
|
|
cacti_log(__FUNCTION__ . ' #AREA/LINEx items: ' . $_count, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
/* if there's at least one AREA item, this is NOT a pure LINEx graph
|
|
* if there's NO AREA item, this IS a pure LINEx graph
|
|
* in case we find STACKs, there must be at least one AREA as well, or the graph itself is malformed
|
|
* this would fail on a PURE GPRINT/HRULE/VRULE graph as well, but that is malformed, too
|
|
*/
|
|
$_pure_stacked_graph = ($_count == 0);
|
|
}
|
|
|
|
return $_pure_stacked_graph;
|
|
}
|
|
|
|
/**
|
|
* find out, if graph has a STACK
|
|
*
|
|
* @param int $_local_graph_id - graph to be examined
|
|
*
|
|
* @return bool - true, if pure STACKed graph
|
|
*/
|
|
function aggregate_is_stacked_graph(int $_local_graph_id) : bool {
|
|
cacti_log(__FUNCTION__ . ' local_graph: ' . $_local_graph_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
$_pure_stacked_graph = false;
|
|
$_stacked_graph = false;
|
|
|
|
if (!empty($_local_graph_id)) {
|
|
// fetch all AREA graph items
|
|
$_count = db_fetch_cell_prepared('SELECT COUNT(id)
|
|
FROM graph_templates_item
|
|
WHERE graph_templates_item.local_graph_id = ?
|
|
AND graph_templates_item.graph_type_id = ?',
|
|
[$_local_graph_id, GRAPH_ITEM_TYPE_STACK]);
|
|
|
|
cacti_log(__FUNCTION__ . ' #AREA/LINEx items: ' . $_count, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
/* if there's at least one AREA item, this is NOT a pure LINEx graph
|
|
* if there's NO AREA item, this IS a pure LINEx graph
|
|
* in case we find STACKs, there must be at least one AREA as well, or the graph itself is malformed
|
|
* this would fail on a PURE GPRINT/HRULE/VRULE graph as well, but that is malformed, too
|
|
*/
|
|
$_stacked_graph = ($_count > 0);
|
|
}
|
|
|
|
return $_stacked_graph;
|
|
}
|
|
|
|
/**
|
|
* Converts the graph type of a specific graph item from an old type to a new type.
|
|
*
|
|
* @param int $_graph_id The ID of the graph whose item type is to be converted.
|
|
* @param int $_old_type The current type of the graph item.
|
|
* @param int $_new_type The new type to which the graph item should be converted.
|
|
*
|
|
* @return void
|
|
*/
|
|
function aggregate_conditional_convert_graph_type(int $_graph_id, int $_old_type, int $_new_type) : void {
|
|
cacti_log(__FUNCTION__ . ' called: graph: ' . $_graph_id . ' old item type: ' . $_old_type . ' new item type: ' . $_new_type, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
if (!empty($_graph_id) && !empty($_old_type)) {
|
|
// fetch the first item of requested graph_type
|
|
$_graph_item_id = db_fetch_cell_prepared('SELECT id
|
|
FROM graph_templates_item AS gti
|
|
WHERE gti.local_graph_id = ?
|
|
AND gti.graph_type_id = ?
|
|
ORDER BY sequence
|
|
LIMIT 1',
|
|
[$_graph_id, $_old_type]);
|
|
|
|
// and update it to the new graph_type
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET graph_templates_item.graph_type_id = ?
|
|
WHERE graph_templates_item.id = ?',
|
|
[$_new_type, $_graph_item_id]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the graph type of an aggregate graph item based on the provided old and new graph types.
|
|
*
|
|
* @param int $graph_index The index of the graph item within the aggregate.
|
|
* @param int $old_graph_type The current graph type of the item.
|
|
* @param int $new_graph_type The desired new graph type for the item.
|
|
*
|
|
* @return int The resulting graph type after applying the change.
|
|
*/
|
|
function aggregate_change_graph_type(int $graph_index, int $old_graph_type, int $new_graph_type) : int {
|
|
cacti_log(__FUNCTION__ . ' called. Index ' . $graph_index . ' old type ' . $old_graph_type . ' Graph Type: ' . $new_graph_type, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
/* LEGEND entries and xRULEs stay unchanged
|
|
* xRULEs honestly do not make much sense on an aggregated graph, though */
|
|
switch ($old_graph_type) {
|
|
case GRAPH_ITEM_TYPE_GPRINT:
|
|
case GRAPH_ITEM_TYPE_GPRINT_LAST:
|
|
case GRAPH_ITEM_TYPE_GPRINT_MIN:
|
|
case GRAPH_ITEM_TYPE_GPRINT_MAX:
|
|
case GRAPH_ITEM_TYPE_GPRINT_AVERAGE:
|
|
case GRAPH_ITEM_TYPE_TEXTALIGN:
|
|
case GRAPH_ITEM_TYPE_TIC:
|
|
case GRAPH_ITEM_TYPE_COMMENT:
|
|
case GRAPH_ITEM_TYPE_HRULE:
|
|
case GRAPH_ITEM_TYPE_VRULE:
|
|
return $old_graph_type;
|
|
}
|
|
|
|
// this item is eligible to a type change
|
|
switch ($new_graph_type) {
|
|
case AGGREGATE_GRAPH_TYPE_KEEP:
|
|
// keep entry as defined by the Graph
|
|
return $old_graph_type;
|
|
case GRAPH_ITEM_TYPE_STACK:
|
|
/* create an AREA/STACK graph
|
|
* pay attention to AREA handling!
|
|
* any AREA/STACKed graph needs a base AREA entry
|
|
* but e.g. a graph that prints on both negative and positive y-axis may hold two AREAs
|
|
* so it's a good idea to keep all AREA entries of the first aggregated elementary graph (index 0)*/
|
|
if ($graph_index == 0 &&
|
|
($old_graph_type == GRAPH_ITEM_TYPE_STACK ||
|
|
$old_graph_type == GRAPH_ITEM_TYPE_LINESTACK ||
|
|
$old_graph_type == GRAPH_ITEM_TYPE_LINE1 ||
|
|
$old_graph_type == GRAPH_ITEM_TYPE_LINE2 ||
|
|
$old_graph_type == GRAPH_ITEM_TYPE_LINE3)) {
|
|
// if the graph type is a stack and the item is 1, it must be converted to area
|
|
return GRAPH_ITEM_TYPE_AREA;
|
|
}
|
|
|
|
if ($graph_index > 0 && $old_graph_type == GRAPH_ITEM_TYPE_AREA) {
|
|
// if the graph type is a stack and the item is 1, it must be converted to area
|
|
return GRAPH_ITEM_TYPE_STACK;
|
|
}
|
|
|
|
if ($graph_index == 0 && $old_graph_type == GRAPH_ITEM_TYPE_AREA) {
|
|
// don't change (multi-)AREAs on the first graph
|
|
return $old_graph_type;
|
|
} else {
|
|
/* this is either
|
|
* - not the first graph, any item type: convert to STACK
|
|
* - the first graph and a LINEx item: convert to STACK
|
|
*/
|
|
return GRAPH_ITEM_TYPE_STACK;
|
|
}
|
|
|
|
/* for a pure LINEx graph,
|
|
* this will result in a pure STACKed graph, without any AREA
|
|
* we will take care of this at the very end, after adding the last graph to the aggregate, during post-processing
|
|
*/
|
|
case AGGREGATE_GRAPH_TYPE_KEEP_STACKED:
|
|
// Like GRAPH_ITEM_TYPE_STACK but don't convert first item to AREA
|
|
if ($graph_index == 0) {
|
|
if ($old_graph_type == GRAPH_ITEM_TYPE_STACK) {
|
|
return GRAPH_ITEM_TYPE_AREA;
|
|
}
|
|
|
|
return $old_graph_type;
|
|
} else {
|
|
return GRAPH_ITEM_TYPE_STACK;
|
|
}
|
|
case AGGREGATE_GRAPH_TYPE_LINE1_STACK:
|
|
if ($graph_index == 0) {
|
|
return GRAPH_ITEM_TYPE_LINE1;
|
|
} else {
|
|
return GRAPH_ITEM_TYPE_LINESTACK;
|
|
}
|
|
case AGGREGATE_GRAPH_TYPE_LINE2_STACK:
|
|
if ($graph_index == 0) {
|
|
return GRAPH_ITEM_TYPE_LINE2;
|
|
} else {
|
|
return GRAPH_ITEM_TYPE_LINESTACK;
|
|
}
|
|
case AGGREGATE_GRAPH_TYPE_LINE3_STACK:
|
|
if ($graph_index == 0) {
|
|
return GRAPH_ITEM_TYPE_LINE3;
|
|
} else {
|
|
return GRAPH_ITEM_TYPE_LINESTACK;
|
|
}
|
|
case GRAPH_ITEM_TYPE_LINE1:
|
|
case GRAPH_ITEM_TYPE_LINE2:
|
|
case GRAPH_ITEM_TYPE_LINE3:
|
|
case GRAPH_ITEM_TYPE_LINESTACK:
|
|
default:
|
|
return $new_graph_type;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* duplicate_color_template - duplicate color template
|
|
*
|
|
* @param int $_color_template_id - id of the base color template
|
|
* @param string $color_template_title - title of the duplicated color template
|
|
*
|
|
* @return void
|
|
*/
|
|
function duplicate_color_template(int $_color_template_id, string $color_template_title) : void {
|
|
cacti_log(__FUNCTION__ . ' called. Color Template Id: ' . $_color_template_id . ' Title: ' . $color_template_title, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// fetch data from table color_templates
|
|
$color_template = db_fetch_row_prepared('SELECT *
|
|
FROM color_templates
|
|
WHERE color_template_id = ?',
|
|
[$_color_template_id]);
|
|
|
|
// fetch data from table color_template_items
|
|
$color_template_items = db_fetch_assoc_prepared('SELECT *
|
|
FROM color_template_items
|
|
WHERE color_template_id = ?',
|
|
[$_color_template_id]);
|
|
|
|
$save = [];
|
|
|
|
// create new entry: color_templates
|
|
$save['color_template_id'] = 0;
|
|
|
|
// substitute the title variable
|
|
$save['name'] = str_replace('<template_title>', $color_template['name'], $color_template_title);
|
|
|
|
cacti_log(__FUNCTION__ . ' called. Id:' . $_color_template_id . ' Title: ' . $color_template_title . ' Replaced: ' . $save['name'], true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
$new_color_template_id = sql_save($save, 'color_templates', 'color_template_id');
|
|
|
|
// create new entry(s): color_template_items
|
|
if (cacti_sizeof($color_template_items)) {
|
|
foreach ($color_template_items as $color_template_item) {
|
|
$save = [];
|
|
$save['color_template_item_id'] = 0;
|
|
$save['color_template_id'] = $new_color_template_id;
|
|
$save['color_id'] = $color_template_item['color_id'];
|
|
$save['sequence'] = $color_template_item['sequence'];
|
|
|
|
cacti_log(__FUNCTION__ . ' called. Id:' . $new_color_template_id . ' Color: ' . $save['color_id'] . ' sequence: ' . $save['sequence'], true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
$new_color_template_item_id = sql_save($save, 'color_template_items', 'color_template_item_id');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* aggregate_cdef_make0 - return the id of a 'Make 0' cdef, create that cdef if necessary
|
|
*
|
|
* @return int - id of the 'Make 0' cdef
|
|
*/
|
|
function aggregate_cdef_make0() : int {
|
|
include_once(CACTI_PATH_LIBRARY . '/cdef.php');
|
|
|
|
cacti_log(__FUNCTION__ . ' called', true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// magic name of new cdef
|
|
$magic = '_MAKE 0';
|
|
|
|
// search the 'magic' cdef
|
|
$cdef_id = db_fetch_cell_prepared('SELECT id
|
|
FROM cdef
|
|
WHERE name = ?',
|
|
[$magic]);
|
|
|
|
if (isset($cdef_id) && $cdef_id > 0) {
|
|
return $cdef_id; // hoping, that nobody changed the cdef_items!
|
|
}
|
|
|
|
// create a new cdef entry
|
|
$save = [];
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_cdef(0);
|
|
$save['system'] = 1;
|
|
$save['name'] = $magic;
|
|
|
|
// save the cdef itself
|
|
$new_cdef_id = sql_save($save, 'cdef');
|
|
|
|
cacti_log(__FUNCTION__ . ' created new cdef: ' . $new_cdef_id . ' name: ' . $magic, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// create a new cdef item entry
|
|
$save = [];
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_cdef(0, 'cdef_item');
|
|
$save['cdef_id'] = $new_cdef_id;
|
|
$save['sequence'] = 1;
|
|
$save['type'] = 6; // this will be replaced by a define as soon as it exists for a pure text field
|
|
$save['value'] = 'CURRENT_DATA_SOURCE,0,*';
|
|
|
|
// save the cdef item, there's only one!
|
|
$cdef_item_id = sql_save($save, 'cdef_items');
|
|
|
|
cacti_log(__FUNCTION__ . ' created new cdef item: ' . $cdef_item_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
return $new_cdef_id;
|
|
}
|
|
|
|
/**
|
|
* Check whether an aggregate item has a usable CDEF expression.
|
|
*
|
|
* @param array<int,array{id:mixed,name:mixed,cdef_text:?string}> $cdefs
|
|
* @param int $cdef_id
|
|
*
|
|
* @return string|null A usable expression, or null when totalling must skip the item
|
|
*/
|
|
function aggregate_cdef_for_totalling(array $cdefs, int $cdef_id) : ?string {
|
|
if (!isset($cdefs[$cdef_id]) || $cdefs[$cdef_id]['cdef_text'] === null || $cdefs[$cdef_id]['cdef_text'] === '') {
|
|
return null;
|
|
}
|
|
|
|
return $cdefs[$cdef_id]['cdef_text'];
|
|
}
|
|
|
|
/**
|
|
* Validate every target before aggregate_cdef_totalling() performs any writes.
|
|
*
|
|
* @param array<int,array<string,mixed>> $graph_template_items
|
|
* @param array<int,array{id:mixed,name:mixed,cdef_text:?string}> $cdefs
|
|
*
|
|
* @return array{items:array<int,array{graph_item:array<string,mixed>,cdef_id:int,cdef_name:mixed,cdef_text:string}>,invalid_cdef_id:?int}
|
|
*/
|
|
function aggregate_prepare_cdef_totalling(array $graph_template_items, array $cdefs) : array {
|
|
$items = [];
|
|
|
|
foreach ($graph_template_items as $graph_template_item) {
|
|
$cdef_id = (int) $graph_template_item['cdef_id'];
|
|
$cdef_text = aggregate_cdef_for_totalling($cdefs, $cdef_id);
|
|
|
|
if ($cdef_text === null) {
|
|
return ['items' => [], 'invalid_cdef_id' => $cdef_id];
|
|
}
|
|
|
|
$items[] = [
|
|
'graph_item' => $graph_template_item,
|
|
'cdef_id' => $cdef_id,
|
|
'cdef_name' => $cdefs[$cdef_id]['name'],
|
|
'cdef_text' => $cdef_text,
|
|
];
|
|
}
|
|
|
|
return ['items' => $items, 'invalid_cdef_id' => null];
|
|
}
|
|
|
|
/**
|
|
* Creates and applies aggregate totalling CDEFs.
|
|
*
|
|
* @param int $_new_graph_id ID of the new graph.
|
|
* @param int $_graph_item_sequence First graph item sequence to total.
|
|
* @param int $_total_type Type of totalling required.
|
|
*
|
|
* @return bool True when every totalling CDEF was applied.
|
|
*/
|
|
function aggregate_cdef_totalling(int $_new_graph_id, int $_graph_item_sequence, int $_total_type) : bool {
|
|
include_once(CACTI_PATH_LIBRARY . '/cdef.php');
|
|
|
|
cacti_log(__FUNCTION__ . ' called. Working on Graph: ' . $_new_graph_id . ' sequence: ' . $_graph_item_sequence . ' totalling: ' . $_total_type, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
$graph_template_items = [];
|
|
|
|
// take graph item data for the totalling items
|
|
if (!empty($_new_graph_id)) {
|
|
/* the totalling cdef must be applied to every item from this sequence on,
|
|
including the GPRINT legend items, or the legend prints untotalled
|
|
single data source values (see #7216) */
|
|
$graph_template_items = db_fetch_assoc_prepared('SELECT id, cdef_id
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
AND sequence >= ?
|
|
ORDER BY sequence',
|
|
[$_new_graph_id, $_graph_item_sequence]);
|
|
|
|
if ($graph_template_items === false) {
|
|
cacti_log(__FUNCTION__ . ' could not load graph items.', true, 'AGGREGATE');
|
|
|
|
return false;
|
|
}
|
|
|
|
cacti_log(__FUNCTION__ . " totalling query: graph=$_new_graph_id seq=$_graph_item_sequence", true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
|
|
// now get the list of cdefs
|
|
$_cdefs = db_fetch_assoc_prepared('SELECT id, name FROM cdef ORDER BY id', []);
|
|
$cdefs = [];
|
|
|
|
if ($_cdefs === false) {
|
|
cacti_log(__FUNCTION__ . ' could not load CDEFs.', true, 'AGGREGATE');
|
|
|
|
return false;
|
|
}
|
|
|
|
// build cdefs array to allow for indexing on cdef_id
|
|
foreach ($_cdefs as $_cdef) {
|
|
$cdefs[$_cdef['id']] = [
|
|
'id' => $_cdef['id'],
|
|
'name' => $_cdef['name'],
|
|
'cdef_text' => get_cdef($_cdef['id']),
|
|
];
|
|
}
|
|
|
|
// add pseudo CDEF for CURRENT_DATA_SOURCE, in case CDEF=NONE
|
|
// we then may apply the standard CDEF procedure to create a new CDEF
|
|
$cdefs[0] = [
|
|
'id' => 0,
|
|
'name' => 'Items',
|
|
'cdef_text' => 'CURRENT_DATA_SOURCE',
|
|
];
|
|
|
|
// new CDEF(s) are required!
|
|
$prepared = aggregate_prepare_cdef_totalling($graph_template_items, $cdefs);
|
|
|
|
if ($prepared['invalid_cdef_id'] !== null) {
|
|
cacti_log(__FUNCTION__ . ' could not apply totals due to invalid or empty CDEF id ' . $prepared['invalid_cdef_id'], true, 'AGGREGATE');
|
|
|
|
return false;
|
|
}
|
|
|
|
$totalling_items = $prepared['items'];
|
|
$num_items = cacti_sizeof($totalling_items);
|
|
|
|
if ($num_items > 0) {
|
|
$i = 0;
|
|
|
|
foreach ($totalling_items as $totalling_item) {
|
|
// current cdef
|
|
$graph_template_item = $totalling_item['graph_item'];
|
|
$cdef_id = $totalling_item['cdef_id'];
|
|
$cdef_name = $totalling_item['cdef_name'];
|
|
$cdef_text = $totalling_item['cdef_text'];
|
|
|
|
cacti_log(__FUNCTION__ . ' cdef id: ' . $cdef_id . ' name: ' . $cdef_name . ' value: ' . $cdef_text, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// new cdef
|
|
$new_cdef_text = 'INVALID'; // in case sth goes wrong
|
|
|
|
switch ($_total_type) {
|
|
case AGGREGATE_TOTAL_TYPE_SIMILAR:
|
|
$new_cdef_text = str_replace('CURRENT_DATA_SOURCE', 'SIMILAR_DATA_SOURCES_NODUPS', $cdef_text);
|
|
|
|
break;
|
|
case AGGREGATE_TOTAL_TYPE_ALL:
|
|
$new_cdef_text = str_replace('CURRENT_DATA_SOURCE', 'ALL_DATA_SOURCES_NODUPS', $cdef_text);
|
|
|
|
break;
|
|
}
|
|
|
|
// is the new cdef already present?
|
|
$new_cdef_id = '';
|
|
|
|
foreach ($cdefs as $cdef) {
|
|
cacti_log(__FUNCTION__ . ' verify matching cdef: ' . $cdef['id'] . ' on: ' . $cdef['cdef_text'], true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
if ($cdef['cdef_text'] === $new_cdef_text) {
|
|
$new_cdef_id = $cdef['id'];
|
|
cacti_log(__FUNCTION__ . ' matching cdef: ' . $new_cdef_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// leave on first match
|
|
break;
|
|
}
|
|
}
|
|
|
|
// in case, we have NO match
|
|
if (empty($new_cdef_id)) {
|
|
// create a new cdef entry
|
|
$save = [];
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_cdef(0);
|
|
$save['system'] = 1;
|
|
$new_cdef_name = 'INVALID ' . $cdef_name; // in case anything goes wrong
|
|
|
|
switch ($_total_type) {
|
|
case AGGREGATE_TOTAL_TYPE_SIMILAR:
|
|
$new_cdef_name = '_AGGREGATE SIMILAR ' . $cdef_name;
|
|
|
|
break;
|
|
case AGGREGATE_TOTAL_TYPE_ALL:
|
|
$new_cdef_name = '_AGGREGATE ALL ' . $cdef_name;
|
|
|
|
break;
|
|
}
|
|
|
|
$save['name'] = $new_cdef_name;
|
|
|
|
// save the cdef itself
|
|
$new_cdef_id = sql_save($save, 'cdef');
|
|
|
|
cacti_log(__FUNCTION__ . ' created new cdef: ' . $new_cdef_id . ' name: ' . $new_cdef_name . ' value: ' . $new_cdef_text, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// create a new cdef item entry
|
|
$save = [];
|
|
$save['id'] = 0;
|
|
$save['hash'] = get_hash_cdef(0, 'cdef_item');
|
|
$save['cdef_id'] = $new_cdef_id;
|
|
$save['sequence'] = 1;
|
|
$save['type'] = 6; // this will be replaced by a define as soon as it exists for a pure text field
|
|
$save['value'] = $new_cdef_text;
|
|
|
|
// save the cdef item, there's only one!
|
|
$cdef_item_id = sql_save($save, 'cdef_items');
|
|
|
|
cacti_log(__FUNCTION__ . ' created new cdef item: ' . $cdef_item_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// now extend the cdef array to learn the newly entered cdef for the next loop
|
|
$cdefs[$new_cdef_id]['id'] = $new_cdef_id;
|
|
$cdefs[$new_cdef_id]['name'] = $new_cdef_name;
|
|
$cdefs[$new_cdef_id]['cdef_text'] = $new_cdef_text;
|
|
}
|
|
|
|
// now that we have a new cdef id, update record accordingly
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET cdef_id = ?
|
|
WHERE id = ?',
|
|
[$new_cdef_id, $graph_template_item['id']]);
|
|
|
|
cacti_log(__FUNCTION__ . ' updated new cdef id: ' . $new_cdef_id . ' for item: ' . $graph_template_item['id'], true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** auto_hr - set a new hr when items are skipped
|
|
*
|
|
* @param array $s - array of skipped items
|
|
* @param array $h - array of items with HR
|
|
*
|
|
* @return array - array with new HR markers
|
|
*/
|
|
function auto_hr(array $s, array $h) : array {
|
|
cacti_log(__FUNCTION__ . ' called', true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// start at end of array, both arrays are from 1 .. cacti_count(array)
|
|
$i = cacti_count($h);
|
|
|
|
// make sure, that last item always has a HR, even if template does not have any
|
|
$h[$i] = true;
|
|
|
|
do {
|
|
// if skipped item has a HR
|
|
if (isset($s[$i]) && ($s[$i] > 0) && $h[$i]) {
|
|
// set previous item (if any) to HR
|
|
if (isset($h[$i - 1])) {
|
|
$h[$i - 1] = $h[$i];
|
|
}
|
|
}
|
|
} while ($i-- > 0);
|
|
|
|
return $h;
|
|
}
|
|
|
|
/** auto_title - generate a title suggested to the user
|
|
*
|
|
* @param int $_local_graph_id - the id of the graph stanza
|
|
*
|
|
* @return string - the title
|
|
*/
|
|
function auto_title(int $_local_graph_id) : string {
|
|
cacti_log(__FUNCTION__ . ' called. Local Graph Id: ' . $_local_graph_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG);
|
|
|
|
// apply given graph title, but drop host and query variables
|
|
$graph_title = 'Aggregate ';
|
|
$graph_title .= db_fetch_cell_prepared('SELECT title
|
|
FROM graph_templates_graph
|
|
WHERE local_graph_id = ?',
|
|
[$_local_graph_id]);
|
|
|
|
cacti_log('title:' . $graph_title, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// remove all '- |query_*|' occurrences
|
|
$pattern = '/-?\s+\|query_\w+\|/';
|
|
$graph_title = preg_replace($pattern, '', $graph_title);
|
|
|
|
cacti_log('title:' . $graph_title, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
// remove all '- |host_*|' occurrences
|
|
$pattern = '/-?\s+\|host_\w+\|/';
|
|
$graph_title = preg_replace($pattern, '', (string) $graph_title);
|
|
|
|
cacti_log('title:' . $graph_title, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG);
|
|
|
|
return $graph_title;
|
|
}
|
|
|
|
/**
|
|
* Removes multiple aggregate graphs and their associated items from the database.
|
|
*
|
|
* @param array $graphs An array of graph IDs to be removed.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_aggregate_remove_multi(array $graphs) : void {
|
|
include_once(CACTI_PATH_LIBRARY . '/api_graph.php');
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $graph) {
|
|
$ag = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$graph]);
|
|
|
|
db_execute_prepared('DELETE FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$graph]);
|
|
|
|
db_execute_prepared('DELETE FROM aggregate_graphs_items
|
|
WHERE aggregate_graph_id = ?',
|
|
[$ag]);
|
|
|
|
db_execute_prepared('DELETE FROM aggregate_graphs_graph_item
|
|
WHERE aggregate_graph_id = ?',
|
|
[$ag]);
|
|
}
|
|
|
|
api_graph_remove_multi($graphs);
|
|
}
|
|
}
|
|
|
|
// To-do remove orphaned elements
|
|
/**
|
|
* Prunes orphaned graphs and their associated items from the database.
|
|
*
|
|
* @param int $local_graph_id Optional. The ID of a specific local graph to prune. If greater than 0,
|
|
* only the specified local graph and its associated items will be pruned.
|
|
* Defaults to 0, which means all orphaned graphs will be pruned.
|
|
*
|
|
* @return void
|
|
*/
|
|
function aggregate_prune_graphs(int $local_graph_id = 0) : void {
|
|
$aggregate_graphs = [];
|
|
$local_graph_ids = [];
|
|
|
|
$sql_where = '';
|
|
$sql_params = [];
|
|
|
|
if ($local_graph_id > 0) {
|
|
$sql_where = 'AND pagi.local_graph_id = ?';
|
|
$sql_params[] = $local_graph_id;
|
|
}
|
|
|
|
// Phase 1 Aggregate Graphs
|
|
$pruneme = db_fetch_assoc_prepared("SELECT
|
|
pagi.aggregate_graph_id,
|
|
pagi.local_graph_id
|
|
FROM aggregate_graphs_items AS pagi
|
|
LEFT JOIN graph_local AS gl
|
|
ON pagi.local_graph_id = gl.id
|
|
WHERE gl.id IS NULL
|
|
$sql_where",
|
|
$sql_params);
|
|
|
|
foreach ($pruneme as $p) {
|
|
$local_graph_ids[$p['local_graph_id']] = $p['local_graph_id'];
|
|
$aggregate_graphs[$p['aggregate_graph_id']] = $p['aggregate_graph_id'];
|
|
}
|
|
|
|
if (cacti_sizeof($local_graph_ids)) {
|
|
db_execute('DELETE FROM aggregate_graphs_items
|
|
WHERE local_graph_id IN (' . implode(',', $local_graph_ids) . ')');
|
|
}
|
|
|
|
if (cacti_sizeof($aggregate_graphs) || $local_graph_id > 0) {
|
|
if ($local_graph_id > 0) {
|
|
$agg_graph = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (!empty($agg_graph)) {
|
|
$aggregate_graphs[$agg_graph] = $agg_graph;
|
|
}
|
|
}
|
|
|
|
// Phase 2 - Graphs based upon aggregates
|
|
foreach ($aggregate_graphs as $a) {
|
|
$graph_id = db_fetch_cell_prepared('SELECT local_graph_id
|
|
FROM aggregate_graphs
|
|
WHERE id = ?',
|
|
[$a]);
|
|
|
|
$bad_graph_items = array_rekey(db_fetch_assoc_prepared('SELECT gti.id
|
|
FROM graph_templates_item AS gti
|
|
LEFT JOIN data_template_rrd AS dtr
|
|
ON gti.task_item_id=dtr.id
|
|
WHERE gti.local_graph_id = ?
|
|
AND dtr.id IS NULL
|
|
AND gti.graph_type_id IN (4,5,6,7,8,9)', [$graph_id]), 'id', 'id');
|
|
|
|
if (cacti_sizeof($bad_graph_items)) {
|
|
db_execute('DELETE FROM graph_templates_item
|
|
WHERE id IN (' . implode(',', $bad_graph_items) . ')
|
|
AND local_graph_id > 0');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a list of graphs to a specific graph template.
|
|
*
|
|
* @param array $graphs An array of graph IDs to be converted.
|
|
*
|
|
* @return void
|
|
*/
|
|
function api_aggregate_convert_to_graph(array $graphs) : void {
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $graph) {
|
|
$ag = db_fetch_cell_prepared('SELECT id
|
|
FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$graph]);
|
|
|
|
db_execute_prepared('DELETE FROM aggregate_graphs
|
|
WHERE local_graph_id = ?',
|
|
[$graph]);
|
|
|
|
db_execute_prepared('DELETE FROM aggregate_graphs_items
|
|
WHERE aggregate_graph_id = ?',
|
|
[$ag]);
|
|
|
|
db_execute_prepared('DELETE FROM aggregate_graphs_graph_item
|
|
WHERE aggregate_graph_id = ?',
|
|
[$ag]);
|
|
|
|
$graph_template = db_fetch_cell_prepared('SELECT MAX(gl.graph_template_id)
|
|
FROM graph_local AS gl
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON gl.id = gti.local_graph_id
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dtr.id = gti.task_item_id
|
|
WHERE dtr.id IN (
|
|
SELECT DISTINCT dtr.id FROM graph_local AS gl
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON gti.local_graph_id = gl.id
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dtr.id = gti.task_item_id
|
|
WHERE gl.id = ?
|
|
)',
|
|
[$graph]);
|
|
|
|
if ($graph_template > 0) {
|
|
db_execute_prepared('UPDATE graph_local
|
|
SET graph_template_id = ?
|
|
WHERE id = ?',
|
|
[$graph_template, $graph]);
|
|
}
|
|
}
|
|
}
|
|
}
|