0) { // override selected fields from template data with aggregate template data $aggregate_data = db_fetch_row_prepared('SELECT * FROM aggregate_graph_templates_graph WHERE aggregate_template_id = ?', [$aggregate_template_id]); if (cacti_sizeof($aggregate_data)) { foreach ($aggregate_data as $field => $value) { if (str_starts_with($field, 't_') && $value == 'on') { $value_field_name = substr($field, 2); $template_data[$value_field_name] = $aggregate_data[$value_field_name]; } } } } if (cacti_sizeof($existing_data) == 0) { // this is a new graph, use template data $graph_data = $template_data; $graph_data['id'] = 0; $graph_data['title'] = $graph_title; $graph_data['title_cache'] = $graph_title; } elseif ($aggregate_template_id > 0) { /* this graph exists and is templated from aggregate template, * use template data */ $graph_data = $template_data; $graph_data['id'] = $existing_data['id']; $graph_data['title'] = $existing_data['title']; $graph_data['title_cache'] = $existing_data['title_cache']; } else { /* this is an existing graph and not templated from aggregate, * reuse its old data */ $graph_data = $existing_data; } if ($aggregate_template_id == 0) { // now use new data $graph_data = array_merge($graph_data, $new_data); } // safety check - don't allow empty titles if ($graph_title != '') { $graph_data['title'] = $graph_title; $graph_data['title_cache'] = $graph_title; } $graph_data['auto_padding'] = 'on'; $graph_data['local_graph_id'] = $local_graph_id; $graph_data['local_graph_template_graph_id'] = 0; // no templating $graph_data['graph_template_id'] = 0; // no templating $graph_templates_graph_id = sql_save($graph_data, 'graph_templates_graph'); // update title cache if (!empty($graph_templates_graph_id)) { update_graph_title_cache($local_graph_id); } return $graph_templates_graph_id; } /** * Inserts graph items from an old graph into a new graph. * * @param int $_new_graph_id The ID of the new graph. * @param int $_old_graph_id The ID of the old graph. * @param int $_graph_template_id The ID of the graph template. * @param array $_skip Array of items to skip. * @param array $_totali Array of total items. * @param int $_graph_item_sequence The sequence number of the graph item. * @param int $_selected_graph_index The index of the selected graph. * @param array $_color_templates Array of color templates. * @param array $_graph_item_types Array of graph item types. * @param array $_cdefs Array of CDEFs. * @param int $_graph_type The type of the graph. * @param string $_gprint_prefix The prefix for GPRINT. * @param string $_gprint_format The format for GPRINT. * @param int $_total The total value. * @param string $_total_type The type of the total (default is empty string). * @param array $member_graphs Array of member graphs (default is empty array). * * @return int The next sequence number to be filled. */ function aggregate_graphs_insert_graph_items(int $_new_graph_id, int $_old_graph_id, int $_graph_template_id, array $_skip, array $_totali, int $_graph_item_sequence, int $_selected_graph_index, array $_color_templates, array $_graph_item_types, array $_cdefs, int $_graph_type, string $_gprint_prefix, string $_gprint_format, int $_total, string $_total_type = '', array $member_graphs = []) : int { global $struct_graph_item, $graph_item_types; // Remove filter item unset($struct_graph_item['data_template_id']); include_once(CACTI_PATH_LIBRARY . '/api_aggregate.php'); // suppress warnings error_reporting(E_ALL); // install own error handler set_error_handler('aggregate_error_handler'); cacti_log(__FUNCTION__ . ' called. Insert example graph:' . $_old_graph_id . ' Graph Template:' . $_graph_template_id . ' into Graph:' . $_new_graph_id . ' at Sequence:' . $_graph_item_sequence . ' Graph_No:' . $_selected_graph_index . ' Type Action: ' . $_graph_type, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); cacti_log(__FUNCTION__ . ' skipping: ' . serialize($_skip), true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); // take graph item data from old one if (!empty($_old_graph_id)) { $graph_items = db_fetch_assoc_prepared('SELECT * FROM graph_templates_item WHERE local_graph_id = ? ORDER BY sequence', [$_old_graph_id]); $graph_local = db_fetch_row_prepared('SELECT host_id, graph_template_id, snmp_query_id, snmp_index FROM graph_local WHERE id = ?', [$_old_graph_id]); } else { $graph_items = db_fetch_assoc_prepared('SELECT * FROM graph_templates_item WHERE local_graph_id = ? AND graph_template_id = ? ORDER BY sequence', [$_old_graph_id, $_graph_template_id]); $graph_local = []; } // create new entry(s): graph_templates_item $num_items = cacti_sizeof($graph_items); $_hr = []; if ($num_items > 0) { // take care of items having a HR that shall be skipped $i = 0; for ($i; $i < $num_items; $i++) { // remember existing hard returns (array must start at 1 to match $skipped_items $_hr[$i + 1] = ($graph_items[$i]['hard_return'] != ''); } // move 'skipped hard returns' to previous graph item $_hr = auto_hr($_skip, $_hr); // next entry will have to have a prepended text format $prepend = true; $prepend_cnt = 0; $skip_graph = false; $make0_cdef = aggregate_cdef_make0(); $i = 0; foreach ($graph_items as $graph_item) { // loop starts at 0, but $_skip starts at 1, so increment before comparing $i++; // go ahead, if this graph item has to be skipped if (isset($_skip[$i]) && !empty($_skip[$i])) { continue; } // HRULES will only appear in the total one way or nother if ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_HRULE) { continue; } if ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_COMMENT) { if (preg_match('/(:bits:|:bytes:|\|sum:)/', $graph_item['text_format'])) { // Only skip nth percentile COMMENT values $parts = explode('|', $graph_item['text_format']); if (isset($parts[1])) { $pparts = explode(':', $parts[1]); if (is_numeric($pparts[0])) { continue; } } } } if ($_total == AGGREGATE_TOTAL_ONLY) { // if we only need the totalling legend, ... if (($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_LAST) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_MIN) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_MAX) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_GPRINT_AVERAGE) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_TEXTALIGN) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_TIC) || ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_COMMENT)) { // and this is a legend entry (GPRINT, COMMENT), skip continue; } else { // this is a graph entry, remove text to make it disappear // do NOT skip! // we need this entry as a DEF // and as long as cacti does not provide for a 'pure DEF' graph item type // we need this workaround $graph_item['text_format'] = ''; // make sure, that this entry does not have a HR, // else a single colored mark will be drawn $graph_item['hard_return'] = ''; $_hr[$i] = ''; // make sure, that data of this item will be suppressed: make 0! $graph_item['cdef_id'] = $make0_cdef; // try to pick the best totaling cf id $graph_item['consolidation_function_id'] = db_fetch_cell('SELECT consolidation_function_id FROM graph_templates_item WHERE color_id > 0 AND sequence = ' . $graph_item['sequence'] . (cacti_sizeof($member_graphs) ? ' AND ' . array_to_sql_or($member_graphs, 'local_graph_id') : '') . (cacti_sizeof($_skip) ? ' AND sequence NOT IN (' . implode(',', $_skip) . ')' : '') . (cacti_sizeof($_totali) ? ' AND sequence IN (' . implode(',', $_totali) . ')' : '') . ' ORDER BY sequence ASC LIMIT 1'); } } elseif ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_AREA || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE1 || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE2 || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE3 || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_STACK) { if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) { $graph_item['text_format'] = $_gprint_prefix; } elseif ($_gprint_format != '') { $graph_item['text_format'] = $_gprint_prefix . ' ' . $graph_item['text_format']; } else { $graph_item['text_format'] = $_gprint_prefix; } } // use all data from 'old' graph ... $save = $graph_item; // now it's time for some 'special purpose' processing // selected fields will need special treatment // take care of color changes only if not set to None if (isset($_color_templates[$i])) { if ($_color_templates[$i] > 0) { // get the size of the color templates array // if number of colored items exceed array size, use round robin $num_colors = db_fetch_cell_prepared('SELECT COUNT(color_id) FROM color_template_items WHERE color_template_id = ?', [$_color_templates[$i]]); // a color template with no items counts zero, and the round // robin below divides by that count. PHP 8 raises // DivisionByZeroError where PHP 7 warned and carried on. if ($num_colors > 0) { // templating required, get color for current graph item. // The id is bound, as the count above already does; the // offset is arithmetic on two integers, which no // placeholder is needed for and MySQL will not bind in // a LIMIT on every supported version. $offset = $_selected_graph_index % $num_colors; $save['color_id'] = db_fetch_cell_prepared('SELECT color_id FROM color_template_items WHERE color_template_id = ? ORDER BY sequence LIMIT ' . $offset . ',1', [$_color_templates[$i]]); } else { $save['color_id'] = $graph_item['color_id']; } } else { // set a color even if no color templating is required $save['color_id'] = $graph_item['color_id']; } } // else: no color templating defined, e.g. GPRINT entry // do we want to override cdef of this item // certainly not if it was set to $make0_cdef above if ($_cdefs[$i] > 0 && $graph_item['cdef_id'] != $make0_cdef) { $save['cdef_id'] = $_cdefs[$i]; } // take care of the graph_item_type // user may want to override types (ex. LINEx to AREA) // do this before we try start converting stuff to AREAs and such if ($_graph_item_types[$i] > 0) { $save['graph_type_id'] = $_graph_item_types[$i]; } else { $save['graph_type_id'] = $graph_item['graph_type_id']; } // change graph types, if requested $save['graph_type_id'] = aggregate_change_graph_type($_selected_graph_index, $save['graph_type_id'], $_graph_type); // new item text format required? if ($_total_type != '') { if ($prepend) { if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL && $prepend_cnt > 0) { continue; } // pointless to add any data source item name here, cause ALL are totaled if ($_gprint_format != '') { $save['text_format'] = $graph_item['text_format']; } else { $save['text_format'] = ''; } // no more prepending until next line break is encountered $prepend = false; $prepend_cnt++; } elseif (str_contains($save['text_format'], ':current:')) { if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) { $save['text_format'] = str_replace(':current:', ':aggregate_sum:', $save['text_format']); } elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) { $save['text_format'] = str_replace(':current:', ':aggregate_current:', $save['text_format']); } else { cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for :current: in text_format', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); } } elseif (str_contains($save['text_format'], ':max:')) { if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) { $save['text_format'] = str_replace(':max:', ':aggregate_sum_peak:', $save['text_format']); } elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) { $save['text_format'] = str_replace(':max:', ':aggregate_current_peak:', $save['text_format']); } else { cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for :max: in text_format', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); } } } if ($save['text_format'] != '') { $save['text_format'] = substitute_host_data($save['text_format'], '|', '|', ($graph_local['host_id'] ?? 0)); cacti_log(__FUNCTION__ . ' substituted:' . $save['text_format'], true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); // if this is a data query graph type, try to substitute if (isset($graph_local['snmp_query_id']) && $graph_local['snmp_query_id'] > 0 && $graph_local['snmp_index'] != '') { $max_chars = intval(read_config_option('max_data_query_field_length')); $save['text_format'] = substitute_snmp_query_data($save['text_format'], $graph_local['host_id'], $graph_local['snmp_query_id'], $graph_local['snmp_index'], $max_chars); cacti_log(__FUNCTION__ . ' substituted:' . $save['text_format'] . ' for ' . $graph_local['host_id'] . ',' . $graph_local['snmp_query_id'] . ',' . $graph_local['snmp_index'], true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); } } //
wanted? if (isset($_hr[$i]) && $_hr[$i] > 0) { $save['hard_return'] = 'on'; } // if this item defines a line break, remember to prepend next line if ($save['text_format'] != '') { $prepend = ($save['hard_return'] == 'on'); } // provide new sequence number $save['sequence'] = $_graph_item_sequence; cacti_log(__FUNCTION__ . ' hard return: ' . $save['hard_return'] . ' sequence: ' . $_graph_item_sequence, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); $save['id'] = 0; $save['local_graph_template_item_id'] = 0; // disconnect this graph item from the graph template item $save['local_graph_id'] = $_new_graph_id; $save['graph_template_id'] = 0; // disconnect this graph item from the graph template $save['hash'] = ''; // remove any template attribs $graph_item_mappings[$graph_item['id']] = sql_save($save, 'graph_templates_item'); $_graph_item_sequence++; } } // restore original error handler restore_error_handler(); // return with next sequence number to be filled return $_graph_item_sequence; } /** * Insert or update aggregate graph items in DB tables * * @param array $items * @param string $table * @return bool true if save was successful, false otherwise */ function aggregate_graph_items_save(array $items, string $table) : bool { $defaults = []; if ($table == 'aggregate_graphs_graph_item') { $defaults['aggregate_graph_id'] = null; $id_field = 'aggregate_graph_id'; } elseif ($table == 'aggregate_graph_templates_item') { $defaults['aggregate_template_id'] = null; $id_field = 'aggregate_template_id'; } else { return false; } $defaults['graph_templates_item_id'] = null; $defaults['sequence'] = 0; $defaults['color_template'] = 0; $defaults['t_graph_type_id'] = ''; $defaults['graph_type_id'] = 0; $defaults['t_cdef_id'] = ''; $defaults['cdef_id'] = 0; $defaults['item_skip'] = ''; $defaults['item_total'] = ''; $items_sql = []; foreach ($items as $item) { // substitute any missing fields with defaults $item = array_merge($defaults, $item); // remove any extra fields $item = array_intersect_key($item, $defaults); // without these graph item makes no sense if (!isset($item[$id_field]) || !isset($item['graph_templates_item_id'])) { return false; } // convert to partial SQL statement $items_sql[] = sprintf( ' (%d, %d, %d, %d, %s, %d, %s, %d, %s, %s)', $item[$id_field], $item['graph_templates_item_id'], $item['sequence'], $item['color_template'], db_qstr($item['t_graph_type_id']), $item['graph_type_id'], db_qstr($item['t_cdef_id']), $item['cdef_id'], db_qstr($item['item_skip']), db_qstr($item['item_total']) ); } $sql = 'INSERT INTO ' . $table; $sql .= '(' . implode(', ', array_keys($defaults)) . ') VALUES '; $sql .= implode(', ', $items_sql); cacti_log(__FUNCTION__ . ' called. SQL: ' . $sql, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); // remove all old items if (isset($items[0][$id_field])) { db_execute("DELETE FROM $table WHERE " . $id_field . '=' . $items[0][$id_field]); } if (db_execute($sql) == 1) { return true; } else { return false; } } /** * Validate extra graph parameters posted from graph edit form. * You can check for validation errors with cacti function is_error_message * * @param array $posted values posted from form * @param bool $has_override form had override checkboxes * * @return array - cleaned up graph parameters */ function aggregate_validate_graph_params(array $posted, bool $has_override = false) : array { $check_post_params = [ 'alt_y_grid' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'auto_padding' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'auto_scale' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'auto_scale_log' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'auto_scale_opts' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => ''], 'auto_scale_rigid' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'base_value' => ['type' => 'int', 'allow_empty' => true, 'default' => 0, 'regex' => '^[0-9]+$'], 'dynamic_labels' => ['type' => 'str', 'allow_empty' => true, 'default' => 0, 'regex' => ''], 'force_rules_legend' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'grouping' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'height' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => '^[0-9]+$'], 'image_format_id' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => ''], 'left_axis_format' => ['type' => 'int', 'allow_empty' => true, 'default' => '', 'regex' => '^[0-9]+$'], 'left_axis_formatter' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'legend_direction' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'legend_position' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'lower_limit' => ['type' => 'int', 'allow_empty' => true, 'default' => 0, 'regex' => '^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?)|U$'], 'no_gridfit' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'right_axis' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'right_axis_format' => ['type' => 'int', 'allow_empty' => true, 'default' => '', 'regex' => '^[0-9]+$'], 'right_axis_formatter' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'right_axis_label_angle' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'right_axis_range' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'vertical_label_angle' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'right_axis_label' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'scale_log_units' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'slope_mode' => ['type' => 'bool', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'tab_width' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => '^[0-9]+$'], 'unit_exponent_value' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'unit_length' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'unit_value' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'upper_limit' => ['type' => 'int', 'allow_empty' => true, 'default' => 0, 'regex' => '^(-?([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)([eE][+\-]?[0-9]+)?)|U$'], 'vertical_label' => ['type' => 'str', 'allow_empty' => true, 'default' => '', 'regex' => ''], 'width' => ['type' => 'int', 'allow_empty' => false, 'default' => 0, 'regex' => '^[0-9]+$'] ]; $params_new = []; // validate posted form fields foreach ($check_post_params as $field => $defs) { if ($has_override && !isset($posted['t_' . $field])) { // override checkbox off - use default value $params_new['t_' . $field] = ''; $params_new[$field] = $defs['default']; continue; } if ($has_override) { // override checkbox was on $params_new['t_' . $field] = 'on'; } // validate value if (in_array($field, ['vertical_label_angle', 'right_axis_label_angle', 'right_axis_range'], true)) { $params_new[$field] = CactiValidator::validateRrdGraphAxisInput($posted[$field] ?? '', $field); } elseif ($defs['type'] == 'bool') { $params_new[$field] = (isset($posted[$field])) ? 'on' : ''; } else { $params_new[$field] = (isset($posted[$field]) ? form_input_validate(htmle($posted[$field]), $field, $defs['regex'], $defs['allow_empty'], 3) : $defs['default']); } } return $params_new; } /** * Populate graph items array with posted values. * $graph_items array must be keyed on graph item id. * * @param array $posted Values posted from form * @param array $graph_items Reference to graph items array to update with form values * * @return void */ function aggregate_validate_graph_items(array $posted, array &$graph_items) : void { foreach ($posted as $var => $val) { // work on color_templates if (preg_match('/^agg_color_([0-9]+)$/', $var, $matches)) { // ================= input validation ================= input_validate_input_number($matches[1], 'agg_color'); // ==================================================== $graph_templates_item_id = str_replace('agg_color_', '', $var); if (isset($graph_items[$graph_templates_item_id])) { $graph_items[$graph_templates_item_id]['color_template'] = $val; } else { cacti_log('Something fubar in agg_color'); } } // work on checkboxed for skipping items if (preg_match('/^agg_skip_([0-9]+)$/', $var, $matches)) { // ================= input validation ================= input_validate_input_number($matches[1], 'agg_skip'); // ==================================================== $graph_templates_item_id = str_replace('agg_skip_', '', $var); if (isset($graph_items[$graph_templates_item_id])) { $graph_items[$graph_templates_item_id]['item_skip'] = $val; } else { cacti_log('Something fubar in agg_skip'); } } // work on checkboxed for totalling items if (preg_match('/^agg_total_([0-9]+)$/', $var, $matches)) { // ================= input validation ================= input_validate_input_number($matches[1], 'agg_total'); // ==================================================== $graph_templates_item_id = str_replace('agg_total_', '', $var); if (isset($graph_items[$graph_templates_item_id])) { $graph_items[$graph_templates_item_id]['item_total'] = $val; } else { cacti_log('Something fubar in agg_total'); } } } } /** * Cleans up aggregated graphs. * * @param int $base The base graph ID. * @param int $aggregate The aggregate graph ID. * @param int $reorder The reorder flag. * * @return void */ function aggregate_graphs_cleanup(int $base, int $aggregate, int $reorder) : void { include_once(CACTI_PATH_LIBRARY . '/api_aggregate.php'); cacti_log(__FUNCTION__ . ' called. Base ' . $base . ' Aggregate ' . $aggregate . ' Reorder: ' . $reorder, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); // suppress warnings error_reporting(E_ALL); // install own error handler set_error_handler('aggregate_error_handler'); // restore original error handler restore_error_handler(); } /** * Reorders the data source graph for a given aggregate. * * @param int $base The base ID for the operation. * @param string $graph_template_id The ID of the graph template. * @param int $aggregate The aggregate ID. * @param int $reorder The new order position. * @param int $graph_type The type of the graph. * * @return bool */ function aggregate_reorder_ds_graph(int $base, string $graph_template_id, int $aggregate, int $reorder, int $graph_type) : bool { cacti_log(__FUNCTION__ . ' called. Base Graph ' . $base . ' Graph Template ' . $graph_template_id . ' Aggregate Graph ' . $aggregate . ' Reorder: ' . $reorder, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); // suppress warnings error_reporting(E_ALL); // install own error handler set_error_handler('aggregate_error_handler'); $new_seq = 1; $base_handler = false; $updates = []; // Get the order of items to re-arrange on the graph if ($reorder == AGGREGATE_ORDER_NONE) { restore_error_handler(); return true; } $sql_order = ''; if ($reorder == AGGREGATE_ORDER_DS_GRAPH) { $sql_order = 'dtr.data_source_name, gti.sequence'; } elseif ($reorder == AGGREGATE_ORDER_BASE_GRAPH) { $base_handler = true; } else { $sql_order = 'gtg.title_cache, gti.sequence'; } if ($base_handler) { /* get all different local_data_template_rrd_id's * respecting the order that the aggregated graph has */ $sql_where = "WHERE gti.local_graph_id = $base" . ($base == 0 ? " AND gti.graph_template_id = $graph_template_id" : ''); $sql_id_column = ($base == 0 ? 'id' : 'local_data_template_rrd_id'); $sql = "SELECT DISTINCT dtr.$sql_id_column AS local_data_template_rrd_id FROM data_template_rrd AS dtr LEFT JOIN graph_templates_item AS gti ON gti.task_item_id = dtr.id $sql_where ORDER BY gti.sequence"; $ds_ids = db_fetch_assoc($sql); foreach ($ds_ids as $ds_id) { cacti_log('local_data_template_rrd_id: ' . $ds_id['local_data_template_rrd_id'], false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); /* get all different task_item_id's * respecting the order that the aggregated graph has */ $sql = "SELECT gti.id, gti.task_item_id FROM graph_templates_item AS gti LEFT JOIN data_template_rrd AS dtr ON gti.task_item_id = dtr.id WHERE gti.local_graph_id = $aggregate AND dtr.local_data_template_rrd_id = " . $ds_id['local_data_template_rrd_id'] . ' ORDER BY sequence'; cacti_log(__FUNCTION__ . ' sql: ' . $sql, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); $items = db_fetch_assoc($sql); foreach ($items as $item) { // accumulate the updates to avoid interfering the next loops $updates[] = 'UPDATE graph_templates_item SET sequence = ' . $new_seq++ . ' WHERE id = ' . $item['id']; } } } else { $aggregate_graph_id = db_fetch_cell_prepared('SELECT id FROM aggregate_graphs WHERE local_graph_id = ?', [$aggregate]); $list = array_rekey( db_fetch_assoc_prepared("SELECT DISTINCT dtr.local_data_id FROM data_template_rrd AS dtr INNER JOIN graph_templates_item AS gti ON gti.task_item_id = dtr.id INNER JOIN aggregate_graphs_items AS agi ON gti.local_graph_id = agi.local_graph_id INNER JOIN graph_templates_graph AS gtg ON gtg.local_graph_id = gti.local_graph_id WHERE agi.aggregate_graph_id = ? ORDER BY $sql_order", [$aggregate_graph_id]), 'local_data_id', 'local_data_id' ); if (cacti_sizeof($list)) { $sql_order = 'FIELD(dtr.local_data_id, ' . implode(', ', $list) . '), gti.sequence'; } $sql = "SELECT gti.id, gti.task_item_id, graph_type_id FROM graph_templates_item AS gti LEFT JOIN data_template_rrd AS dtr ON gti.task_item_id = dtr.id WHERE gti.local_graph_id = $aggregate AND dtr.local_data_id IN (" . implode(', ', $list) . ") ORDER BY $sql_order"; if ($reorder != AGGREGATE_ORDER_NONE && $reorder != AGGREGATE_ORDER_DS_GRAPH) { $color_ids = db_fetch_assoc("SELECT color_id FROM graph_templates_item AS gti LEFT JOIN data_template_rrd AS dtr ON gti.task_item_id = dtr.id WHERE gti.local_graph_id = $aggregate AND dtr.local_data_id IN (" . implode(', ', $list) . ') ORDER BY gti.sequence'); } else { $color_ids = []; } cacti_log(__FUNCTION__ . ' sql: ' . $sql, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); $items = db_fetch_assoc($sql); $i = 0; foreach ($items as $item) { $new_graph_type = aggregate_change_graph_type($i, $item['graph_type_id'], $graph_type); if (isset($color_ids[$i])) { $color_id = $color_ids[$i]['color_id']; } else { $color_id = ''; } // accumulate the updates to avoid interfering the next loops $updates[] = 'UPDATE graph_templates_item SET sequence = ' . $new_seq . ", graph_type_id = $new_graph_type " . ($color_id != '' ? ', color_id = ' . $color_id : '') . ' WHERE id = ' . $item['id']; $i++; $new_seq++; } } // now get all 'empty' local_data_template_rrd_id's // = those graph items without associated data source (e.g. COMMENT) $sql = "SELECT id FROM graph_templates_item AS gti WHERE gti.local_graph_id = $aggregate AND gti.task_item_id = 0 ORDER BY sequence"; cacti_log($sql, false, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); $empty_task_items = db_fetch_assoc($sql); // now add those 'empty' one's to the end foreach ($empty_task_items as $item) { // accumulate the updates to avoid interfering the next loops $updates[] = 'UPDATE graph_templates_item SET sequence = ' . $new_seq++ . ' WHERE id = ' . $item['id']; } // now run all updates if (cacti_sizeof($updates)) { foreach ($updates as $update) { cacti_log(__FUNCTION__ . ' update: ' . $update, false, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); db_execute($update); } } // restore original error handler restore_error_handler(); return true; } /** * Pushes out aggregate graph data based on the provided aggregate template ID or local graph ID. * * @param int $aggregate_template_id The ID of the aggregate template to use for generating the graph data. * @param int $local_graph_id The ID of the local graph to use for generating the graph data. Default is 0. * * @return bool True when every selected aggregate was updated. */ function push_out_aggregates(int $aggregate_template_id, int $local_graph_id = 0) : bool { $attribs = []; $attribs['skipped_items'] = []; $attribs['total_items'] = []; $attribs['color_templates'] = []; $attribs['graph_item_types'] = []; $attribs['cdefs'] = []; $member_graphs = []; if ($local_graph_id > 0 && $aggregate_template_id == 0) { $id = db_fetch_cell_prepared('SELECT id FROM aggregate_graphs WHERE local_graph_id = ?', [$local_graph_id]); $attribs['graph_title'] = db_fetch_cell_prepared('SELECT title_format FROM aggregate_graphs WHERE id = ?', [$id]); $attribs['skipped_items'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence FROM aggregate_graphs_graph_item WHERE item_skip="on" AND aggregate_graph_id = ? ORDER BY sequence', [$id]), 'sequence', 'sequence' ); $attribs['total_items'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence FROM aggregate_graphs_graph_item WHERE item_total="on" AND aggregate_graph_id = ? ORDER BY sequence', [$id]), 'sequence', 'sequence' ); $attribs['color_templates'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence, color_template FROM aggregate_graphs_graph_item WHERE color_template>=0 AND aggregate_graph_id = ? ORDER BY sequence', [$id]), 'sequence', 'color_template' ); $attribs['graph_item_types'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence, graph_type_id FROM aggregate_graphs_graph_item WHERE t_graph_type_id="on" AND aggregate_graph_id = ? ORDER BY sequence', [$id]), 'sequence', 'graph_type_id' ); $attribs['cdefs'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence, cdef_id FROM aggregate_graphs_graph_item WHERE t_cdef_id="on" AND aggregate_graph_id = ? ORDER BY sequence', [$id]), 'sequence', 'cdef_id' ); $attribs['aggregate_graph_id'] = $aggregate_template_id; $attribs['template_propogation'] = ''; $template_data = db_fetch_row_prepared('SELECT * FROM aggregate_graphs WHERE id = ?', [$id]); $attribs['graph_template_id'] = $template_data['graph_template_id']; $attribs['gprint_prefix'] = $template_data['gprint_prefix']; $attribs['gprint_format'] = $template_data['gprint_format']; $attribs['graph_type'] = $template_data['graph_type']; $attribs['total'] = $template_data['total']; $attribs['total_type'] = $template_data['total_type']; $attribs['total_prefix'] = $template_data['total_prefix']; $attribs['reorder'] = $template_data['order_type']; $attribs['item_no'] = db_fetch_cell_prepared('SELECT COUNT(*) FROM aggregate_graphs_graph_item WHERE aggregate_graph_id = ?', [$id]); } else { $attribs['graph_title'] = ''; $attribs['skipped_items'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence FROM aggregate_graph_templates_item WHERE item_skip="on" AND aggregate_template_id = ? ORDER BY sequence', [$aggregate_template_id]), 'sequence', 'sequence' ); $attribs['total_items'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence FROM aggregate_graph_templates_item WHERE item_total="on" AND aggregate_template_id = ? ORDER BY sequence', [$aggregate_template_id]), 'sequence', 'sequence' ); $attribs['color_templates'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence, color_template FROM aggregate_graph_templates_item WHERE color_template>=0 AND aggregate_template_id = ? ORDER BY sequence', [$aggregate_template_id]), 'sequence', 'color_template' ); $attribs['graph_item_types'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence, graph_type_id FROM aggregate_graph_templates_item WHERE t_graph_type_id="on" AND aggregate_template_id = ? ORDER BY sequence', [$aggregate_template_id]), 'sequence', 'graph_type_id' ); $attribs['cdefs'] = array_rekey( db_fetch_assoc_prepared('SELECT sequence, cdef_id FROM aggregate_graph_templates_item WHERE t_cdef_id="on" AND aggregate_template_id = ? ORDER BY sequence', [$aggregate_template_id]), 'sequence', 'cdef_id' ); $attribs['aggregate_template_id'] = $aggregate_template_id; $template_data = db_fetch_row_prepared('SELECT * FROM aggregate_graph_templates WHERE id = ?', [$aggregate_template_id]); $attribs['template_propogation'] = 'on'; $attribs['graph_template_id'] = $template_data['graph_template_id']; $attribs['gprint_prefix'] = $template_data['gprint_prefix']; $attribs['gprint_format'] = $template_data['gprint_format']; $attribs['graph_type'] = $template_data['graph_type']; $attribs['total'] = $template_data['total']; $attribs['total_type'] = $template_data['total_type']; $attribs['total_prefix'] = $template_data['total_prefix']; $attribs['reorder'] = $template_data['order_type']; $attribs['item_no'] = db_fetch_cell_prepared('SELECT COUNT(*) FROM aggregate_graph_templates_item WHERE aggregate_template_id = ?', [$aggregate_template_id]); } $aggregate_graphs = []; if ($local_graph_id > 0) { $aggregate_graphs[] = $local_graph_id; } else { $graphs = db_fetch_assoc_prepared('SELECT local_graph_id FROM aggregate_graphs WHERE aggregate_template_id = ?', [$aggregate_template_id]); if (cacti_sizeof($graphs)) { foreach ($graphs as $g) { $aggregate_graphs[] = $g['local_graph_id']; } } } if (cacti_sizeof($aggregate_graphs)) { foreach ($aggregate_graphs as $ag) { $member_graphs = []; $graphs = db_fetch_assoc_prepared('SELECT DISTINCT agi.local_graph_id FROM aggregate_graphs AS ag INNER JOIN aggregate_graphs_items AS agi ON ag.id=agi.aggregate_graph_id WHERE ag.local_graph_id = ?', [$ag]); if (cacti_sizeof($graphs)) { foreach ($graphs as $mg) { $member_graphs[] = $mg['local_graph_id']; } if (!aggregate_create_update($ag, $member_graphs, $attribs)) { return false; } } } } return true; } /** * Creates or updates an aggregate based on criteria * * @param int $local_graph_id The ID of the local graph to create or update. * @param array $member_graphs An array of member graphs to include in the aggregate. * @param array $attribs An array of attributes for the aggregate graph. * @param bool $manage_transaction Start and finish the transaction in this function. * * @return bool True when the aggregate update was committed. */ function aggregate_create_update(int &$local_graph_id, array $member_graphs, array $attribs, bool $manage_transaction = true) : bool { cacti_log(__FUNCTION__ . ' called. Graph id: ' . $local_graph_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); // suppress warnings error_reporting(E_ALL); // install own error handler set_error_handler('aggregate_error_handler'); $original_local_graph_id = $local_graph_id; if ($manage_transaction && !db_begin_transaction()) { raise_message('aggregate_transaction_failed', __('Unable to start the aggregate graph update.'), MESSAGE_LEVEL_ERROR); restore_error_handler(); return false; } if (cacti_sizeof($member_graphs)) { $graph_title = ($attribs['graph_title'] ?? ''); $aggregate_template = ($attribs['aggregate_template_id'] ?? 0); $graph_template_id = ($attribs['graph_template_id'] ?? 0); $aggregate_graph = ($attribs['aggregate_graph_id'] ?? 0); $template_propogation = ($attribs['template_propogation'] ?? 'on'); $gprint_prefix = ($attribs['gprint_prefix'] ?? ''); $gprint_format = ($attribs['gprint_format'] ?? ''); $_graph_type = ($attribs['graph_type'] ?? 0); $_total = ($attribs['total'] ?? 0); $_total_type = ($attribs['total_type'] ?? 0); $_total_prefix = ($attribs['total_prefix'] ?? ''); $_reorder = ($attribs['reorder'] ?? 0); $item_no = ($attribs['item_no'] ?? 0); $color_templates = (is_array($attribs['color_templates']) ? $attribs['color_templates'] : []); $graph_item_types = (is_array($attribs['graph_item_types']) ? $attribs['graph_item_types'] : []); $cdefs = (is_array($attribs['cdefs']) ? $attribs['cdefs'] : []); $skipped_items = (is_array($attribs['skipped_items']) ? $attribs['skipped_items'] : []); $total_items = (is_array($attribs['total_items']) ? $attribs['total_items'] : []); $example_graph_id = 0; // save the aggregate information $save1 = []; if ($local_graph_id == 0) { // create new graph based on first graph selected $local_graph_id = aggregate_graph_save($example_graph_id, $graph_template_id, $graph_title, $aggregate_template); $save1['id'] = ''; $new_aggregate = true; } else { // update graph params of existing aggregate graph $local_graph_id = aggregate_graph_save($local_graph_id, $graph_template_id, $graph_title, $aggregate_template); $save1['id'] = db_fetch_cell_prepared('SELECT id FROM aggregate_graphs WHERE local_graph_id = ?', [$local_graph_id]); $new_aggregate = false; } $save1['aggregate_template_id'] = $aggregate_template; $save1['template_propogation'] = $template_propogation; if ($graph_title != '') { $save1['title_format'] = $graph_title; } else { $save1['title_format'] = 'New Graph'; } $save1['local_graph_id'] = $local_graph_id; $save1['graph_template_id'] = $graph_template_id; $save1['gprint_prefix'] = $gprint_prefix; $save1['gprint_format'] = $gprint_format; $save1['graph_type'] = $_graph_type; $save1['total'] = $_total; $save1['total_type'] = $_total_type; $save1['total_prefix'] = $_total_prefix; $save1['order_type'] = $_reorder; $save1['user_id'] = $_SESSION[SESS_USER_ID]; $aggregate_graph_id = sql_save($save1, 'aggregate_graphs'); // sequence number of next graph item to be added, index starts at 1 $next_item_sequence = 1; $j = 1; $i = 0; // remove all old graph items first if ($local_graph_id > 0) { db_execute_prepared('DELETE FROM graph_templates_item WHERE local_graph_id = ?', [$local_graph_id]); } /* now add the graphs one by one to the newly created graph * program flow is governed by * - totalling * - new graph type: convert graph to e.g. AREA */ // loop for all selected graphs foreach ($member_graphs as $graph_id) { // insert all graph items of selected graph // next items to be inserted have to be in sequence $next_item_sequence = aggregate_graphs_insert_graph_items( $local_graph_id, $graph_id, $graph_template_id, $skipped_items, $total_items, $next_item_sequence, $i, $color_templates, $graph_item_types, $cdefs, $_graph_type, $gprint_prefix, $gprint_format, $_total, '', $member_graphs); db_execute_prepared('REPLACE INTO aggregate_graphs_items (aggregate_graph_id, local_graph_id, sequence) VALUES (?, ?, ?)', [$aggregate_graph_id, $graph_id, $j]); $j++; $i++; } cacti_log(__FUNCTION__ . ' all items inserted, next item seq: ' . $next_item_sequence . ' selGraph: ' . $i, true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); /* post processing for pure LINEx graphs * if we convert to AREA/STACK, the function aggregate_graphs_insert_graph_items * created a pure STACK graph (see comments in that function) * so let's find out, if we have a pure STACK now ... */ if (aggregate_is_pure_stacked_graph($local_graph_id)) { // ... and convert to AREA aggregate_conditional_convert_graph_type($local_graph_id, GRAPH_ITEM_TYPE_STACK, GRAPH_ITEM_TYPE_AREA); } if (aggregate_is_stacked_graph($local_graph_id)) { /* reorder graph items, if requested * for STACKed graphs, reorder before adding totals */ aggregate_reorder_ds_graph( $example_graph_id, $graph_template_id, $local_graph_id, $_reorder, $_graph_type); } $_orig_graph_type = $_graph_type; // special code to add totalling graph items switch ($_total) { case AGGREGATE_TOTAL_NONE: // no totalling // do NOT add any totalling items break; case AGGREGATE_TOTAL_ALL: // any totalling option was selected ... $_graph_type = GRAPH_ITEM_TYPE_LINE1; $cf_id = db_fetch_cell_prepared('SELECT consolidation_function_id FROM graph_templates_item WHERE color_id > 0' . (cacti_sizeof($member_graphs) > 0 ? ' AND ' . array_to_sql_or($member_graphs, 'local_graph_id') : '') . (cacti_sizeof($skipped_items) > 0 ? ' AND sequence NOT IN(' . implode(',', array_fill(0, count($skipped_items), '?')) . ')' : '') . ' ORDER BY sequence ASC LIMIT 1', array_values($skipped_items)); // add an empty line before total items db_execute_prepared("INSERT INTO graph_templates_item (local_graph_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence) VALUES (?, 1, ?, '', '', 'on', 2, ?)", [$local_graph_id, $cf_id, $next_item_sequence++]); // Fall through: all totals also need the common totalling graph items. case AGGREGATE_TOTAL_ONLY: // use the prefix for totalling GPRINTs as given by the user switch ($_total_type) { case AGGREGATE_TOTAL_TYPE_SIMILAR: case AGGREGATE_TOTAL_TYPE_ALL: $gprint_prefix = $_total_prefix; break; } // now skip all items, that are // - explicitly marked as skipped (based on $skipped_items) // - OR NOT marked as 'totalling' items for ($k = 1; $k <= $item_no; $k++) { cacti_log(__FUNCTION__ . ' old skip: ' . ($skipped_items[$k] ?? ''), true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); // skip all items, that shall not be totalled if (!isset($total_items[$k])) { $skipped_items[$k] = $k; } cacti_log(__FUNCTION__ . ' new skip: ' . ($skipped_items[$k] ?? ''), true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); } // add the 'templating' graph to the new graph, honoring skipped, hr and color aggregate_graphs_insert_graph_items( $local_graph_id, $example_graph_id, $graph_template_id, $skipped_items, $total_items, $next_item_sequence, $i, $color_templates, $graph_item_types, $cdefs, $_graph_type, // TODO: user may choose LINEx instead of assuming LINE1 $gprint_prefix, $gprint_format, AGGREGATE_TOTAL_ALL, // now add the totalling line(s) $_total_type, $member_graphs); // now pay attention to CDEFs // next_item_sequence still points to the first totalling graph item if (!aggregate_cdef_totalling( $local_graph_id, $next_item_sequence, $_total_type)) { if ($manage_transaction) { db_rollback_transaction(); $local_graph_id = $original_local_graph_id; } raise_message('aggregate_invalid_cdef', __('Unable to create aggregate totals because a referenced CDEF is invalid or empty.'), MESSAGE_LEVEL_ERROR); restore_error_handler(); return false; } } /* post processing for pure LINEx graphs * if we convert to AREA/STACK, the function aggregate_graphs_insert_graph_items * created a pure STACK graph (see comments in that function) * so let's find out, if we have a pure STACK now ... */ if (aggregate_is_pure_stacked_graph($local_graph_id)) { // ... and convert to AREA aggregate_conditional_convert_graph_type($local_graph_id, GRAPH_ITEM_TYPE_STACK, GRAPH_ITEM_TYPE_AREA); } if (!aggregate_is_stacked_graph($local_graph_id)) { /* reorder graph items, if requested * for non-STACKed graphs, we want to reorder the totals as well */ aggregate_reorder_ds_graph( $example_graph_id, $graph_template_id, $local_graph_id, $_reorder, $_graph_type); } // Handle stacked lines properly aggregate_handle_stacked_lines($local_graph_id, $_orig_graph_type, $_total, $_total_type, $_total_prefix); // Handle aggregate nth percentiles if ($_total != AGGREGATE_TOTAL_NONE) { aggregate_handle_ptile_type($member_graphs, $skipped_items, $local_graph_id, $_total, $_total_type); } } if ($manage_transaction && !db_commit_transaction()) { db_rollback_transaction(); $local_graph_id = $original_local_graph_id; raise_message('aggregate_commit_failed', __('Unable to commit the aggregate graph update.'), MESSAGE_LEVEL_ERROR); restore_error_handler(); return false; } // restore original error handler restore_error_handler(); return true; } /** * Handles the percentile type aggregation for member graphs. * * @param array $member_graphs An array of member graphs to be aggregated. * @param array $skipped_items An array of items that were skipped during the aggregation process. * @param int $local_graph_id The ID of the local graph being processed. * @param int $_total The total value calculated during the aggregation. * @param string $_total_type The type of total calculation (e.g., 'sum', 'average'). * * @return void */ function aggregate_handle_ptile_type(array $member_graphs, array $skipped_items, int $local_graph_id, int $_total, string $_total_type) : void { $special_comments = null; $special_hrules = null; $graph_template_id = 0; $agg_info = db_fetch_row_prepared('SELECT * FROM aggregate_graphs WHERE local_graph_id = ?', [$local_graph_id]); if (cacti_sizeof($agg_info)) { $comments_hrules = db_fetch_assoc_prepared('SELECT * FROM graph_templates_item WHERE graph_type_id IN (?, ?) AND graph_template_id = ? AND local_graph_id = 0' . (cacti_sizeof($skipped_items) ? ' AND sequence NOT IN(' . implode(',', array_fill(0, count($skipped_items), '?')) . ')' : '') . ' AND (text_format != "" || value != "") ORDER BY sequence ASC', [GRAPH_ITEM_TYPE_COMMENT, GRAPH_ITEM_TYPE_HRULE, $agg_info['graph_template_id'], ...array_values($skipped_items)]); $graph_template_id = $agg_info['graph_template_id']; } else { if (cacti_sizeof($member_graphs)) { $template_graph[] = $member_graphs[0]; } else { $template_graph = []; } $comments_hrules = db_fetch_assoc_prepared('SELECT * FROM graph_templates_item WHERE graph_type_id IN(' . GRAPH_ITEM_TYPE_COMMENT . ',' . GRAPH_ITEM_TYPE_HRULE . ')' . (cacti_sizeof($template_graph) ? ' AND ' . array_to_sql_or($template_graph, 'local_graph_id') : '') . (cacti_sizeof($skipped_items) ? ' AND sequence NOT IN(' . implode(',', array_fill(0, count($skipped_items), '?')) . ')' : '') . ' AND (text_format != "" || value != "") ORDER BY local_graph_id, sequence ASC', array_values($skipped_items)); if (cacti_sizeof($comments_hrules)) { $graph_template_id = $comments_hrules[0]['graph_template_id']; } } $next_item_sequence = db_fetch_cell_prepared('SELECT MAX(sequence) FROM graph_templates_item WHERE local_graph_id = ?', [$local_graph_id]); if (cacti_sizeof($comments_hrules)) { foreach ($comments_hrules as $item) { switch($item['graph_type_id']) { case GRAPH_ITEM_TYPE_COMMENT: if (!isset($special_comments[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']])) { if (preg_match('/(:bits:|:bytes:)/', $item['text_format'])) { $special_comments[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']] = true; $parts = explode('|', $item['text_format']); if (isset($parts[1])) { $pparts = explode(':', $parts[1]); if (isset($pparts[3])) { if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) { $pparts[3] = str_replace('current', 'aggregate_sum', $pparts[3]); $pparts[3] = str_replace('max', 'aggregate_sum_peak', $pparts[3]); } elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) { $pparts[3] = str_replace('current', 'aggregate_current', $pparts[3]); $pparts[3] = str_replace('max', 'aggregate_current_peak', $pparts[3]); } else { cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for pparts[3] in text_format', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); } switch($pparts[3]) { case 'current': $new_ppart = 'current'; break; case 'total': $new_ppart = 'aggregate_sum'; break; case 'max': $new_ppart = 'max'; break; case 'total_peak': $new_ppart = 'total_peak'; break; case 'all_max_current': case 'all_max_peak': case 'aggregate_max': $new_ppart = 'aggregate_peak'; break; case 'aggregate_sum': case 'aggregate_sum_peak': case 'aggregate_current': case 'aggregate_current_peak': case 'aggregate': case 'aggregate_peak': $new_ppart = $pparts[3]; break; default: $new_ppart = 'max'; break; } $pparts[3] = $new_ppart; $parts[1] = implode(':', $pparts); $item['text_format'] = implode('|', $parts); } } db_execute_prepared("INSERT INTO graph_templates_item (graph_template_id, local_graph_id, task_item_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence) VALUES (?, ?, ?, ?, 1, ?, '', ?, 2, ?)", [ $graph_template_id, $local_graph_id, $item['task_item_id'], GRAPH_ITEM_TYPE_COMMENT, $item['text_format'], $item['hard_return'], $next_item_sequence++ ] ); } } break; case GRAPH_ITEM_TYPE_HRULE: if (!isset($special_hrules[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']])) { if (preg_match('/(:bits:|:bytes:)/', $item['value'])) { $special_hrules[$item['text_format'] . '|' . $item['value'] . '|' . $item['task_item_id']] = true; $parts = explode('|', $item['value']); if (isset($parts[1])) { $pparts = explode(':', $parts[1]); if (isset($pparts[3])) { if ($_total_type == AGGREGATE_TOTAL_TYPE_ALL) { $pparts[3] = str_replace('current', 'aggregate_sum', $pparts[3]); $pparts[3] = str_replace('max', 'aggregate_sum_peak', $pparts[3]); } elseif ($_total_type == AGGREGATE_TOTAL_TYPE_SIMILAR) { $pparts[3] = str_replace('current', 'aggregate_current', $pparts[3]); $pparts[3] = str_replace('max', 'aggregate_current_peak', $pparts[3]); } else { cacti_log(__FUNCTION__ . ' unhandled total_type ' . $_total_type . ' for pparts[3] in value', true, 'AGGREGATE', POLLER_VERBOSITY_DEBUG); } switch($pparts[3]) { case 'current': $new_ppart = 'current'; break; case 'total': $new_ppart = 'aggregate_sum'; break; case 'max': $new_ppart = 'max'; break; case 'total_peak': $new_ppart = 'total_peak'; break; case 'all_max_current': case 'all_max_peak': case 'aggregate_max': $new_ppart = 'aggregate_peak'; break; case 'aggregate_peak': case 'aggregate_sum': case 'aggregate_sum_peak': case 'aggregate_current': case 'aggregate_current_peak': case 'aggregate': $new_ppart = $pparts[3]; break; default: $new_ppart = 'max'; break; } $pparts[3] = $new_ppart; $parts[1] = implode(':', $pparts); $item['value'] = implode('|', $parts); } } // add an empty line before nth percentile for the first item only if (cacti_sizeof($special_hrules) == 1) { db_execute_prepared("INSERT INTO graph_templates_item (graph_template_id, local_graph_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence) VALUES (?, ?, 1, 1, '', '', 'on', 2, ?)", [$graph_template_id, $local_graph_id, $next_item_sequence++]); } db_execute_prepared("INSERT INTO graph_templates_item (graph_template_id, local_graph_id, task_item_id, graph_type_id, color_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence) VALUES (?, ?, ?, ?, ?, 1, ?, ?, '', 2, ?)", [ $graph_template_id, $local_graph_id, $item['task_item_id'], GRAPH_ITEM_TYPE_HRULE, $item['color_id'], $item['text_format'], $item['value'], $next_item_sequence++ ] ); } } break; } } } } /** * Handles the aggregation of stacked lines for a given graph. * * @param int $local_graph_id The ID of the local graph. * @param string $_orig_graph_type The original type of the graph. * @param int $_total The total value to be aggregated. * @param string $_total_type The type of the total value. * @param string $_total_prefix The prefix for the total value. * * @return void */ function aggregate_handle_stacked_lines(int $local_graph_id, string $_orig_graph_type, int $_total, string $_total_type, string $_total_prefix) : void { // Handle the stacked line cases switch line widths $width = '0.01'; $special_type = ''; $special_line = false; switch ($_orig_graph_type) { case AGGREGATE_GRAPH_TYPE_LINE1_STACK: $width = '1.00'; $special_type = GRAPH_ITEM_TYPE_LINE1; $special_line = true; break; case AGGREGATE_GRAPH_TYPE_LINE2_STACK: $width = '2.00'; $special_type = GRAPH_ITEM_TYPE_LINE2; $special_line = true; break; case AGGREGATE_GRAPH_TYPE_LINE3_STACK: $width = '3.00'; $special_type = GRAPH_ITEM_TYPE_LINE3; $special_line = true; break; } if ($special_line) { if ($_total == AGGREGATE_TOTAL_NONE) { db_execute_prepared('UPDATE graph_templates_item SET line_width = ? WHERE local_graph_id = ? AND graph_type_id IN (?)', [$width, $local_graph_id, GRAPH_ITEM_TYPE_LINESTACK]); } // Handle special case total prefix db_execute_prepared('UPDATE graph_templates_item SET graph_type_id = ? WHERE local_graph_id = ? AND text_format = ?', [$special_type, $local_graph_id, $_total_prefix]); if ($_total == AGGREGATE_TOTAL_ALL) { db_execute_prepared('UPDATE graph_templates_item SET graph_type_id = ?, line_width = "0.01" WHERE local_graph_id = ? AND graph_type_id = ? AND text_format != ?', [ GRAPH_ITEM_TYPE_LINESTACK, $local_graph_id, $special_type, $_total_prefix ] ); } } // handle any missing lines db_execute_prepared('UPDATE graph_templates_item SET line_width="0.01" WHERE graph_type_id = ? AND line_width="0.00" AND local_graph_id = ?', [GRAPH_ITEM_TYPE_LINESTACK, $local_graph_id]); } /** * Retrieves data sources for aggregation. * * @param array $graph_array Array of graphs to aggregate. * @param mixed $data_sources Array to store the retrieved data sources. * @param mixed $graph_template Template for the graphs. * @param string $message Optional. Message to store any errors or information. * * @return bool True on success, false on failure. */ function aggregate_get_data_sources(array &$graph_array, mixed &$data_sources, mixed &$graph_template, string &$message = '') : bool { if (cacti_sizeof($graph_array)) { // fetch all data sources for all selected graphs $data_sources = db_fetch_assoc('SELECT dtd.local_data_id, dtd.name_cache FROM data_template_rrd AS dtr INNER JOIN graph_templates_item AS gti ON dtr.id = gti.task_item_id INNER JOIN data_template_data AS dtd ON dtr.local_data_id = dtd.local_data_id WHERE ' . array_to_sql_or($graph_array, 'gti.local_graph_id') . ' AND dtd.local_data_id > 0 GROUP BY dtd.local_data_id ORDER BY dtd.name_cache'); // verify, that only a single graph template is used, else // aggregate will look funny $templates = db_fetch_assoc('SELECT DISTINCT gl.graph_template_id AS id FROM graph_local AS gl WHERE ' . array_to_sql_or($graph_array, 'gl.id')); if (cacti_sizeof($templates) > 1) { $message = __('The Graphs chosen for the Aggregate Graph below represent Graphs from multiple Graph Templates. Aggregate does not support creating Aggregate Graphs from multiple Graph Templates.'); return false; } if (cacti_sizeof($templates) == 1) { if ($templates[0]['id'] == 0) { // selected graphs do not use templates $message = __('The Graphs chosen for the Aggregate Graph do not use Graph Templates. Aggregate does not support creating Aggregate Graphs from non-templated graphs.'); return false; } else { $graph_template = $templates[0]['id']; return true; } } else { $message = __('There was some error in MySQL/MariaDB. Can not continue.'); return false; } } else { $message = __('You must pass at least a single Graph to this function'); return false; } } /** * Draws the list of aggregate graph items. * * @param int $_graph_id The ID of the graph. Default is 0. * @param int $_graph_template_id The ID of the graph template. Default is 0. * @param array $_object An array of objects related to the graph. Default is an empty array. * * @return void */ function draw_aggregate_graph_items_list(int $_graph_id = 0, int $_graph_template_id = 0, array $_object = []) : void { global $graph_item_types, $consolidation_functions; cacti_log(__FUNCTION__ . ' called. graph: ' . $_graph_id . ' template: ' . $_graph_template_id, true, 'AGGREGATE', POLLER_VERBOSITY_DEVDBG); if ($_graph_id == 0 && $_graph_template_id == 0) { return; } $item_list_where = ''; // fetch graph items if ($_graph_id == 0) { $item_list_where = "gti.local_graph_id=0 AND gti.graph_template_id=$_graph_template_id "; } if ($_graph_template_id == 0) { $item_list_where = "gti.local_graph_id=$_graph_id "; } $item_list = db_fetch_assoc("SELECT gti.id, gti.text_format, gti.value, gti.hard_return, gti.graph_type_id, gti.consolidation_function_id, cdef.name as cdef_name, colors.hex, vdef.name AS vdef_name, gtgp.name AS gprint_name FROM graph_templates_item AS gti LEFT JOIN graph_templates_gprint AS gtgp ON gti.gprint_id=gtgp.id LEFT JOIN cdef ON gti.cdef_id=cdef.id LEFT JOIN vdef ON gti.vdef_id=vdef.id LEFT JOIN colors ON gti.color_id=colors.id WHERE $item_list_where ORDER BY gti.sequence"); // fetch color templates $color_templates = db_fetch_assoc('SELECT color_template_id, name FROM color_templates ORDER BY name'); $current_vals = []; $is_edit = false; $is_templated = false; if (cacti_sizeof($_object) > 0 && $_object['id'] > 0) { // drawing items for existing aggregate graph/template $is_edit = true; // fetch existing item values if (isset($_object['aggregate_template_id']) && $_object['aggregate_template_id'] == 0) { // this is aggregate graph with no aggregate template $current_vals = db_fetch_assoc_prepared('SELECT * FROM aggregate_graphs_graph_item WHERE aggregate_graph_id = ?', [$_object['id']]); $item_editor_link_param = 'aggregate_graph_id=' . $_object['id'] . '&local_graph_id=' . $_object['local_graph_id']; $is_templated = false; } elseif (isset($_object['aggregate_template_id'])) { // this is aggregate graph from aggregate template $current_vals = db_fetch_assoc_prepared('SELECT * FROM aggregate_graph_templates_item WHERE aggregate_template_id = ?', [$_object['aggregate_template_id']]); $item_editor_link_param = 'aggregate_template_id=' . $_object['aggregate_template_id']; $is_templated = true; } else { // this is aggregate template $current_vals = db_fetch_assoc_prepared('SELECT * FROM aggregate_graph_templates_item WHERE aggregate_template_id = ?', [$_object['id']]); $item_editor_link_param = 'aggregate_template_id=' . $_object['id']; $is_templated = true; } // key results on item id $current_vals = array_rekey( $current_vals, 'graph_templates_item_id', ['color_template', 'item_skip', 'item_total', 't_graph_type_id', 'graph_type_id'] ); } // draw list of graph items html_start_box(($is_templated ? __('Graph Template Items') : __('Graph Items')), '100%', false, 3, 'center', ''); // print column header print ""; DrawMatrixHeaderItem(__('Graph Item'), '', 1); DrawMatrixHeaderItem(__('Data Source'), '',1); DrawMatrixHeaderItem(__('Graph Item Type'), '', 1); DrawMatrixHeaderItem(__('CF Type'), '', 1); DrawMatrixHeaderItem(__('GPrint'), '', 1); DrawMatrixHeaderItem(__('CDEF'), '', 1); DrawMatrixHeaderItem(__('VDEF'), '', 1); DrawMatrixHeaderItem(__('Item Color'), '', 2); DrawMatrixHeaderItem(__('Color Template'), '', 1); DrawMatrixHeaderItem(__('Skip'), '', 1); DrawMatrixHeaderItem(__('Total'), '', 1); print ''; $group_counter = 0; $_graph_type_name = ''; $i = 0; if (cacti_sizeof($item_list)) { foreach ($item_list as $item) { // graph grouping display logic $this_row_style = ''; $use_custom_class = false; $hard_return = ''; $matrix_title = ''; $customClass = ''; if (!preg_match('/(GPRINT|TEXTALIGN|HRULE|VRULE|TICK)/', $graph_item_types[$item['graph_type_id']])) { $this_row_style = 'font-weight: bold;'; $use_custom_class = true; if ($group_counter % 2 == 0) { $customClass = 'graphItem'; } else { $customClass = 'graphItemAlternate'; } $group_counter++; } // column "Data Source" $_graph_type_name = $graph_item_types[$item['graph_type_id']]; $force_skip = false; switch (true) { case preg_match('/(TEXTALIGN)/', $_graph_type_name): $matrix_title = 'TEXTALIGN: ' . ucfirst($item['textalign']); break; case preg_match('/(TICK)/', $_graph_type_name): $matrix_title = '(' . $item['data_source_name'] . '): ' . $item['text_format']; break; case preg_match('/(AREA|STACK|GPRINT|LINE[123])/', $_graph_type_name): $matrix_title = $item['text_format']; break; case preg_match('/(HRULE)/', $_graph_type_name): $matrix_title = 'HRULE: ' . $item['value']; if (preg_match('/(:bits:|:bytes:|\|sum:)/', $item['value'])) { $force_skip = false; } else { $force_skip = true; } break; case preg_match('/(VRULE)/', $_graph_type_name): $force_skip = true; $matrix_title = 'VRULE: ' . $item['value']; break; case preg_match('/(COMMENT)/', $_graph_type_name): $matrix_title = 'COMMENT: ' . $item['text_format']; if (preg_match('/(:bits:|:bytes:|\|sum:)/', $item['text_format'])) { $force_skip = false; } elseif ($item['text_format'] != '') { $force_skip = false; } else { $force_skip = true; } break; } // values can be overridden in aggregate graph/template if ($is_edit && isset($current_vals[$item['id']]['t_graph_type_id']) && $current_vals[$item['id']]['t_graph_type_id'] == 'on') { $item['graph_type_id'] = $current_vals[$item['id']]['graph_type_id']; } // alternating row color if ($use_custom_class == false) { form_alternate_row(); } else { print ""; } // column 'Graph Item' print ''; if ($is_edit == false) { // no existing aggregate graph/template print __('Item # %d', ($i + 1)); } elseif (isset($_object['template_propogation']) && $_object['template_propogation']) { // existing aggregate graph with template propagation enabled print __('Item # %d', ($i + 1)); } else { // existing aggregate template or graph with no templating // create a link to graph item editor print '' . __('Item # %d', ($i + 1)) . ''; } print ''; if ($item['hard_return'] == 'on') { $hard_return = '<HR>'; } print "" . htmle($matrix_title) . $hard_return . ''; // column 'Graph Item Type' print "" . $graph_item_types[$item['graph_type_id']] . ''; // column 'CF Type' print "" . $consolidation_functions[$item['consolidation_function_id']] . ''; // column 'GPrint' print "" . $item['gprint_name'] . ''; // column 'CDEF' print "" . $item['cdef_name'] . ''; // column 'CDEF' print "" . $item['vdef_name'] . ''; // column 'Item Color' print " '; print "" . $item['hex'] . ''; // column 'Color Template' print ''; if (!empty($item['hex'])) { print "'; } print ''; // column "Skip" if (!$force_skip) { print ""; print "