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 * 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('', $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 * 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('', $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, __esc('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, __esc('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 // "