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>
2663 lines
89 KiB
PHP
2663 lines
89 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/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* push_out_data_source_custom_data - pushes out the "custom data" associated with a data
|
|
* template to all of its children. this includes all fields inherited from the host
|
|
* and the data template
|
|
*
|
|
* @param int $data_template_id The id of the data template to push out values for
|
|
*
|
|
* @return bool
|
|
*/
|
|
function push_out_data_source_custom_data(int $data_template_id) : bool {
|
|
// valid data template id?
|
|
if (empty($data_template_id)) {
|
|
return false;
|
|
}
|
|
|
|
// get data_input_id from template
|
|
$data_template_data = db_fetch_row_prepared('SELECT id, data_input_id
|
|
FROM data_template_data
|
|
WHERE data_template_id = ?
|
|
AND local_data_id = 0',
|
|
[$data_template_id]);
|
|
|
|
// must be a data template
|
|
if (empty($data_template_data['data_input_id'])) {
|
|
return false;
|
|
}
|
|
|
|
// get a list of data sources using this template
|
|
$data_sources = db_fetch_assoc_prepared('SELECT data_template_data.id
|
|
FROM data_template_data
|
|
WHERE data_template_id = ?
|
|
AND local_data_id > 0', [$data_template_id]);
|
|
|
|
// get data query custom index fields to ignore
|
|
$data_query_field_index_ids = data_input_field_always_checked(0, true);
|
|
|
|
if (cacti_sizeof($data_query_field_index_ids)) {
|
|
$exclude_sql_where = ' AND did.data_input_field_id NOT IN (' . implode(',', $data_query_field_index_ids) . ')';
|
|
} else {
|
|
$exclude_sql_where = '';
|
|
}
|
|
|
|
/**
|
|
* pull out all custom templated 'input' values from the template itself
|
|
* templated items are selected by querying t_value = '' OR t_value = NULL
|
|
* or that are not Data Query related index values
|
|
*/
|
|
$template_input_fields = array_rekey(
|
|
db_fetch_assoc_prepared("SELECT dif.id,
|
|
dif.type_code, did.value, did.t_value
|
|
FROM data_input_fields AS dif
|
|
INNER JOIN data_input_data AS did
|
|
ON dif.id = did.data_input_field_id
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dtd.id = did.data_template_data_id
|
|
WHERE (dif.input_output = 'in')
|
|
AND (did.t_value = '' OR did.t_value IS NULL)
|
|
$exclude_sql_where
|
|
AND (did.data_template_data_id = ?)
|
|
AND (dtd.local_data_template_data_id = 0)",
|
|
[$data_template_data['id']]),
|
|
'id', ['type_code', 'value', 't_value']
|
|
);
|
|
|
|
// which data_input_fields are templated?
|
|
if (cacti_sizeof($template_input_fields)) {
|
|
$dif_in_str = ' AND dif.id IN (' . implode(', ', array_keys($template_input_fields)) . ')';
|
|
} else {
|
|
$dif_in_str = '';
|
|
}
|
|
|
|
/**
|
|
* pull out all templated 'input' values from all related data sources
|
|
* unfortunately, you can't simply provide the same test as above
|
|
* all input fields not related to a template ALWAYS are marked with t_value = NULL
|
|
* so we will verify against the list of data_input_field id's taken from above
|
|
*/
|
|
$input_fields = db_fetch_assoc_prepared("SELECT dtd.id AS data_template_data_id,
|
|
dif.id, dif.type_code, did.value, did.t_value, did.data_template_id, did.local_data_id, did.host_id
|
|
FROM data_input_fields AS dif
|
|
INNER JOIN data_input_data AS did
|
|
ON dif.id = did.data_input_field_id
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dtd.id = did.data_template_data_id
|
|
WHERE (dif.input_output = 'in')
|
|
$dif_in_str
|
|
$exclude_sql_where
|
|
AND (did.t_value='' OR did.t_value IS NULL)
|
|
AND (dtd.local_data_template_data_id = ?)",
|
|
[$data_template_data['id']]);
|
|
|
|
$ds_cnt = 0;
|
|
$did_cnt = 0;
|
|
$ds_in_str = '';
|
|
$did_vals = '';
|
|
|
|
if (cacti_sizeof($data_sources)) {
|
|
foreach ($data_sources as $data_source) {
|
|
if (cacti_sizeof($input_fields)) {
|
|
foreach ($input_fields as $input_field) {
|
|
if ($data_source['id'] == $input_field['data_template_data_id'] &&
|
|
isset($template_input_fields[$input_field['id']])) {
|
|
/**
|
|
* do not push out 'host fields'. This list should never match as we have already
|
|
* excluded these special types codes. However, we will maintain for reference.
|
|
*/
|
|
if (!preg_match('/^' . VALID_HOST_FIELDS . '$/i', $input_field['type_code'])) {
|
|
// this is not a 'host field', so we should either push out the value if it is templated
|
|
$did_vals .= ($did_cnt == 0 ? '' : ',') .
|
|
'(' .
|
|
$input_field['id'] . ', ' .
|
|
$data_source['id'] . ', ' .
|
|
$input_field['data_template_id'] . ', ' .
|
|
$input_field['local_data_id'] . ', ' .
|
|
$input_field['host_id'] . ', ' .
|
|
db_qstr($template_input_fields[$input_field['id']]['value']) .
|
|
')';
|
|
|
|
$did_cnt++;
|
|
} elseif ($template_input_fields[$input_field['id']]['value'] != $input_field['value']) {
|
|
// templated input field deviates from current data source, so update required
|
|
$did_vals .= ($did_cnt == 0 ? '' : ',') .
|
|
'(' .
|
|
$input_field['id'] . ', ' .
|
|
$data_source['id'] . ', ' .
|
|
$input_field['data_template_id'] . ', ' .
|
|
$input_field['local_data_id'] . ', ' .
|
|
$input_field['host_id'] . ', ' .
|
|
db_qstr($template_input_fields[$input_field['id']]['value']) .
|
|
')';
|
|
$did_cnt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create large inserts to reduce turns
|
|
$ds_in_str .= ($ds_cnt == 0 ? '(' : ',') . $data_source['id'];
|
|
$ds_cnt++;
|
|
|
|
// per 1000 data source, update rows
|
|
if ($ds_cnt % 1000 == 0) {
|
|
$ds_in_str .= ')';
|
|
push_out_data_source_templates($did_vals, $ds_in_str);
|
|
$ds_cnt = 0;
|
|
$did_cnt = 0;
|
|
$ds_in_str = '';
|
|
$did_vals = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($ds_cnt > 0) {
|
|
$ds_in_str .= ')';
|
|
push_out_data_source_templates($did_vals, $ds_in_str);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* push out changed data template fields to related data sources
|
|
*
|
|
* @param string $did_vals The data input data fields
|
|
* @param string $ds_in_str All data sources, formatted as SQL 'IN' clause
|
|
*
|
|
* @return void
|
|
*/
|
|
function push_out_data_source_templates(string $did_vals, string $ds_in_str) : void {
|
|
// update all templated input fields
|
|
if ($did_vals != '') {
|
|
db_execute("INSERT INTO data_input_data
|
|
(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, value)
|
|
VALUES $did_vals
|
|
ON DUPLICATE KEY UPDATE value=VALUES(value)");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* push_out_data_source_item - pushes out templated data template item fields to all matching
|
|
* children.
|
|
*
|
|
* @param int $data_template_rrd_id The id of the data template item to push out values for
|
|
*
|
|
* @return bool
|
|
*/
|
|
function push_out_data_source_item(int $data_template_rrd_id) : bool {
|
|
global $struct_data_source_item;
|
|
|
|
// get information about this data template
|
|
$data_template_rrd = db_fetch_row_prepared('SELECT *
|
|
FROM data_template_rrd
|
|
WHERE id = ?',
|
|
[$data_template_rrd_id]);
|
|
|
|
// must be a data template
|
|
if (empty($data_template_rrd['data_template_id'])) {
|
|
return false;
|
|
}
|
|
|
|
// loop through each data source column name (from the above array)
|
|
foreach ($struct_data_source_item as $field_name => $field_array) {
|
|
// are we allowed to push out the column?
|
|
if (((empty($data_template_rrd['t_' . $field_name])) || (preg_match('/FORCE:/', $field_name))) && ((isset($data_template_rrd['t_' . $field_name])) && (isset($data_template_rrd[$field_name])))) {
|
|
db_execute_prepared("UPDATE data_template_rrd
|
|
SET $field_name = ?
|
|
WHERE local_data_template_rrd_id = ?",
|
|
[$data_template_rrd[$field_name], $data_template_rrd['id']]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* push_out_data_source - pushes out templated data template fields to all matching children
|
|
*
|
|
* @param int $data_template_data_id The id of the data template to push out values for
|
|
*
|
|
* @return bool
|
|
*/
|
|
function push_out_data_source(int $data_template_data_id) : bool {
|
|
global $struct_data_source;
|
|
|
|
// get information about this data template
|
|
$data_template_data = db_fetch_row_prepared('SELECT * FROM data_template_data WHERE id = ?', [$data_template_data_id]);
|
|
|
|
// must be a data template
|
|
if (empty($data_template_data['data_template_id'])) {
|
|
return false;
|
|
}
|
|
|
|
// loop through each data source column name (from the above array)
|
|
foreach ($struct_data_source as $field_name => $field_array) {
|
|
// are we allowed to push out the column?
|
|
if (((empty($data_template_data['t_' . $field_name])) || (preg_match('/FORCE:/', $field_name))) && ((isset($data_template_data['t_' . $field_name])) && (isset($data_template_data[$field_name])))) {
|
|
db_execute_prepared("UPDATE data_template_data
|
|
SET $field_name = ?
|
|
WHERE local_data_template_data_id=?",
|
|
[$data_template_data[$field_name], $data_template_data['id']]);
|
|
|
|
// update the title cache
|
|
if ($field_name == 'name') {
|
|
update_data_source_title_cache_from_template($data_template_data['data_template_id']);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* data_input_field_always_checked - check for data query custom fields
|
|
*
|
|
* This function will tell cacti which data input fields must always
|
|
* carry the data source information since it's key to linking
|
|
* data query information into the data source
|
|
*
|
|
* @param int $data_input_field_id The data input field
|
|
* @param bool $return_ids Return an array of special ids
|
|
*
|
|
* @return mixed true, false or the ids
|
|
*/
|
|
function data_input_field_always_checked(int $data_input_field_id = 0, bool $return_ids = false) : mixed {
|
|
static $always_checked = [];
|
|
|
|
$always_checked_hashes = [
|
|
'6027a919c7c7731fbe095b6f53ab127b', // SNMP Query - Index Type
|
|
'cbbe5c1ddfb264a6e5d509ce1c78c95f', // SNMP Query - Index Value
|
|
'e6deda7be0f391399c5130e7c4a48b28', // SNMP Query - Output Type ID
|
|
'172b4b0eacee4948c6479f587b62e512', // Script Server Data Query - Index Type
|
|
'30fb5d5bcf3d66bb5abe88596f357c26', // Script Server Data Query - Index Value
|
|
'31112c85ae4ff821d3b288336288818c', // Script Server Data Query - Output Type ID
|
|
'd39556ecad6166701bfb0e28c5a11108', // Script Query - Index Type
|
|
'3b7caa46eb809fc238de6ef18b6e10d5', // Script Query - Indev Value
|
|
'74af2e42dc12956c4817c2ef5d9983f9', // Script Query - Output Type ID
|
|
];
|
|
|
|
if (!cacti_sizeof($always_checked)) {
|
|
$always_checked = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT id
|
|
FROM data_input_fields
|
|
WHERE hash IN (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
OR (type_code IN("index_type", "index_value", "output_type"))',
|
|
$always_checked_hashes),
|
|
'id', 'id'
|
|
);
|
|
}
|
|
|
|
if ($return_ids) {
|
|
return $always_checked;
|
|
}
|
|
|
|
if (isset($always_checked[$data_input_field_id])) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* change_data_template - changes the data template for a particular data source to
|
|
* $data_template_id.
|
|
*
|
|
* @param int $local_data_id The id of the data source to change the data template for
|
|
* @param int $data_template_id Id the of the data template to change to. specify '0' for no
|
|
* @param array $profile A structure of data source profile attributes data template
|
|
*
|
|
* @return void
|
|
*/
|
|
function change_data_template(int $local_data_id, int $data_template_id, array $profile = []) : void {
|
|
global $struct_data_source, $struct_data_source_item;
|
|
|
|
// always update tables to new data template (or no data template)
|
|
db_execute_prepared('UPDATE data_local
|
|
SET data_template_id = ? WHERE id = ?',
|
|
[$data_template_id, $local_data_id]);
|
|
|
|
// get data about the template and the data source
|
|
$data = db_fetch_row_prepared('SELECT *
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?' ,
|
|
[$local_data_id]);
|
|
|
|
if ($data_template_id == 0) {
|
|
$template_data = $data;
|
|
} else {
|
|
$template_data = db_fetch_row_prepared('SELECT *
|
|
FROM data_template_data
|
|
WHERE local_data_id = 0
|
|
AND data_template_id = ?',
|
|
[$data_template_id]);
|
|
}
|
|
|
|
// determine if we are here for the first time, or coming back
|
|
$exists = db_fetch_cell_prepared('SELECT local_data_template_data_id
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?',
|
|
[$local_data_id]);
|
|
|
|
if (empty($exists)) {
|
|
$new_save = true;
|
|
} else {
|
|
$new_save = false;
|
|
}
|
|
|
|
// some basic field values that ALL data sources should have
|
|
$save['id'] = ($data['id'] ?? 0);
|
|
$save['local_data_template_data_id'] = $template_data['id'];
|
|
$save['local_data_id'] = $local_data_id;
|
|
$save['data_template_id'] = $data_template_id;
|
|
|
|
// loop through the 'templated field names' to find to the rest...
|
|
foreach ($struct_data_source as $field_name => $field_array) {
|
|
// handle the data source profile
|
|
if ($field_name == 'rrd_step' && cacti_sizeof($profile)) {
|
|
$save[$field_name] = $profile['step'];
|
|
} elseif ((isset($data[$field_name])) || (isset($template_data[$field_name]))) {
|
|
if ((!empty($template_data['t_' . $field_name])) && ($new_save == false)) {
|
|
$save[$field_name] = $data[$field_name];
|
|
} else {
|
|
$save[$field_name] = $template_data[$field_name];
|
|
}
|
|
}
|
|
}
|
|
|
|
// these fields should never be overwritten by the template
|
|
$save['data_source_path'] = ($data['data_source_path'] ?? '');
|
|
|
|
$data_template_data_id = sql_save($save, 'data_template_data');
|
|
|
|
$data_rrds_list = db_fetch_assoc_prepared('SELECT *
|
|
FROM data_template_rrd
|
|
WHERE local_data_id = ?',
|
|
[$local_data_id]);
|
|
|
|
if ($data_template_id == 0) {
|
|
$template_rrds_list = $data_rrds_list;
|
|
} else {
|
|
$template_rrds_list = db_fetch_assoc_prepared('SELECT *
|
|
FROM data_template_rrd
|
|
WHERE local_data_id = 0
|
|
AND data_template_id = ?',
|
|
[$data_template_id]);
|
|
}
|
|
|
|
if (cacti_sizeof($data_rrds_list)) {
|
|
// this data source already has 'child' items
|
|
} else {
|
|
/* this data source does NOT have 'child' items; loop through each item in the template
|
|
and write it exactly to each item */
|
|
if (cacti_sizeof($template_rrds_list)) {
|
|
foreach ($template_rrds_list as $template_rrd) {
|
|
unset($save);
|
|
|
|
$save['id'] = 0;
|
|
$save['local_data_template_rrd_id'] = $template_rrd['id'];
|
|
$save['local_data_id'] = $local_data_id;
|
|
$save['data_template_id'] = $template_rrd['data_template_id'];
|
|
|
|
foreach ($struct_data_source_item as $field_name => $field_array) {
|
|
// handle the data source profile
|
|
if ($field_name == 'rrd_heartbeat' && cacti_sizeof($profile)) {
|
|
$save[$field_name] = $profile['heartbeat'];
|
|
} else {
|
|
$save[$field_name] = $template_rrd[$field_name];
|
|
}
|
|
}
|
|
|
|
sql_save($save, 'data_template_rrd');
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure to copy down script data (data_input_data) as well
|
|
$host_id = db_fetch_cell_prepared('SELECT host_id
|
|
FROM data_local
|
|
WHERE id = ?',
|
|
[$local_data_id]);
|
|
|
|
$data_input_data = db_fetch_assoc_prepared('SELECT *
|
|
FROM data_input_data
|
|
WHERE data_template_data_id = ?',
|
|
[$template_data['id']]);
|
|
|
|
/* this section is before most everything else so we can determine if this is a new save, by checking
|
|
the status of the 'local_data_template_data_id' column */
|
|
if (cacti_sizeof($data_input_data)) {
|
|
foreach ($data_input_data as $item) {
|
|
/**
|
|
* always propagate on a new save, only propagate templated fields thereafter
|
|
* noting that always checked should not be propagated after the initial save.
|
|
*/
|
|
if ($new_save == true || (empty($item['t_value']) && !data_input_field_always_checked($item['data_input_field_id']))) {
|
|
db_execute_prepared('REPLACE INTO data_input_data
|
|
(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$item['data_input_field_id'],
|
|
$data_template_data_id,
|
|
$data_template_id,
|
|
$local_data_id,
|
|
$host_id,
|
|
$item['t_value'],
|
|
$item['value']
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* push_out_graph - pushes out templated graph template fields to all matching children
|
|
*
|
|
* @param int $graph_template_graph_id The id of the graph template to push out values for
|
|
* @param bool $push_title If the pattern for the title changes, push out those changes
|
|
* to the various graphs.
|
|
*
|
|
* @return bool
|
|
*/
|
|
function push_out_graph(int $graph_template_graph_id, bool $push_title = true) : bool {
|
|
global $struct_graph;
|
|
|
|
// get information about this graph template
|
|
$graph_template_graph = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE id = ?', [$graph_template_graph_id]);
|
|
|
|
// must be a graph template
|
|
if ($graph_template_graph['graph_template_id'] == 0) {
|
|
return false;
|
|
}
|
|
|
|
// don't push out if you have no graphs
|
|
$has_graphs = db_fetch_cell_prepared('SELECT COUNT(id)
|
|
FROM graph_local
|
|
WHERE graph_template_id = ?',
|
|
[$graph_template_graph['graph_template_id']]);
|
|
|
|
if ($has_graphs == 0) {
|
|
return false;
|
|
}
|
|
|
|
// loop through each graph column name (from the above array)
|
|
foreach ($struct_graph as $field_name => $field_array) {
|
|
// are we allowed to push out the column?
|
|
if (isset($graph_template_graph['t_' . $field_name]) && empty($graph_template_graph['t_' . $field_name])) {
|
|
if ($field_array['method'] != 'spacer') {
|
|
db_execute_prepared("UPDATE graph_templates_graph
|
|
SET $field_name = ?
|
|
WHERE local_graph_template_graph_id = ?", [$graph_template_graph[$field_name], $graph_template_graph['id']]);
|
|
}
|
|
|
|
// update the title cache
|
|
if ($field_name == 'title' && $push_title) {
|
|
update_graph_title_cache_from_template($graph_template_graph['graph_template_id']);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* push_out_graph_input - pushes out the value of a graph input to a single child item. this function
|
|
* differs from other push_out_* functions in that it does not push out the value of this element to
|
|
* all attached children. instead, it obtains the current value of the graph input based on other
|
|
* graph items and pushes out the 'active' value
|
|
*
|
|
* @param int $graph_template_input_id The id of the graph input to push out values for
|
|
* @param int $graph_template_item_id The id the graph template item to push out
|
|
* @param array $session_members When looking for the 'active' value of the graph input, ignore these graph
|
|
* template items. typically you want to ignore all items that were just selected and have yet to be
|
|
* saved to the database. this is because these items most likely contain incorrect data
|
|
*
|
|
* @return void
|
|
*/
|
|
function push_out_graph_input(int $graph_template_input_id, int $graph_template_item_id, array $session_members) : void {
|
|
$graph_input = db_fetch_row_prepared('SELECT graph_template_id, column_name
|
|
FROM graph_template_input
|
|
WHERE id = ?', [$graph_template_input_id]);
|
|
|
|
/* column_name is a schema identifier interpolated straight into the SELECT
|
|
* and UPDATE statements below; it cannot be bound as a parameter, so confine
|
|
* it to an editable graph-item field. Without this a crafted
|
|
* graph_template_input row injects SQL through column_name (CVE-2024-31458). */
|
|
if (!cacti_sizeof($graph_input)) {
|
|
return;
|
|
}
|
|
|
|
if (!graph_template_input_column_is_allowed($graph_input['column_name'])) {
|
|
cacti_log('ERROR: push_out_graph_input() refused an invalid graph input field', false, 'SECURITY');
|
|
|
|
return;
|
|
}
|
|
|
|
$column_name = $graph_input['column_name'];
|
|
$graph_template_id = (int) $graph_input['graph_template_id'];
|
|
|
|
$graph_input_items = db_fetch_assoc_prepared('SELECT graph_template_item_id
|
|
FROM graph_template_input_defs
|
|
WHERE graph_template_input_id = ?', [$graph_template_input_id]);
|
|
|
|
$i = 0;
|
|
|
|
if (cacti_sizeof($graph_input_items)) {
|
|
foreach ($graph_input_items as $item) {
|
|
$include_items[$i] = $item['graph_template_item_id'];
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
/* we always want to make sure to stay within the same graph item input, so make a list of each
|
|
item included in this input to be included in the sql query */
|
|
if (isset($include_items)) {
|
|
$sql_include_items = 'AND ' . array_to_sql_or($include_items, 'local_graph_template_item_id');
|
|
} else {
|
|
$sql_include_items = 'AND 0=1';
|
|
}
|
|
|
|
if (cacti_sizeof($session_members) == 0) {
|
|
$values_to_apply = db_fetch_assoc('SELECT local_graph_id,' . $column_name . '
|
|
FROM graph_templates_item
|
|
WHERE graph_template_id=' . $graph_template_id . " $sql_include_items
|
|
AND local_graph_id>0
|
|
GROUP BY local_graph_id");
|
|
} else {
|
|
$new_session_members = [];
|
|
|
|
foreach ($session_members as $item_id => $item_id) {
|
|
$new_session_members[] = $item_id;
|
|
}
|
|
|
|
$values_to_apply = db_fetch_assoc('SELECT local_graph_id,' . $column_name . '
|
|
FROM graph_templates_item
|
|
WHERE graph_template_id=' . $graph_template_id . '
|
|
AND local_graph_id>0
|
|
AND !(' . array_to_sql_or($new_session_members, 'local_graph_template_item_id') . ") $sql_include_items GROUP BY local_graph_id");
|
|
}
|
|
|
|
if (cacti_sizeof($values_to_apply)) {
|
|
foreach ($values_to_apply as $value) {
|
|
if (!graph_template_input_value_is_allowed($column_name, $value[$column_name])) {
|
|
cacti_log('ERROR: push_out_graph_input() refused an invalid graph input value', false, 'SECURITY');
|
|
|
|
continue;
|
|
}
|
|
|
|
// this is just an extra check that i threw in to prevent users' graphs from getting really messed up
|
|
if (!(($column_name == 'task_item_id') && (empty($value[$column_name])))) {
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET ' . $column_name . ' = ?
|
|
WHERE local_graph_id = ?
|
|
AND local_graph_template_item_id = ?',
|
|
[$value[$column_name], $value['local_graph_id'], $graph_template_item_id]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* push_out_graph_item - pushes out templated graph template item fields to all matching
|
|
* children. if the graph template item is part of a graph input, the field will not be
|
|
* pushed out
|
|
*
|
|
* @param int $graph_template_item_id The id of the graph template item to push out values for
|
|
* @param bool $task_item_changed Add additional steps if the task items changed
|
|
*
|
|
* @return bool
|
|
*/
|
|
function push_out_graph_item(int $graph_template_item_id, bool $task_item_changed = false) : bool {
|
|
global $struct_graph_item;
|
|
|
|
// get information about this graph template
|
|
$graph_template_item = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE id = ?',
|
|
[$graph_template_item_id]);
|
|
|
|
// must be a graph template
|
|
if ($graph_template_item['graph_template_id'] == 0) {
|
|
return false;
|
|
}
|
|
|
|
$graph_template_id = $graph_template_item['graph_template_id'];
|
|
|
|
// don't push out if you have no graphs
|
|
$has_graphs = db_fetch_cell_prepared('SELECT COUNT(id)
|
|
FROM graph_local
|
|
WHERE graph_template_id = ?',
|
|
[$graph_template_item['graph_template_id']]);
|
|
|
|
if ($has_graphs == 0) {
|
|
return false;
|
|
}
|
|
|
|
update_graph_template_items($graph_template_id, $graph_template_item_id, $task_item_changed);
|
|
|
|
return true;
|
|
}
|
|
|
|
function update_graph_data_source_output_type(int $local_graph_id, int $output_type_id) : void {
|
|
$graph_local = db_fetch_row_prepared('SELECT *
|
|
FROM graph_local
|
|
WHERE id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$task_items = db_fetch_cell_prepared('SELECT GROUP_CONCAT(DISTINCT task_item_id) AS items
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if ($task_items != '') {
|
|
$local_data_id = db_fetch_cell("SELECT DISTINCT local_data_id
|
|
FROM data_template_rrd
|
|
WHERE id IN($task_items)");
|
|
} else {
|
|
$local_data_id = 0;
|
|
}
|
|
|
|
if ($local_data_id > 0) {
|
|
$data = db_fetch_row_prepared('SELECT id, data_input_id, data_template_id, name, local_data_id
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?',
|
|
[$local_data_id]);
|
|
|
|
// get each INPUT field for this data input source
|
|
$output_type_field_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_input_fields
|
|
WHERE data_input_id = ?
|
|
AND input_output = "in"
|
|
AND type_code="output_type"
|
|
ORDER BY sequence',
|
|
[$data['data_input_id']]);
|
|
|
|
$snmp_query_graph_id = db_fetch_cell_prepared('SELECT value
|
|
FROM data_input_data
|
|
WHERE data_template_data_id = ?
|
|
AND data_input_field_id = ?',
|
|
[$data['id'], $output_type_field_id]);
|
|
|
|
if ($snmp_query_graph_id != $output_type_id && $output_type_id > 0) {
|
|
db_execute_prepared('UPDATE data_input_data
|
|
SET value = ?
|
|
WHERE data_template_data_id = ?
|
|
AND data_input_field_id = ?',
|
|
[$output_type_id, $data['id'], $output_type_field_id]);
|
|
|
|
db_execute_prepared('UPDATE graph_local
|
|
SET snmp_query_graph_id = ?
|
|
WHERE graph_template_id = ?
|
|
AND id = ?',
|
|
[$output_type_id, $graph_local['graph_template_id'], $local_graph_id]);
|
|
|
|
push_out_host($graph_local['host_id'], $local_data_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* graph_template_has_override
|
|
*
|
|
* This function determines if a Graph Template has fields that
|
|
* allow changing of their values at create time. Data that
|
|
* is considered for override includes data in:
|
|
*
|
|
* Data Input Fields
|
|
* Data Template Data Fields
|
|
* Graph Template Fields
|
|
*
|
|
* @param int $graph_template_id
|
|
*
|
|
* @return bool override allowed
|
|
*/
|
|
function graph_template_has_override(int $graph_template_id) : bool {
|
|
$graph_template = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id = 0',
|
|
[$graph_template_id]);
|
|
|
|
// Check the Graph Template first for adherence
|
|
if (cacti_sizeof($graph_template)) {
|
|
foreach ($graph_template as $field => $value) {
|
|
if (str_starts_with($field, 't_')) {
|
|
if ($value == 'on') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Next let's check it's source Data Templates
|
|
$data_templates = db_fetch_assoc_prepared('SELECT DISTINCT dtd.*
|
|
FROM data_template_data AS dtd
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dtd.data_template_id = dtr.data_template_id
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON dtr.id = gti.task_item_id
|
|
WHERE gti.graph_template_id = ?
|
|
AND dtd.local_data_id = 0
|
|
AND dtr.local_data_id = 0
|
|
AND gti.hash != ""',
|
|
[$graph_template_id]);
|
|
|
|
if (cacti_sizeof($data_templates)) {
|
|
foreach ($data_templates as $dtd) {
|
|
foreach ($dtd as $field => $value) {
|
|
if (str_starts_with($field, 't_')) {
|
|
if ($value == 'on') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lastly check the data input fields
|
|
$input_fields = db_fetch_assoc_prepared('SELECT dif.data_input_id, did.t_value, did.value, dtd.name
|
|
FROM data_template_data AS dtd
|
|
INNER JOIN data_template AS dt
|
|
ON dt.id = dtd.data_template_id
|
|
INNER JOIN data_input_data AS did
|
|
ON did.data_template_data_id = dtd.id
|
|
INNER JOIN data_input_fields AS dif
|
|
ON dif.id = did.data_input_field_id
|
|
WHERE dt.hash != ""
|
|
AND dtd.id = ?
|
|
AND dtd.local_data_id = 0
|
|
AND dif.input_output = "in"
|
|
AND did.t_value = "on"',
|
|
[$dtd['id']]);
|
|
|
|
if (cacti_sizeof($input_fields)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
$graph_template_inputs = db_fetch_cell_prepared('SELECT COUNT(*)
|
|
FROM graph_template_input
|
|
WHERE graph_template_id = ?
|
|
AND column_name != "task_item_id"',
|
|
[$graph_template_id]);
|
|
|
|
if ($graph_template_inputs > 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function parse_graph_template_id(mixed $value) : mixed {
|
|
if (str_contains($value, '_')) {
|
|
$template_parts = explode('_', $value);
|
|
|
|
if (is_numeric($template_parts[0]) && is_numeric($template_parts[1])) {
|
|
return ['graph_template_id' => $template_parts[0], 'output_type_id' => $template_parts[1]];
|
|
} else {
|
|
cacti_log('ERROR: Unable to parse graph_template_id with value ' . $value, false, 'WEBUI');
|
|
|
|
exit;
|
|
}
|
|
} else {
|
|
return ['graph_template_id' => $value];
|
|
}
|
|
}
|
|
|
|
function resequence_graphs_simple(int $graph_template_id) : void {
|
|
$template_items = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id = 0
|
|
ORDER BY id',
|
|
[$graph_template_id]);
|
|
|
|
if (cacti_sizeof($template_items)) {
|
|
foreach ($template_items as $item) {
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET sequence = ?
|
|
WHERE local_graph_template_item_id = ?',
|
|
[$item['sequence'], $item['id']]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function resequence_graphs(int $graph_template_id, int $local_graph_id = 0, bool $force = false) : bool {
|
|
static $repairs = 0;
|
|
static $template_item_ids = [];
|
|
|
|
$template_items = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id = 0
|
|
ORDER BY id',
|
|
[$graph_template_id]);
|
|
|
|
$local_graphs = [];
|
|
|
|
// If force is enabled, we are also doing a repair
|
|
if ($local_graph_id > 0) {
|
|
$local_graphs[$local_graph_id] = $local_graph_id;
|
|
} elseif ($local_graph_id == 0) {
|
|
$local_graphs = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT id
|
|
FROM graph_local
|
|
WHERE graph_template_id = ?',
|
|
[$graph_template_id]),
|
|
'id', 'id'
|
|
);
|
|
}
|
|
|
|
if (cacti_sizeof($local_graphs)) {
|
|
// Put all the template item ids into an array
|
|
if (!isset($template_item_ids[$graph_template_id])) {
|
|
foreach ($template_items as $item) {
|
|
$template_item_ids[$graph_template_id][] = $item['id'];
|
|
}
|
|
}
|
|
|
|
foreach ($local_graphs as $local_graph_id) {
|
|
if ($local_graph_id > 0 && $force) {
|
|
$graph_items = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
ORDER BY sequence, id',
|
|
[$local_graph_id]);
|
|
|
|
$deletes = [];
|
|
|
|
if (cacti_sizeof($graph_items) != cacti_sizeof($template_items)) {
|
|
cacti_log(sprintf('WARNING: Graph Item Issue for Graph: %s, Template: %s, Items: %s/%s', $local_graph_id, $graph_template_id, cacti_sizeof($graph_items), cacti_sizeof($template_items)), false);
|
|
|
|
// First search the graph_items list for items that don't exist in the template
|
|
foreach ($graph_items as $gitem) {
|
|
$found = false;
|
|
|
|
if (!in_array($gitem['local_graph_template_item_id'], $template_item_ids[$graph_template_id], true)) {
|
|
$deletes[] = $gitem['id'];
|
|
$repairs++;
|
|
}
|
|
}
|
|
|
|
// Next search for duplicated local_graph_template_item_id's and remove the most
|
|
// recent one basically repairing the graph
|
|
foreach ($template_items as $item) {
|
|
$found = false;
|
|
$search = $item['id'];
|
|
|
|
// If we find a duplicate of any local_graph_template_item_id delete
|
|
// the most recent
|
|
foreach ($graph_items as $gitem) {
|
|
// If we have a match, mark 'found'. If we have a second or more
|
|
// matches, then remove that one
|
|
if ($gitem['local_graph_template_item_id'] == $search) {
|
|
if ($found) {
|
|
$deletes[] = $gitem['id'];
|
|
$repairs++;
|
|
}
|
|
|
|
$found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($deletes)) {
|
|
db_execute_prepared('DELETE FROM graph_templates_item
|
|
WHERE id IN (' . implode(',', $deletes) . ')
|
|
AND local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($template_items)) {
|
|
foreach ($template_items as $item) {
|
|
if ($local_graph_id == -1) {
|
|
// Everything including the template
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET sequence = ?
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_template_item_id = ?',
|
|
[$item['sequence'], $graph_template_id, $item['id']]);
|
|
} elseif ($local_graph_id == 0) {
|
|
// Everything except the template
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET sequence = ?
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id > 0
|
|
AND local_graph_template_item_id = ?',
|
|
[$item['sequence'], $graph_template_id, $item['id']]);
|
|
} else {
|
|
// Just the graph itself
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET sequence = ?
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id = ?
|
|
AND local_graph_template_item_id = ?',
|
|
[$item['sequence'], $graph_template_id, $local_graph_id, $item['id']]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$_SESSION['sess_gt_repairs'] = $repairs;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* retemplate_graphs - reapply the graph template as it currently exists to all
|
|
* graphs using that template. This is important when you have graphs that
|
|
* have multiple versions of a template.
|
|
*
|
|
* @param int $graph_template_id The graph template id to retemplate
|
|
* @param int $local_graph_id Optional local graph id
|
|
* @param bool $sequence_only Optional only perform a resequence on graphs that have a mismatch on graph items
|
|
*
|
|
* @return void
|
|
*/
|
|
function retemplate_graphs(int $graph_template_id, int $local_graph_id = 0, bool $sequence_only = false) : void {
|
|
if (!$sequence_only || $local_graph_id > 0) {
|
|
if ($local_graph_id == 0) {
|
|
$graphs = db_fetch_assoc_prepared('SELECT id
|
|
FROM graph_local
|
|
WHERE graph_template_id = ?',
|
|
[$graph_template_id]);
|
|
} else {
|
|
$graphs = db_fetch_assoc_prepared('SELECT id
|
|
FROM graph_local
|
|
WHERE graph_template_id = ?
|
|
AND id = ?',
|
|
[$graph_template_id, $local_graph_id]);
|
|
}
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $graph) {
|
|
change_graph_template($graph['id'], $graph_template_id, true);
|
|
}
|
|
}
|
|
} else {
|
|
$items = db_fetch_cell_prepared('SELECT COUNT(*)
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = 0
|
|
AND graph_template_id = ?',
|
|
[$graph_template_id]);
|
|
|
|
$graphs = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT local_graph_id, COUNT(*) AS items
|
|
FROM graph_templates_item
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id > 0
|
|
GROUP BY local_graph_id
|
|
HAVING items != ?',
|
|
[$graph_template_id, $items]),
|
|
'local_graph_id', 'local_graph_id'
|
|
);
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $graph) {
|
|
resequence_graphs($graph_template_id, $graph, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* change_graph_template - generic routine that changes the graph template for a particular
|
|
* graph to a specific graph_template_id which can be considered a generic
|
|
* re-templating function.
|
|
*
|
|
* @param int $local_graph_id The id of the graph to change the graph template for.
|
|
* @param int $graph_template_id The id the of the graph template to change to. specify
|
|
* '0' for no graph template.
|
|
* @param bool $force If true and the target graph template has more or less graph items than
|
|
* the current graph, perform a deeper re-template activity, or a full repair.
|
|
*
|
|
* @return bool
|
|
*/
|
|
function change_graph_template(int $local_graph_id, int $graph_template_id, bool $force = false) : bool {
|
|
global $struct_graph, $struct_graph_item;
|
|
|
|
static $cols = [];
|
|
|
|
$template_data = parse_graph_template_id($graph_template_id);
|
|
$graph_template_id = $template_data['graph_template_id'];
|
|
|
|
// get information about both the graph and the graph template we're using
|
|
$graph_list = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if ($graph_template_id == 0) {
|
|
$template_graph_list = $graph_list;
|
|
} else {
|
|
$template_graph_list = db_fetch_row_prepared('SELECT *
|
|
FROM graph_templates_graph
|
|
WHERE local_graph_id = 0
|
|
AND graph_template_id = ?',
|
|
[$graph_template_id]);
|
|
}
|
|
|
|
if (!cacti_sizeof($template_graph_list)) {
|
|
cacti_log(sprintf('WARNING: Graph Template with ID %s Does not have any Graph Items', $graph_template_id), false, 'AUTOM8');
|
|
|
|
return false;
|
|
}
|
|
|
|
if (isset($template_data['output_type_id'])) {
|
|
$output_type_id = $template_data['output_type_id'];
|
|
} else {
|
|
$output_type_id = 0;
|
|
}
|
|
|
|
$snmp_query_id = db_fetch_cell_prepared('SELECT snmp_query_id
|
|
FROM graph_local
|
|
WHERE id = ?',
|
|
[$local_graph_id]);
|
|
|
|
// always update tables to new graph template (or no graph template)
|
|
db_execute_prepared('UPDATE graph_local
|
|
SET graph_template_id = ?
|
|
WHERE id = ?',
|
|
[$graph_template_id, $local_graph_id]);
|
|
|
|
if ($force) {
|
|
$changed = true;
|
|
} elseif ($output_type_id > 0) {
|
|
$changed = true;
|
|
} elseif (cacti_sizeof($graph_list) && $graph_template_id != $graph_list['graph_template_id']) {
|
|
$changed = false;
|
|
} else {
|
|
$changed = true;
|
|
}
|
|
|
|
// determine if we are here for the first time, or coming back
|
|
$exists = db_fetch_cell_prepared('SELECT local_graph_template_graph_id
|
|
FROM graph_templates_graph
|
|
WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
if (!$exists) {
|
|
$new_save = true;
|
|
} else {
|
|
$new_save = false;
|
|
}
|
|
|
|
// some basic field values that ALL graphs should have
|
|
$save['id'] = ($graph_list['id'] ?? 0);
|
|
$save['local_graph_template_graph_id'] = $template_graph_list['id'];
|
|
$save['local_graph_id'] = $local_graph_id;
|
|
$save['graph_template_id'] = $graph_template_id;
|
|
|
|
// loop through the 'templated field names' to find the rest...
|
|
foreach ($struct_graph as $field_name => $field_array) {
|
|
$value_type = "t_$field_name";
|
|
|
|
if ($field_array['method'] != 'spacer') {
|
|
if ((!empty($template_graph_list[$value_type])) && ($new_save == false)) {
|
|
$save[$field_name] = $graph_list[$field_name];
|
|
} else {
|
|
$save[$field_name] = $template_graph_list[$field_name];
|
|
}
|
|
}
|
|
}
|
|
|
|
sql_save($save, 'graph_templates_graph');
|
|
|
|
$graph_items_list = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
ORDER BY id',
|
|
[$local_graph_id]);
|
|
|
|
if ($graph_template_id == 0) {
|
|
$template_items_list = $graph_items_list;
|
|
} else {
|
|
$template_items_list = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = 0
|
|
AND graph_template_id = ?
|
|
ORDER BY id',
|
|
[$graph_template_id]);
|
|
}
|
|
|
|
if (!cacti_sizeof($cols)) {
|
|
$cols = db_get_table_column_types('graph_templates_item');
|
|
}
|
|
|
|
if (cacti_sizeof($template_items_list)) {
|
|
foreach ($template_items_list as $template_item) {
|
|
unset($save);
|
|
|
|
$save['local_graph_template_item_id'] = $template_item['id'];
|
|
$save['local_graph_id'] = $local_graph_id;
|
|
$save['graph_template_id'] = $template_item['graph_template_id'];
|
|
$save['sequence'] = $template_item['sequence'];
|
|
|
|
// go through the existing graph_items and look for the matching local_graph_template_item_id
|
|
$found = false;
|
|
$found_item = [];
|
|
|
|
if (cacti_sizeof($graph_items_list) && $new_save == false) {
|
|
foreach ($graph_items_list as $item) {
|
|
if ($item['local_graph_template_item_id'] == $template_item['id']) {
|
|
$found_item = $item;
|
|
$found = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($found) {
|
|
foreach ($found_item as $column => $value) {
|
|
switch($column) {
|
|
case 'local_graph_id':
|
|
case 'hash':
|
|
case 'local_graph_template_item_id':
|
|
case 'graph_template_id':
|
|
case 'sequence':
|
|
break;
|
|
default:
|
|
if (str_contains($cols[$column]['type'], 'int') ||
|
|
str_contains($cols[$column]['type'], 'float') ||
|
|
str_contains($cols[$column]['type'], 'decimal') ||
|
|
str_contains($cols[$column]['type'], 'double')) {
|
|
if (!empty($value)) {
|
|
$save[$column] = $value;
|
|
} else {
|
|
$save[$column] = 0;
|
|
}
|
|
} else {
|
|
$save[$column] = $value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// no graph item at this position, tack it on
|
|
$save['id'] = 0;
|
|
|
|
// attempt to discover the task_item_id
|
|
$local_data_ids = db_fetch_cell_prepared('SELECT
|
|
GROUP_CONCAT(DISTINCT local_data_id) AS ids
|
|
FROM data_template_rrd
|
|
WHERE id IN (
|
|
SELECT DISTINCT task_item_id
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
)',
|
|
[$local_graph_id]);
|
|
|
|
$data_source_name = db_fetch_cell_prepared('SELECT data_source_name
|
|
FROM data_template_rrd WHERE id = ?',
|
|
[$template_item['task_item_id']]);
|
|
|
|
if ($data_source_name != '' && $local_data_ids != '') {
|
|
$task_item_id = db_fetch_cell_prepared('SELECT DISTINCT id
|
|
FROM data_template_rrd
|
|
WHERE local_data_id IN (' . $local_data_ids . ')
|
|
AND data_source_name = ?
|
|
LIMIT 1', [$data_source_name]);
|
|
|
|
if (!empty($task_item_id)) {
|
|
$save['task_item_id'] = $task_item_id;
|
|
} else {
|
|
$save['task_item_id'] = 0;
|
|
}
|
|
} else {
|
|
$save['task_item_id'] = 0;
|
|
}
|
|
|
|
foreach ($template_item as $column => $value) {
|
|
switch($column) {
|
|
case 'id':
|
|
case 'hash':
|
|
case 'local_graph_id':
|
|
case 'local_graph_template_item_id':
|
|
case 'graph_template_id':
|
|
case 'sequence':
|
|
case 'task_item_id':
|
|
break;
|
|
default:
|
|
if (str_contains($cols[$column]['type'], 'int') ||
|
|
str_contains($cols[$column]['type'], 'float') ||
|
|
str_contains($cols[$column]['type'], 'decimal') ||
|
|
str_contains($cols[$column]['type'], 'double')) {
|
|
if (!empty($value)) {
|
|
$save[$column] = $value;
|
|
} else {
|
|
$save[$column] = 0;
|
|
}
|
|
} else {
|
|
$save[$column] = $value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sql_save($save, 'graph_templates_item');
|
|
}
|
|
}
|
|
|
|
if ($new_save == false && $force) {
|
|
resequence_graphs($graph_template_id, $local_graph_id, $force);
|
|
}
|
|
|
|
// handle changes in data template if there are any
|
|
if ($new_save == false && $changed && $snmp_query_id > 0) {
|
|
update_graph_data_source_output_type($local_graph_id, $output_type_id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* update_graph_template_items - update a graph template item for a graph template
|
|
* this function is used to push out graph item changes to graphs of a specific
|
|
* template. It will be called when you are saving a graph item. In cases where
|
|
* the Graph Item is new, it will update all graphs with a new item. When
|
|
* the graph item exists, it'll just walk through all columns and update the values.
|
|
*
|
|
* @param int $graph_template_id The graph template id to update
|
|
* @param int $graph_template_item_id The graph template item id from the template
|
|
* @param bool $task_item_changed Tells us if this is a new graph item or one that
|
|
* needs to be pushed to all graphs
|
|
*
|
|
* @return void
|
|
*/
|
|
function update_graph_template_items(int $graph_template_id, int $graph_template_item_id, bool $task_item_changed) : void {
|
|
global $struct_graph_item;
|
|
|
|
static $cols = [];
|
|
|
|
$template_items_list = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = 0
|
|
AND graph_template_id = ?
|
|
AND id = ?
|
|
ORDER BY id',
|
|
[$graph_template_id, $graph_template_item_id]);
|
|
|
|
if (!$task_item_changed) {
|
|
if (cacti_sizeof($template_items_list)) {
|
|
$sql = 'UPDATE graph_templates_item ';
|
|
$sql_params = [];
|
|
|
|
foreach ($template_items_list as $template_item) {
|
|
foreach ($template_item as $column => $value) {
|
|
switch($column) {
|
|
case 'color_id':
|
|
case 'color2_id':
|
|
case 'alpha':
|
|
case 'alpha2':
|
|
case 'gradheight':
|
|
case 'graph_type_id':
|
|
case 'line_width':
|
|
case 'dashes':
|
|
case 'dash_offset':
|
|
case 'cdef_id':
|
|
case 'vdef_id':
|
|
case 'shift':
|
|
case 'consolidation_function_id':
|
|
case 'textalign':
|
|
case 'legend':
|
|
case 'text_format':
|
|
case 'value':
|
|
case 'hard_return':
|
|
case 'gprint_id':
|
|
$sql .= (cacti_sizeof($sql_params) ? ', ' : 'SET ') . "`$column` = ?";
|
|
$sql_params[] = $value;
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacti_sizeof($sql_params)) {
|
|
$sql .= ' WHERE local_graph_template_item_id = ?';
|
|
$sql_params[] = $graph_template_item_id;
|
|
|
|
db_execute_prepared($sql, $sql_params);
|
|
}
|
|
}
|
|
} else {
|
|
if (!cacti_sizeof($cols)) {
|
|
$cols = db_get_table_column_types('graph_templates_item');
|
|
}
|
|
|
|
$graphs = array_rekey(
|
|
db_fetch_assoc_prepared('SELECT id
|
|
FROM graph_local
|
|
WHERE graph_template_id = ?',
|
|
[$graph_template_id]),
|
|
'id', 'id'
|
|
);
|
|
|
|
if (cacti_sizeof($graphs)) {
|
|
foreach ($graphs as $local_graph_id) {
|
|
$graph_items_list = db_fetch_assoc_prepared('SELECT *
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
ORDER BY id',
|
|
[$local_graph_id]);
|
|
|
|
if (cacti_sizeof($template_items_list)) {
|
|
foreach ($template_items_list as $template_item) {
|
|
unset($save);
|
|
|
|
$save['local_graph_template_item_id'] = $template_item['id'];
|
|
$save['local_graph_id'] = $local_graph_id;
|
|
$save['graph_template_id'] = $template_item['graph_template_id'];
|
|
$save['sequence'] = $template_item['sequence'];
|
|
|
|
// go through the existing graph_items and look for the matching local_graph_template_item_id
|
|
$found = false;
|
|
$found_item = [];
|
|
|
|
if (cacti_sizeof($graph_items_list)) {
|
|
foreach ($graph_items_list as $item) {
|
|
if ($item['local_graph_template_item_id'] == $template_item['id']) {
|
|
$found_item = $item;
|
|
$found = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($found) {
|
|
foreach ($found_item as $column => $value) {
|
|
switch($column) {
|
|
case 'local_graph_id':
|
|
case 'hash':
|
|
case 'local_graph_template_item_id':
|
|
case 'graph_template_id':
|
|
case 'sequence':
|
|
break;
|
|
default:
|
|
if (str_contains($cols[$column]['type'], 'int') ||
|
|
str_contains($cols[$column]['type'], 'float') ||
|
|
str_contains($cols[$column]['type'], 'decimal') ||
|
|
str_contains($cols[$column]['type'], 'double')) {
|
|
if (!empty($value)) {
|
|
$save[$column] = $value;
|
|
} else {
|
|
$save[$column] = 0;
|
|
}
|
|
} else {
|
|
$save[$column] = $value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// no graph item at this position, tack it on
|
|
$save['id'] = 0;
|
|
|
|
// attempt to discover the task_item_id
|
|
$local_data_ids = db_fetch_cell_prepared('SELECT
|
|
GROUP_CONCAT(DISTINCT local_data_id) AS ids
|
|
FROM data_template_rrd
|
|
WHERE id IN (
|
|
SELECT DISTINCT task_item_id
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
)',
|
|
[$local_graph_id]);
|
|
|
|
$data_source_name = db_fetch_cell_prepared('SELECT data_source_name
|
|
FROM data_template_rrd WHERE id = ?',
|
|
[$template_item['task_item_id']]);
|
|
|
|
if ($data_source_name != '' && $local_data_ids != '') {
|
|
$task_item_id = db_fetch_cell_prepared('SELECT DISTINCT id
|
|
FROM data_template_rrd
|
|
WHERE local_data_id IN (' . $local_data_ids . ')
|
|
AND data_source_name = ?
|
|
LIMIT 1', [$data_source_name]);
|
|
|
|
if (!empty($task_item_id)) {
|
|
$save['task_item_id'] = $task_item_id;
|
|
} else {
|
|
$save['task_item_id'] = 0;
|
|
}
|
|
} else {
|
|
$save['task_item_id'] = 0;
|
|
}
|
|
|
|
foreach ($template_item as $column => $value) {
|
|
switch($column) {
|
|
case 'id':
|
|
case 'hash':
|
|
case 'local_graph_id':
|
|
case 'local_graph_template_item_id':
|
|
case 'graph_template_id':
|
|
case 'sequence':
|
|
case 'task_item_id':
|
|
break;
|
|
default:
|
|
if (str_contains($cols[$column]['type'], 'int') ||
|
|
str_contains($cols[$column]['type'], 'float') ||
|
|
str_contains($cols[$column]['type'], 'decimal') ||
|
|
str_contains($cols[$column]['type'], 'double')) {
|
|
if (!empty($value)) {
|
|
$save[$column] = $value;
|
|
} else {
|
|
$save[$column] = 0;
|
|
}
|
|
} else {
|
|
$save[$column] = $value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sql_save($save, 'graph_templates_item');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* graph_to_graph_template - converts a graph to a graph template
|
|
*
|
|
* @param int $local_graph_id The id of the graph to be converted
|
|
* @param string $graph_title The graph title to use for the new graph template. the variable
|
|
* <graph_title> will be substituted for the current graph title.
|
|
*
|
|
* @return void
|
|
*/
|
|
function graph_to_graph_template(int $local_graph_id, string $graph_title) : void {
|
|
// create a new graph template entry
|
|
$title_template = db_fetch_cell_prepared('SELECT title
|
|
FROM graph_templates_graph WHERE local_graph_id = ?',
|
|
[$local_graph_id]);
|
|
|
|
$title = str_replace('<graph_title>', $title_template, $graph_title);
|
|
|
|
db_execute_prepared('INSERT INTO graph_templates
|
|
(id, name, hash)
|
|
VALUES (0, ?, ?)', [$title, get_hash_graph_template(0)]);
|
|
|
|
$graph_template_id = db_fetch_insert_id();
|
|
|
|
// update graph to point to the new template
|
|
db_execute_prepared('UPDATE graph_templates_graph
|
|
SET local_graph_id=0, local_graph_template_graph_id=0, graph_template_id = ?
|
|
WHERE local_graph_id = ?',
|
|
[$graph_template_id, $local_graph_id]);
|
|
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET local_graph_id=0, local_graph_template_item_id=0, graph_template_id = ?, task_item_id=0
|
|
WHERE local_graph_id = ?',
|
|
[$graph_template_id, $local_graph_id]);
|
|
|
|
// create hashes for the graph template items
|
|
$items = db_fetch_assoc_prepared('SELECT id
|
|
FROM graph_templates_item
|
|
WHERE graph_template_id = ?
|
|
AND local_graph_id=0',
|
|
[$graph_template_id]);
|
|
|
|
for ($j = 0; $j < cacti_count($items); $j++) {
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET hash = ? WHERE id= ?',
|
|
[get_hash_graph_template($items[$j]['id'], 'graph_template_item'), $items[$j]['id']]);
|
|
}
|
|
|
|
// delete the old graph local entry
|
|
db_execute_prepared('DELETE FROM graph_local WHERE id = ?', [$local_graph_id]);
|
|
db_execute_prepared('DELETE FROM graph_tree_items WHERE local_graph_id = ?', [$local_graph_id]);
|
|
}
|
|
|
|
/**
|
|
* data_source_to_data_template - converts a data source to a data template
|
|
*
|
|
* @param int $local_data_id The id of the data source to be converted
|
|
* @param string $data_source_title The data source title to use for the new data template. the variable
|
|
* <ds_title> will be substituted for the current data source title.
|
|
*
|
|
* @return void
|
|
*/
|
|
function data_source_to_data_template(int $local_data_id, string $data_source_title) : void {
|
|
// create a new graph template entry
|
|
$title_template = db_fetch_cell_prepared('SELECT name
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?',
|
|
[$local_data_id]);
|
|
|
|
$title = str_replace('<ds_title>', $title_template, $data_source_title);
|
|
|
|
db_execute_prepared('INSERT INTO data_template
|
|
(id, name, hash)
|
|
VALUES (0, ?, ?)',
|
|
[$title, get_hash_data_template(0)]);
|
|
|
|
$data_template_id = db_fetch_insert_id();
|
|
|
|
// update graph to point to the new template
|
|
db_execute_prepared('UPDATE data_template_data
|
|
SET local_data_id=0, local_data_template_data_id=0, data_template_id = ?
|
|
WHERE local_data_id = ?',
|
|
[$data_template_id, $local_data_id]);
|
|
|
|
db_execute_prepared('UPDATE data_template_rrd
|
|
SET local_data_id=0, local_data_template_rrd_id=0, data_template_id = ?
|
|
WHERE local_data_id = ?',
|
|
[$data_template_id, $local_data_id]);
|
|
|
|
// create hashes for the data template items
|
|
$items = db_fetch_assoc_prepared('SELECT id
|
|
FROM data_template_rrd
|
|
WHERE data_template_id = ?
|
|
AND local_data_id=0',
|
|
[$data_template_id]);
|
|
|
|
for ($j = 0; $j < cacti_count($items); $j++) {
|
|
db_execute_prepared('UPDATE data_template_rrd
|
|
SET hash = ? WHERE id =?',
|
|
[get_hash_data_template($items[$j]['id'], 'data_template_item'), $items[$j]['id']]);
|
|
}
|
|
|
|
// resolve the owning poller before the data_local row (needed for the
|
|
// join) is deleted below
|
|
$poller_id = db_fetch_cell_prepared('SELECT h.poller_id
|
|
FROM data_local AS dl
|
|
INNER JOIN host AS h
|
|
ON h.id = dl.host_id
|
|
WHERE dl.id = ?',
|
|
[$local_data_id]);
|
|
|
|
// delete the old graph local entry
|
|
db_execute_prepared('DELETE FROM data_local WHERE id = ?', [$local_data_id]);
|
|
|
|
poller_item_delete_for_data_source($local_data_id);
|
|
|
|
if ($poller_id > 1) {
|
|
if (remote_poller_up($poller_id)) {
|
|
if (($rcnn_id = poller_push_to_remote_db_connect($poller_id, true)) !== false) {
|
|
poller_item_delete_for_data_source($local_data_id, $rcnn_id, false);
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
} else {
|
|
raise_message('poller_down_' . $poller_id, __('Remote Poller %s is Down, you will need to perform a FullSync once it is up again', $poller_id), MESSAGE_LEVEL_WARN);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* graph_template_connect_task_items - wire each new graph item to its new data
|
|
* source item (task_item_id) after a graph is created from a template.
|
|
*
|
|
* The two per-item id lookups are pre-fetched in bulk so this runs one UPDATE
|
|
* per item instead of two SELECTs plus an UPDATE for each (see #7380). The maps
|
|
* keep the first match per key to mirror the original db_fetch_cell() behaviour,
|
|
* so the resulting task_item_id wiring is identical to the per-item version.
|
|
*
|
|
* @param int $graph_template_id The graph template the graph was created from
|
|
* @param array $cache_array The create cache (local_graph_id + local_data_id map)
|
|
* @param mixed $db_conn The connection to use or false for the default
|
|
*
|
|
* @return void
|
|
*/
|
|
function graph_template_connect_task_items(int $graph_template_id, array $cache_array, mixed $db_conn = false) : void {
|
|
if (!isset($cache_array['local_graph_id'])) {
|
|
return;
|
|
}
|
|
|
|
$template_item_list = db_fetch_assoc_prepared('SELECT
|
|
gti.id, dtr.id AS data_template_rrd_id, dtr.data_template_id
|
|
FROM graph_templates_item AS gti
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON gti.task_item_id=dtr.id
|
|
WHERE gti.graph_template_id = ?
|
|
AND local_graph_id=0
|
|
AND task_item_id>0',
|
|
[$graph_template_id], true, $db_conn);
|
|
|
|
if (!cacti_sizeof($template_item_list)) {
|
|
return;
|
|
}
|
|
|
|
// new graph_templates_item ids for this graph, keyed by template item id
|
|
$new_graph_items = [];
|
|
|
|
$graph_item_list = db_fetch_assoc_prepared('SELECT id, local_graph_template_item_id
|
|
FROM graph_templates_item
|
|
WHERE local_graph_id = ?
|
|
ORDER BY id',
|
|
[$cache_array['local_graph_id']], true, $db_conn);
|
|
|
|
foreach ($graph_item_list as $row) {
|
|
if (!isset($new_graph_items[$row['local_graph_template_item_id']])) {
|
|
$new_graph_items[$row['local_graph_template_item_id']] = $row['id'];
|
|
}
|
|
}
|
|
|
|
// new data_template_rrd ids for the created data sources, keyed by
|
|
// "<template rrd id>:<local data id>"
|
|
$new_rrd_items = [];
|
|
$local_data_ids = array_values($cache_array['local_data_id'] ?? []);
|
|
|
|
if (cacti_sizeof($local_data_ids)) {
|
|
$rrd_item_list = db_fetch_assoc('SELECT id, local_data_template_rrd_id, local_data_id
|
|
FROM data_template_rrd
|
|
WHERE local_data_id IN (' . implode(', ', array_map('intval', $local_data_ids)) . ')
|
|
ORDER BY id',
|
|
true, $db_conn);
|
|
|
|
foreach ($rrd_item_list as $row) {
|
|
$key = $row['local_data_template_rrd_id'] . ':' . $row['local_data_id'];
|
|
|
|
if (!isset($new_rrd_items[$key])) {
|
|
$new_rrd_items[$key] = $row['id'];
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($template_item_list as $template_item) {
|
|
if (isset($cache_array['local_data_id'][$template_item['data_template_id']])) {
|
|
$local_data_id = $cache_array['local_data_id'][$template_item['data_template_id']];
|
|
|
|
$graph_template_item_id = $new_graph_items[$template_item['id']] ?? 0;
|
|
$data_template_rrd_id = $new_rrd_items[$template_item['data_template_rrd_id'] . ':' . $local_data_id] ?? 0;
|
|
|
|
if (!empty($data_template_rrd_id) && !empty($graph_template_item_id)) {
|
|
db_execute_prepared('UPDATE graph_templates_item
|
|
SET task_item_id = ?
|
|
WHERE id = ?',
|
|
[$data_template_rrd_id, $graph_template_item_id], true, $db_conn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* create_complete_graph_from_template - creates a graph and all necessary data sources based on a
|
|
* graph template.
|
|
*
|
|
* @param int $graph_template_id The id of the graph template that will be used to create the new graph.
|
|
* @param int $host_id The id of the host to associate the new graph and data sources with
|
|
* @param array $snmp_query_array If the new data sources are to be based on a data query, specify the
|
|
* necessary data query information here. it must contain the following information:
|
|
*
|
|
* $snmp_query_array['snmp_query_id']
|
|
* $snmp_query_array['snmp_index_on']
|
|
* $snmp_query_array['snmp_query_graph_id']
|
|
* $snmp_query_array['snmp_index']
|
|
*
|
|
* @param mixed $suggested_vals Any additional information to be included in the new graphs or
|
|
* data sources must be included in the array. data is to be included in the following format:
|
|
*
|
|
* $values['cg'][graph_template_id]['graph_template'][field_name] = $value // graph template
|
|
* $values['cg'][graph_template_id]['graph_template_item'][graph_template_item_id][field_name] = $value // graph template item
|
|
* $values['cg'][data_template_id]['data_template'][field_name] = $value // data template
|
|
* $values['cg'][data_template_id]['data_template_item'][data_template_item_id][field_name] = $value // data template item
|
|
* $values['sg'][data_query_id][graph_template_id]['graph_template'][field_name] = $value // graph template (w/ data query)
|
|
* $values['sg'][data_query_id][graph_template_id]['graph_template_item'][graph_template_item_id][field_name] = $value // graph template item (w/ data query)
|
|
* $values['sg'][data_query_id][data_template_id]['data_template'][field_name] = $value // data template (w/ data query)
|
|
*
|
|
* @return mixed False if failing, otherwise the local_data_id data in a cache array
|
|
*/
|
|
function create_complete_graph_from_template(int $graph_template_id, int $host_id, array $snmp_query_array, mixed &$suggested_vals) : mixed {
|
|
include_once(CACTI_PATH_LIBRARY . '/data_query.php');
|
|
|
|
if (!graph_template_whitelist_check($graph_template_id)) {
|
|
raise_message(10);
|
|
|
|
return false;
|
|
}
|
|
|
|
// don't create a graph for a bad template
|
|
if (empty($graph_template_id)) {
|
|
raise_message('bad_template', __('Attempting to Create Graph from Non-Template'), MESSAGE_LEVEL_ERROR);
|
|
cacti_log('Attempting to Create Graph from Non-Template', false, 'AUTOM8');
|
|
|
|
return false;
|
|
}
|
|
|
|
// don't create a graph from a remove template
|
|
$gt = db_fetch_cell_prepared('SELECT id
|
|
FROM graph_templates
|
|
WHERE id = ?',
|
|
[$graph_template_id]);
|
|
|
|
if (empty($gt)) {
|
|
raise_message('bad_template', __('Attempting to Create Graph from Removed Graph Template'), MESSAGE_LEVEL_ERROR);
|
|
cacti_log('Attempting to Create Graph from Removed Graph Template ' . $graph_template_id, false, 'AUTOM8');
|
|
|
|
return false;
|
|
}
|
|
|
|
// create the graph
|
|
$save['id'] = 0;
|
|
$save['graph_template_id'] = $graph_template_id;
|
|
$save['host_id'] = $host_id;
|
|
|
|
// defaults for non-snmp query based
|
|
$save['snmp_query_id'] = 0;
|
|
$save['snmp_query_graph_id'] = 0;
|
|
$save['snmp_index'] = '';
|
|
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
if (isset($snmp_query_array['snmp_query_id']) && $snmp_query_array['snmp_query_id'] > 0) {
|
|
$save['snmp_query_id'] = $snmp_query_array['snmp_query_id'];
|
|
}
|
|
|
|
if (isset($snmp_query_array['snmp_query_graph_id']) && $snmp_query_array['snmp_query_graph_id'] > 0) {
|
|
$save['snmp_query_graph_id'] = $snmp_query_array['snmp_query_graph_id'];
|
|
}
|
|
|
|
if (isset($snmp_query_array['snmp_index']) && $snmp_query_array['snmp_index'] != '') {
|
|
$save['snmp_index'] = $snmp_query_array['snmp_index'];
|
|
}
|
|
}
|
|
|
|
$cache_array['local_graph_id'] = sql_save($save, 'graph_local');
|
|
|
|
/**
|
|
* Save the last time a graph was created/deleted
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_graph', time());
|
|
|
|
// apply graph items
|
|
change_graph_template($cache_array['local_graph_id'], $graph_template_id, true);
|
|
|
|
// perform graph replacement based upon suggested values
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
api_reapply_suggested_graph_title($cache_array['local_graph_id']);
|
|
}
|
|
|
|
// suggested values: graph, passed from parent
|
|
if (isset($suggested_vals[$graph_template_id]['graph_template'])) {
|
|
foreach ($suggested_vals[$graph_template_id]['graph_template'] as $field_name => $field_value) {
|
|
db_execute_prepared("UPDATE graph_templates_graph
|
|
SET $field_name = ?
|
|
WHERE local_graph_id = ?",
|
|
[$field_value, $cache_array['local_graph_id']]);
|
|
}
|
|
}
|
|
|
|
// suggested values: graph item, passed from parent
|
|
if (isset($suggested_vals[$graph_template_id]['graph_template_item'])) {
|
|
foreach ($suggested_vals[$graph_template_id]['graph_template_item'] as $graph_template_item_id => $field_array) {
|
|
foreach ($field_array as $field_name => $field_value) {
|
|
$graph_item_id = db_fetch_cell_prepared('SELECT id
|
|
FROM graph_templates_item
|
|
WHERE local_graph_template_item_id = ?
|
|
AND local_graph_id = ?',
|
|
[$graph_template_item_id, $cache_array['local_graph_id']]);
|
|
|
|
db_execute_prepared("UPDATE graph_templates_item
|
|
SET $field_name = ?
|
|
WHERE id = ?",
|
|
[$field_value, $graph_item_id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// create each data source, but don't duplicate
|
|
$data_templates = db_fetch_assoc_prepared('SELECT dt.id, dt.name, dtr.data_source_name
|
|
FROM data_template AS dt
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dtr.data_template_id=dt.id
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON gti.task_item_id=dtr.id
|
|
WHERE dtr.local_data_id = 0
|
|
AND gti.local_graph_id = 0
|
|
AND gti.graph_template_id = ?
|
|
GROUP BY dt.id
|
|
ORDER BY dt.name',
|
|
[$graph_template_id]);
|
|
|
|
if (cacti_sizeof($data_templates)) {
|
|
foreach ($data_templates as $data_template) {
|
|
// check if the data source already exists
|
|
$previous_data_source = data_source_exists($graph_template_id, $host_id, $data_template, $snmp_query_array);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Data Source previous ' . ($previous_data_source ? 'exists' : 'does not exist'), false, 'DSTRACE');
|
|
cacti_log('Data Source keys "' . implode(', ', array_keys($previous_data_source)) . '"', false, 'DSTRACE');
|
|
cacti_log('Data Source values "' . implode(', ', array_values($previous_data_source)) . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (cacti_sizeof($previous_data_source)) {
|
|
$use_previous_data = create_graph_custom_data_compatible($suggested_vals, $previous_data_source);
|
|
} else {
|
|
$use_previous_data = false;
|
|
}
|
|
|
|
if (read_config_option('data_source_trace') == 'on' && $use_previous_data) {
|
|
cacti_log('Existing Data Source compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
if (cacti_sizeof($previous_data_source) && $use_previous_data) {
|
|
$cache_array['local_data_id'][$data_template['id']] = $previous_data_source['id'];
|
|
} else {
|
|
// Save the data source
|
|
unset($save);
|
|
|
|
$save['id'] = 0;
|
|
$save['data_template_id'] = $data_template['id'];
|
|
$save['host_id'] = $host_id;
|
|
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
if (isset($snmp_query_array['snmp_query_id']) && $snmp_query_array['snmp_query_id'] > 0) {
|
|
$save['snmp_query_id'] = $snmp_query_array['snmp_query_id'];
|
|
}
|
|
|
|
if (isset($snmp_query_array['snmp_index']) && $snmp_query_array['snmp_index'] != '') {
|
|
$save['snmp_index'] = $snmp_query_array['snmp_index'];
|
|
}
|
|
}
|
|
|
|
$cache_array['local_data_id'][$data_template['id']] = sql_save($save, 'data_local');
|
|
|
|
/**
|
|
* Save the last time a data_source was created/deleted
|
|
* for Caching.
|
|
*/
|
|
set_config_option('time_last_change_data_source', time());
|
|
|
|
// Determine the data source profile to use
|
|
if (isset($suggested_vals[$graph_template_id]['data_template'][$data_template['id']]['data_source_profile_id'])) {
|
|
$profile_id = $suggested_vals[$graph_template_id]['data_template'][$data_template['id']]['data_source_profile_id'];
|
|
|
|
// validate the data source profile
|
|
$profile = [];
|
|
|
|
if ($profile_id != 0) {
|
|
$profile = db_fetch_row_prepared('SELECT *
|
|
FROM data_source_profiles
|
|
WHERE id = ?',
|
|
[$profile_id]);
|
|
}
|
|
|
|
// default to the default profile if the one given is invalid
|
|
if (!cacti_sizeof($profile)) {
|
|
$profile = db_fetch_row('SELECT *
|
|
FROM data_source_profiles
|
|
ORDER BY `default`
|
|
DESC LIMIT 1');
|
|
}
|
|
} else {
|
|
$profile_id = 0;
|
|
$profile = [];
|
|
}
|
|
|
|
change_data_template($cache_array['local_data_id'][$data_template['id']], $data_template['id'], $profile);
|
|
|
|
$data_template_data_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?',
|
|
[$cache_array['local_data_id'][$data_template['id']]]);
|
|
|
|
// Set suggested names and custom data
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
$data_input_fields = array_rekey(db_fetch_assoc_prepared('SELECT dif.id, dif.type_code
|
|
FROM snmp_query AS sq
|
|
INNER JOIN data_input AS di
|
|
ON sq.data_input_id = di.id
|
|
INNER JOIN data_input_fields AS dif
|
|
ON di.id = dif.data_input_id
|
|
WHERE (dif.type_code = "index_type"
|
|
OR dif.type_code = "index_value"
|
|
OR dif.type_code = "output_type")
|
|
AND sq.id = ?',
|
|
[$snmp_query_array['snmp_query_id']]), 'type_code', 'id');
|
|
|
|
$snmp_cache_value = db_fetch_cell_prepared('SELECT field_value
|
|
FROM host_snmp_cache
|
|
WHERE host_id = ?
|
|
AND snmp_query_id = ?
|
|
AND field_name = ?
|
|
AND snmp_index = ?',
|
|
[$host_id, $snmp_query_array['snmp_query_id'],
|
|
$snmp_query_array['snmp_index_on'], $snmp_query_array['snmp_index']]);
|
|
|
|
if (cacti_sizeof($data_input_fields)) {
|
|
foreach ($data_input_fields as $type_code => $id) {
|
|
$checked = data_input_field_always_checked($id) ? 'on' : '';
|
|
|
|
switch($type_code) {
|
|
case 'index_type':
|
|
// save the value to index on (ie. ifindex, ifip, etc)
|
|
db_execute_prepared('REPLACE INTO data_input_data
|
|
(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$id,
|
|
$data_template_data_id,
|
|
$data_template['id'],
|
|
$cache_array['local_data_id'][$data_template['id']],
|
|
$host_id,
|
|
$checked,
|
|
$snmp_query_array['snmp_index_on']
|
|
]
|
|
);
|
|
|
|
break;
|
|
case 'index_value':
|
|
// save the actual value (ie. 3, 192.168.1.101, etc)
|
|
db_execute_prepared('REPLACE INTO data_input_data
|
|
(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$id,
|
|
$data_template_data_id,
|
|
$data_template['id'],
|
|
$cache_array['local_data_id'][$data_template['id']],
|
|
$host_id,
|
|
$checked,
|
|
$snmp_cache_value
|
|
]
|
|
);
|
|
|
|
break;
|
|
case 'output_type':
|
|
// set the expected output type (ie. bytes, errors, packets)
|
|
db_execute_prepared('REPLACE INTO data_input_data
|
|
(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$id,
|
|
$data_template_data_id,
|
|
$data_template['id'],
|
|
$cache_array['local_data_id'][$data_template['id']],
|
|
$host_id,
|
|
$checked,
|
|
$snmp_query_array['snmp_query_graph_id']
|
|
]
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
api_reapply_suggested_data_source_data($cache_array['local_data_id'][$data_template['id']]);
|
|
|
|
// now that we have put data into the 'data_input_data' table, update the snmp cache for ds's
|
|
update_data_source_data_query_cache($cache_array['local_data_id'][$data_template['id']]);
|
|
}
|
|
|
|
// suggested values: data source, passed from parent
|
|
if (isset($suggested_vals[$graph_template_id]['data_template'][$data_template['id']])) {
|
|
foreach ($suggested_vals[$graph_template_id]['data_template'][$data_template['id']] as $field_name => $field_value) {
|
|
db_execute_prepared("UPDATE data_template_data
|
|
SET $field_name = ?
|
|
WHERE local_data_id = ?",
|
|
[$field_value, $cache_array['local_data_id'][$data_template['id']]]);
|
|
}
|
|
}
|
|
|
|
// suggested values: data source item, passed from parent
|
|
if (isset($suggested_vals[$graph_template_id]['data_template_item'])) {
|
|
foreach ($suggested_vals[$graph_template_id]['data_template_item'] as $data_template_item_id => $field_array) {
|
|
foreach ($field_array as $field_name => $field_value) {
|
|
$data_source_item_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template_rrd
|
|
WHERE local_data_template_rrd_id = ?
|
|
AND local_data_id = ?',
|
|
[$data_template_item_id, $cache_array['local_data_id'][$data_template['id']]]);
|
|
|
|
db_execute_prepared("UPDATE data_template_rrd
|
|
SET $field_name = ?
|
|
WHERE id = ?",
|
|
[$field_value, $data_source_item_id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// suggested values: custom data, passed from parent
|
|
if (isset($suggested_vals[$graph_template_id]['custom_data'][$data_template['id']])) {
|
|
foreach ($suggested_vals[$graph_template_id]['custom_data'][$data_template['id']] as $data_input_field_id => $field_value) {
|
|
db_execute_prepared('REPLACE INTO data_input_data
|
|
(data_input_field_id, data_template_data_id, data_template_id, local_data_id, host_id, t_value, value)
|
|
VALUES (?, ?, ?, ?, ?, "", ?)',
|
|
[
|
|
$data_input_field_id,
|
|
$data_template_data_id,
|
|
$data_template['id'],
|
|
$cache_array['local_data_id'][$data_template['id']],
|
|
$host_id,
|
|
$field_value
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
update_data_source_title_cache($cache_array['local_data_id'][$data_template['id']]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// connect the dots: graph -> data source(s)
|
|
graph_template_connect_task_items($graph_template_id, $cache_array);
|
|
|
|
// this will not work until the ds->graph dots are connected
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
if (isset($cache_array['local_graph_id'])) {
|
|
update_graph_data_query_cache($cache_array['local_graph_id']);
|
|
}
|
|
}
|
|
|
|
update_graph_title_cache($cache_array['local_graph_id']);
|
|
|
|
// now that we have the id of the new host, we may plugin postprocessing code
|
|
if (isset($cache_array['local_graph_id'])) {
|
|
$save['id'] = $cache_array['local_graph_id'];
|
|
$save['graph_template_id'] = $graph_template_id; // attention: unset!
|
|
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
$save['snmp_query_id'] = $snmp_query_array['snmp_query_id'];
|
|
$save['snmp_index'] = $snmp_query_array['snmp_index'];
|
|
$save['snmp_query_graph_id'] = $snmp_query_array['snmp_query_graph_id'];
|
|
} else {
|
|
$save['snmp_query_id'] = 0;
|
|
$save['snmp_index'] = 0;
|
|
$save['snmp_query_graph_id'] = 0;
|
|
}
|
|
|
|
// provide automation services
|
|
automation_hook_graph_create_tree($save);
|
|
|
|
api_plugin_hook_function('create_complete_graph_from_template', $save);
|
|
}
|
|
|
|
return $cache_array;
|
|
}
|
|
|
|
/**
|
|
* create_graph_custom_data_compatible - checks to see if this graphs data sources have custom
|
|
* data source properties. If so, then you must duplicate the Data Source and not use
|
|
* the existing one.
|
|
*
|
|
* @param array $suggested_vals Any additional information to be included in the new graphs or
|
|
* data sources must be included in the array. data is to be included in the following format:
|
|
*
|
|
* $values['cg'][graph_template_id]['graph_template'][field_name] = $value // graph template
|
|
* $values['cg'][graph_template_id]['graph_template_item'][graph_template_item_id][field_name] = $value // graph template item
|
|
* $values['cg'][data_template_id]['data_template'][field_name] = $value // data template
|
|
* $values['cg'][data_template_id]['data_template_item'][data_template_item_id][field_name] = $value // data template item
|
|
* $values['sg'][data_query_id][graph_template_id]['graph_template'][field_name] = $value // graph template (w/ data query)
|
|
* $values['sg'][data_query_id][graph_template_id]['graph_template_item'][graph_template_item_id][field_name] = $value // graph template item (w/ data query)
|
|
* $values['sg'][data_query_id][data_template_id]['data_template'][field_name] = $value // data template (w/ data query)
|
|
* $values['sg'][data_query_id][data_template_id]['data_template_item'][data_template_item_id][field_name] = $value // data template item (w/ data query)
|
|
*
|
|
* @param array $previous_data_source The data source to compare to
|
|
*
|
|
* @return bool
|
|
*/
|
|
function create_graph_custom_data_compatible(mixed $suggested_vals, array $previous_data_source) : bool {
|
|
$compatible = true;
|
|
|
|
if (!cacti_sizeof($suggested_vals)) {
|
|
return $compatible;
|
|
}
|
|
|
|
foreach ($suggested_vals as $items) {
|
|
if (cacti_sizeof($items)) {
|
|
foreach ($items as $type => $item) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Type: "' . $type . '"', false, 'DSTRACE');
|
|
cacti_log('Item Keys: "' . implode('", "', array_keys($item)) . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
foreach ($item as $key => $values) {
|
|
if ($type == 'data_template') {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Element Key1: "' . $key . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (is_array($values)) {
|
|
foreach ($values as $vkey => $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Sub Element Key: "' . $vkey . '"', false, 'DSTRACE');
|
|
cacti_log('Item Sub Element Value: "' . $vvalue . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$previous_value = db_fetch_cell_prepared("SELECT `$vkey`
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?
|
|
AND `$vkey` = ?",
|
|
[$previous_data_source['id'], $vvalue]);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Previous Sub Element Value: "' . $previous_value . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if ($previous_value != $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Not Compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} elseif ($type == 'data_template_item') {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Element Key2: "' . $key . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (is_array($values)) {
|
|
foreach ($values as $vkey => $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Sub Element Key: "' . $vkey . '"', false, 'DSTRACE');
|
|
cacti_log('Item Sub Element Value: "' . $vvalue . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$previous_value = db_fetch_cell_prepared("SELECT `$vkey`
|
|
FROM data_template_rrd
|
|
WHERE local_data_id = ?
|
|
AND `$vkey` = ?",
|
|
[$previous_data_source['id'], $vvalue]);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Previous Sub Element Value: "' . $previous_value . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if ($previous_value != $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Not Compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} elseif ($type == 'custom_data') {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Element Key3: "' . $key . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (is_array($values)) {
|
|
foreach ($values as $vkey => $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Sub Element Key: "' . $vkey . '"', false, 'DSTRACE');
|
|
cacti_log('Item Sub Element Value: "' . $vvalue . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$data_template_data_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?',
|
|
[$previous_data_source['id']]);
|
|
|
|
$previous_value = db_fetch_cell_prepared('SELECT value
|
|
FROM data_input_data
|
|
WHERE data_template_data_id = ?
|
|
AND data_input_field_id = ?
|
|
AND value = ?',
|
|
[$data_template_data_id, $vkey, $vvalue]);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Previous Sub Element Value: "' . $previous_value . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if ($previous_value != $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Not Compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} elseif (is_numeric($type)) {
|
|
if (cacti_sizeof($item)) {
|
|
foreach ($item as $ftype => $fitem) {
|
|
if ($ftype == 'data_template') {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Element Key4: "' . $key . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (is_array($fitem)) {
|
|
foreach ($fitem as $vkey => $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Sub Element Key: "' . $vkey . '"', false, 'DSTRACE');
|
|
cacti_log('Item Sub Element Value: "' . $vvalue . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$previous_value = db_fetch_cell_prepared("SELECT `$vkey`
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?
|
|
AND `$vkey` = ?",
|
|
[$previous_data_source['id'], $vvalue]);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Previous Sub Element Value: "' . $previous_value . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if ($previous_value != $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Not Compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} elseif ($ftype == 'data_template_item') {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Element Key5: "' . $key . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (is_array($fitem)) {
|
|
foreach ($fitem as $vkey => $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Sub Element Key: "' . $vkey . '"', false, 'DSTRACE');
|
|
cacti_log('Item Sub Element Value: "' . $vvalue . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$previous_value = db_fetch_cell_prepared("SELECT `$vkey`
|
|
FROM data_template_rrd
|
|
WHERE local_data_id = ?
|
|
AND `$vkey` = ?",
|
|
[$previous_data_source['id'], $vvalue]);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Previous Sub Element Value: "' . $previous_value . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if ($previous_value != $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Not Compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} elseif ($ftype == 'custom_data') {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Element Key6: "' . $key . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if (is_array($fitem)) {
|
|
foreach ($fitem as $vkey => $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Item Sub Element Key: "' . $vkey . '"', false, 'DSTRACE');
|
|
cacti_log('Item Sub Element Value: "' . $vvalue . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$data_template_data_id = db_fetch_cell_prepared('SELECT id
|
|
FROM data_template_data
|
|
WHERE local_data_id = ?',
|
|
[$previous_data_source['id']]);
|
|
|
|
$previous_value = db_fetch_cell_prepared('SELECT value
|
|
FROM data_input_data
|
|
WHERE data_template_data_id = ?
|
|
AND data_input_field_id = ?
|
|
AND value = ?',
|
|
[$data_template_data_id, $vkey, $vvalue]);
|
|
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Previous Sub Element Value: "' . $previous_value . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
if ($previous_value != $vvalue) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Not Compatible', false, 'DSTRACE');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $compatible;
|
|
}
|
|
|
|
function create_save_graph(int $host_id, string $form_type, int $form_id1, array $form_array2, mixed $values) : void {
|
|
// ================= input validation =================
|
|
input_validate_input_number($form_id1, 'form_id1');
|
|
// ====================================================
|
|
|
|
$graph_template_id = 0;
|
|
$snmp_query_array = [];
|
|
$snmp_index_array = [];
|
|
|
|
if ($form_type == 'cg') {
|
|
$graph_template_id = $form_id1;
|
|
} elseif ($form_type == 'sg') {
|
|
foreach ($form_array2 as $form_id2 => $form_array3) {
|
|
// ================= input validation =================
|
|
input_validate_input_number($form_id2, 'form_id2');
|
|
// ====================================================
|
|
|
|
$snmp_index_array = $form_array3;
|
|
|
|
$snmp_query_array['snmp_query_id'] = $form_id1;
|
|
$snmp_query_array['snmp_index_on'] = get_best_data_query_index_type($host_id, $form_id1);
|
|
$snmp_query_array['snmp_query_graph_id'] = $form_id2;
|
|
}
|
|
|
|
$graph_template_id = db_fetch_cell_prepared('SELECT graph_template_id
|
|
FROM snmp_query_graph
|
|
WHERE id = ?',
|
|
[$snmp_query_array['snmp_query_graph_id']]);
|
|
}
|
|
|
|
if ($form_type == 'cg') {
|
|
$snmp_query_array = [];
|
|
|
|
if (isset($values['cg'][$graph_template_id])) {
|
|
$params1 = $values['cg'][$graph_template_id];
|
|
} else {
|
|
$params1 = [];
|
|
}
|
|
|
|
if (isset($values['cg'])) {
|
|
$params2 = $values['cg'];
|
|
} else {
|
|
$params2 = [];
|
|
}
|
|
|
|
if (test_data_sources($graph_template_id, $host_id, 0, '', $params1)) {
|
|
$return_array = create_complete_graph_from_template($graph_template_id, $host_id, $snmp_query_array, $params2);
|
|
|
|
if ($return_array !== false) {
|
|
debug_log_insert('new_graphs', __esc('Created: %s', get_graph_title($return_array['local_graph_id'])));
|
|
|
|
// lastly push host-specific information to our data sources
|
|
if (cacti_sizeof($return_array['local_data_id'])) { // we expect at least one data source associated
|
|
foreach ($return_array['local_data_id'] as $item) {
|
|
push_out_host($host_id, $item);
|
|
}
|
|
} else {
|
|
debug_log_insert('new_graphs', __esc('ERROR: No Data Source associated. Check Template'));
|
|
}
|
|
|
|
db_execute_prepared('INSERT IGNORE INTO host_graph
|
|
(host_id, graph_template_id)
|
|
VALUES(?, ?)',
|
|
[$host_id, $graph_template_id]);
|
|
} else {
|
|
debug_log_insert('new_graphs', __esc('ERROR: Whitelist Validation Failed. Check Data Input Method'));
|
|
}
|
|
} else {
|
|
$name = db_fetch_cell_prepared('SELECT name
|
|
FROM graph_templates
|
|
WHERE id = ?',
|
|
[$graph_template_id]);
|
|
|
|
debug_log_insert('new_graphs', __esc('Graph Not created for %s due to bad data', $name));
|
|
}
|
|
} elseif ($form_type == 'sg') {
|
|
foreach ($snmp_index_array as $snmp_index => $true) {
|
|
$snmp_query_array['snmp_index'] = decode_data_query_index($snmp_index, $snmp_query_array['snmp_query_id'], $host_id);
|
|
|
|
if (test_data_sources($graph_template_id, $host_id, $snmp_query_array['snmp_query_id'], $snmp_query_array['snmp_index'])) {
|
|
$return_array = create_complete_graph_from_template($graph_template_id, $host_id, $snmp_query_array, $values['sg'][$snmp_query_array['snmp_query_id']]);
|
|
|
|
if ($return_array !== false) {
|
|
debug_log_insert('new_graphs', __esc('Created: %s', get_graph_title($return_array['local_graph_id'])));
|
|
|
|
// lastly push host-specific information to our data sources
|
|
if (cacti_sizeof($return_array['local_data_id'])) { // we expect at least one data source associated
|
|
foreach ($return_array['local_data_id'] as $item) {
|
|
push_out_host($host_id, $item);
|
|
}
|
|
} else {
|
|
debug_log_insert('new_graphs', __esc('ERROR: No Data Source associated. Check Template'));
|
|
}
|
|
} else {
|
|
debug_log_insert('new_graphs', __esc('ERROR: Whitelist Validation Failed. Check Data Input Method'));
|
|
}
|
|
} else {
|
|
$name = db_fetch_cell_prepared('SELECT name
|
|
FROM snmp_query
|
|
WHERE id = ?',
|
|
[$snmp_query_array['snmp_query_id']]);
|
|
|
|
debug_log_insert('new_graphs', __esc('NOTE: Graph not added for Data Query %s and index %s due to Data Source verification failure', $name, $snmp_query_array['snmp_index']));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function data_source_exists(int $graph_template_id, int $host_id, array &$data_template, array &$snmp_query_array) : mixed {
|
|
if (cacti_sizeof($snmp_query_array)) {
|
|
$input_fields = db_fetch_cell_prepared('SELECT
|
|
GROUP_CONCAT(DISTINCT snmp_field_name ORDER BY snmp_field_name) AS input_fields
|
|
FROM snmp_query_graph_rrd
|
|
WHERE snmp_query_graph_id = ?',
|
|
[$snmp_query_array['snmp_query_graph_id']]);
|
|
|
|
$find_data_source_names = db_fetch_cell_prepared('SELECT
|
|
GROUP_CONCAT(DISTINCT data_source_name ORDER BY data_source_name) AS data_source_names
|
|
FROM data_template_rrd AS dtr
|
|
INNER JOIN snmp_query_graph_rrd AS sqgr
|
|
ON sqgr.data_template_id = dtr.data_template_id
|
|
WHERE dtr.data_template_id = ?
|
|
AND dtr.local_data_id = 0',
|
|
[$data_template['id']]);
|
|
|
|
// Interfaces are a special case where we can change from 32 to 64 bits on the fly
|
|
if (str_contains($input_fields, 'ifHCInOctets,ifHCOutOctets') ||
|
|
str_contains($input_fields, 'ifInOctets,ifOutOctets')) {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Data Source Exists Special Case "' . $input_fields . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$case_where = 'AND input_fields LIKE ?';
|
|
$case_array = [
|
|
$host_id,
|
|
$data_template['id'],
|
|
$snmp_query_array['snmp_query_id'],
|
|
$snmp_query_array['snmp_index'],
|
|
'%' . $input_fields . '%',
|
|
$find_data_source_names
|
|
];
|
|
} else {
|
|
if (read_config_option('data_source_trace') == 'on') {
|
|
cacti_log('Data Source Exists NOT Special Case "' . $input_fields . '"', false, 'DSTRACE');
|
|
}
|
|
|
|
$case_where = 'AND input_fields = ?';
|
|
$case_array = [
|
|
$host_id,
|
|
$data_template['id'],
|
|
$snmp_query_array['snmp_query_id'],
|
|
$snmp_query_array['snmp_index'],
|
|
$input_fields,
|
|
$find_data_source_names
|
|
];
|
|
}
|
|
|
|
return db_fetch_row_prepared("SELECT dl.*,
|
|
GROUP_CONCAT(DISTINCT snmp_field_name ORDER BY snmp_field_name) AS input_fields,
|
|
GROUP_CONCAT(DISTINCT data_source_name ORDER BY data_source_name) AS data_source_names
|
|
FROM data_local AS dl
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dl.id = dtd.local_data_id
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dtd.local_data_id = dtr.local_data_id
|
|
INNER JOIN data_input_fields AS dif
|
|
ON dif.data_input_id = dtd.data_input_id
|
|
INNER JOIN snmp_query_graph_rrd AS sqgr
|
|
ON sqgr.data_template_id = dtd.data_template_id
|
|
WHERE input_output = 'in'
|
|
AND type_code = 'output_type'
|
|
AND dl.host_id = ?
|
|
AND dl.data_template_id = ?
|
|
AND dl.snmp_query_id = ?
|
|
AND dl.snmp_index = ?
|
|
GROUP BY dtd.local_data_id
|
|
HAVING local_data_id IS NOT NULL
|
|
$case_where
|
|
AND data_source_names = ?",
|
|
$case_array
|
|
);
|
|
} else {
|
|
// create each data source, but don't duplicate
|
|
$data_source = db_fetch_row_prepared('SELECT dl.*
|
|
FROM data_local AS dl
|
|
INNER JOIN data_template_rrd AS dtr
|
|
ON dtr.data_template_id = dl.data_template_id
|
|
AND dtr.local_data_id = dl.id
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON gti.task_item_id = dtr.id
|
|
WHERE dl.host_id = ?
|
|
AND dl.data_template_id = ?
|
|
AND dtr.data_source_name = ?
|
|
AND gti.graph_template_id = ?
|
|
LIMIT 1',
|
|
[$host_id, $data_template['id'], $data_template['data_source_name'], $graph_template_id]);
|
|
|
|
return $data_source;
|
|
}
|
|
}
|
|
|
|
function verify_data_input(string $hash, string $input_string) : array {
|
|
$input = db_fetch_row_prepared('SELECT *
|
|
FROM data_input
|
|
WHERE hash = ?',
|
|
[$hash]);
|
|
|
|
if ($input['input_string'] == $input_string) {
|
|
$input['status'] = true;
|
|
|
|
return $input;
|
|
} else {
|
|
$input['status'] = false;
|
|
|
|
return $input;
|
|
}
|
|
}
|
|
|
|
function verify_data_input_whitelist(string $hash, string $input_string) : mixed {
|
|
if (!defined('CACTI_WHITELIST')) {
|
|
return true;
|
|
}
|
|
|
|
if (!file_exists(CACTI_WHITELIST)) {
|
|
return true;
|
|
}
|
|
|
|
$whitelist = json_decode((string) file_get_contents(CACTI_WHITELIST), true);
|
|
|
|
if (isset($whitelist[$hash])) {
|
|
if ($input_string == $whitelist[$hash]) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
function graph_template_whitelist_check(int $graph_template_id) : bool {
|
|
static $data_input_whitelist = null;
|
|
static $notified = [];
|
|
|
|
// no whitelist file defined, everything whitelisted
|
|
if (!defined('CACTI_WHITELIST')) {
|
|
return true;
|
|
}
|
|
|
|
// whitelist is configured but does not exist, means nothing whitelisted
|
|
if (!file_exists(CACTI_WHITELIST)) {
|
|
return false;
|
|
}
|
|
|
|
// load whitelist, but only once within process execution
|
|
if ($data_input_whitelist == null) {
|
|
$data_input_whitelist = json_decode((string) file_get_contents(CACTI_WHITELIST), true);
|
|
|
|
if ($data_input_whitelist === null) {
|
|
cacti_log('ERROR: Failed to parse input whitelist file: ' . CACTI_WHITELIST);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$valid = true;
|
|
|
|
$data_input_ids = db_fetch_assoc_prepared('SELECT DISTINCT dtd.data_input_id
|
|
FROM data_template_rrd AS dtr
|
|
INNER JOIN data_template_data AS dtd
|
|
ON dtr.data_template_id=dtd.data_template_id
|
|
INNER JOIN graph_templates_item AS gti
|
|
ON gti.task_item_id=dtr.id
|
|
WHERE gti.graph_template_id = ?
|
|
AND gti.local_graph_id = 0',
|
|
[$graph_template_id]);
|
|
|
|
if (cacti_sizeof($data_input_ids)) {
|
|
foreach ($data_input_ids as $dii) {
|
|
$found = false;
|
|
$data_input = db_fetch_row_prepared('SELECT *
|
|
FROM data_input
|
|
WHERE id = ?',
|
|
[$dii['data_input_id']]);
|
|
|
|
if (isset($data_input_whitelist[$data_input['hash']])) {
|
|
$found = true;
|
|
|
|
if ($data_input_whitelist[$data_input['hash']] != $data_input['input_string']) {
|
|
$valid = false;
|
|
}
|
|
}
|
|
|
|
if (!$found && !isset($notified[$dii['data_input_id']])) {
|
|
cacti_log('WARNING: Data input script not found in input whitelist file: ' . CACTI_WHITELIST);
|
|
$notified[$dii['data_input_id']] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $valid;
|
|
}
|