$entity_type, 'entity_id' => $entity_id ];
foreach ($alert_table[$entity_type][$entity_id] as $alert_test_id => $alert_args) {
if ($alert_rules[$alert_test_id]['and']) {
// ALL conditions
$alert = TRUE;
} else {
// ANY condition
$alert = FALSE;
}
$alert_info['alert_test_id'] = $alert_test_id;
$alert_checker = $alert_rules[$alert_test_id];
$update_array = [];
if (is_array($alert_rules[$alert_test_id])) {
print_debug("Checking alert ".$alert_test_id." associated by ".$alert_args['alert_assocs']."\n");
$alert_output .= $alert_rules[$alert_test_id]['alert_name'] . " [";
foreach ($alert_rules[$alert_test_id]['conditions'] as $test_key => $test) {
// Replace tagged conditions with entity params from db, ie @sensor_limit
if (str_starts($test['value'], '@')) {
$ent_val = substr($test['value'], 1);
$test['value'] = $entity[$ent_val];
print_debug("DEBUG: ");
print_debug_vars($entity[$ent_val]);
print_debug(" replaced @".$ent_val." with ". $test['value'] ." from entity. ");
}
print_debug("Testing: " . $test['metric']. " ". $test['condition'] . " " .$test['value']);
$update_array['state']['metrics'][$test['metric']] = $data[$test['metric']];
if (isset($data[$test['metric']])) {
print_debug(" (value: ".$data[$test['metric']].")");
if (test_condition($data[$test['metric']], $test['condition'], $test['value'])) {
// A test has failed. Set the alert variable and make a note of what failed.
//print_cli("%R[FAIL]%N");
$update_array['state']['failed'][] = $test;
if ($alert_rules[$alert_test_id]['and']) {
// ALL conditions
$alert = ($alert && TRUE);
} else {
$alert = ($alert || TRUE);
}
} else {
if ($alert_rules[$alert_test_id]['and']) {
$alert = ($alert && FALSE);
} else {
$alert = ($alert || FALSE);
}
//print_cli("%G[OK]%N");
}
} else {
print_debug("Metric '".$test['metric']."' is not present on entity ".$entity_type."=$entity_id.\n");
if ($alert_rules[$alert_test_id]['and']) {
$alert = ($alert && FALSE);
} else {
$alert = ($alert || FALSE);
}
}
}
// json_encode the state array before we put it into MySQL.
$update_array['state'] = safe_json_encode($update_array['state']);
$last_time = get_time();
if ($alert) {
// Check to see if this alert has been suppressed by anything
## FIXME -- not all of this is implemented
$suppressed = [];
$alert_suppressed = FALSE;
// Have all alerts been suppressed?
if ($config['alerts']['suppress']) {
$alert_suppressed = TRUE;
$suppressed[] = "GLOBAL";
}
// Is there a global scheduled maintenance?
if (isset($GLOBALS['cache']['maint']['global']) && count($GLOBALS['cache']['maint']['global']) > 0) {
$alert_suppressed = TRUE;
$suppressed[] = "MNT_GBL";
}
// Have all alerts on the device been suppressed?
if ($device['ignore']) {
$alert_suppressed = TRUE;
$suppressed[] = "DEV";
}
if ($device['ignore_until']) {
$device['ignore_until_time'] = strtotime($device['ignore_until']);
if ($device['ignore_until_time'] > time()) {
$alert_suppressed = TRUE;
$suppressed[] = "DEV_U";
}
}
if (isset($GLOBALS['cache']['maint'][$entity_type][$entity[$entity_id_field]])) {
$alert_suppressed = TRUE;
$suppressed[] = "MNT_ENT";
}
if (isset($GLOBALS['cache']['maint']['alert_checker'][$alert_test_id])) {
$alert_suppressed = TRUE;
$suppressed[] = "MNT_CHK";
}
if (isset($GLOBALS['cache']['maint']['device'][$device['device_id']])) {
$alert_suppressed = TRUE;
$suppressed[] = "MNT_DEV";
}
// Have all alerts on the entity been suppressed?
if ($entity[$entity_ignore_field]) {
$alert_suppressed = TRUE;
$suppressed[] = "ENT";
}
if (is_numeric($entity['ignore_until']) && $entity['ignore_until'] > time()) {
$alert_suppressed = TRUE;
$suppressed[] = "ENT_U";
}
// Have alerts from this alerter been suppressed?
if ($alert_rules[$alert_test_id]['ignore']) {
$alert_suppressed = TRUE;
$suppressed[] = "CHECK";
}
if ($alert_rules[$alert_test_id]['ignore_until']) {
$alert_rules[$alert_test_id]['ignore_until_time'] = strtotime($alert_rules[$alert_test_id]['ignore_until']);
if ($alert_rules[$alert_test_id]['ignore_until_time'] > time()) {
$alert_suppressed = TRUE;
$suppressed[] = "CHECK_UNTIL";
}
}
// Has this specific alert been suppressed?
if ($alert_args['ignore']) {
$alert_suppressed = TRUE;
$suppressed[] = "ENTRY";
}
if ($alert_args['ignore_until']) {
$alert_args['ignore_until_time'] = strtotime($alert_args['ignore_until']);
if ($alert_args['ignore_until_time'] > time()) {
$alert_suppressed = TRUE;
$suppressed[] = "ENTRY_UNTIL";
}
}
if (is_numeric($alert_args['ignore_until_ok']) && $alert_args['ignore_until_ok'] == '1') {
$alert_suppressed = TRUE;
$suppressed[] = "ENTRY_UNTIL_OK";
}
$update_array['count'] = $alert_args['count'] + 1;
//$update_array['severity'] = $alert_rules[$alert_test_id]['severity'];
// Check against the alert test's delay
if ($alert_args['count'] >= $alert_rules[$alert_test_id]['delay'] && $alert_suppressed) {
// This alert is valid, but has been suppressed.
//echo(" Checks failed. Alert suppressed (".implode(', ', $suppressed).").\n");
$alert_output .= "%PFS%N";
$update_array['alert_status'] = '3';
$update_array['last_message'] = 'Checks failed (Suppressed: ' . implode(', ', $suppressed) . ')';
$update_array['last_checked'] = $last_time;
if ($alert_args['alert_status'] != '3' || $alert_args['last_changed'] == '0') {
$update_array['last_changed'] = $last_time;
$log_id = log_alert('Checks failed but alert suppressed by [' . implode(',', $suppressed) . ']', $device, $alert_info, 'FAIL_SUPPRESSED');
}
$update_array['last_failed'] = $last_time;
} elseif ($alert_args['count'] >= $alert_rules[$alert_test_id]['delay']) {
// This is a real alert.
//echo(" Checks failed. Generate alert.\n");
$alert_output .= "%PF!%N";
$update_array['alert_status'] = '0';
$update_array['last_message'] = 'Checks failed';
$update_array['last_checked'] = $last_time;
if ($alert_args['alert_status'] != '0' || $alert_args['last_changed'] == '0') {
$update_array['last_changed'] = $last_time;
$update_array['last_alerted'] = '0';
$log_id = log_alert('Checks failed', $device, $alert_info, 'FAIL');
}
$update_array['last_failed'] = $last_time;
} else {
// This is alert needs to exist for longer.
//echo(" Checks failed. Delaying alert.\n");
$alert_output .= "%OFD%N";
$update_array['alert_status'] = '2';
$update_array['last_message'] = 'Checks failed (delayed)';
$update_array['last_checked'] = $last_time;
if ($alert_args['alert_status'] != '2' || $alert_args['last_changed'] == '0') {
$update_array['last_changed'] = $last_time;
$log_id = log_alert('Checks failed but alert delayed', $device, $alert_info, 'FAIL_DELAYED');
}
$update_array['last_failed'] = $last_time;
}
} else {
$update_array['count'] = 0;
// Alert conditions passed. Record that we tested it and update status and other data.
$alert_output .= "%gOK%N";
$update_array['alert_status'] = '1';
$update_array['last_message'] = 'Checks OK';
$update_array['last_checked'] = $last_time;
if ($alert_args['alert_status'] != '1' || $alert_args['last_changed'] == '0') {
$update_array['last_changed'] = $last_time;
$log_id = log_alert('Checks succeeded', $device, $alert_info, 'OK');
}
$update_array['last_ok'] = $last_time;
// Status is OK, so disable ignore_until_ok if it has been enabled
if ($alert_args['ignore_until_ok'] != '0') {
$update_entry_array['ignore_until_ok'] = '0';
}
}
unset($suppressed);
unset($alert_suppressed);
#$update_array['alert_table_id'] = $alert_args['alert_table_id'];
/// Perhaps this is better done with SQL replace?
#print_vars($alert_args);
//if (!$alert_args['state_entry'])
//{
// State entry seems to be missing. Insert it before we update it.
//dbInsert(array('alert_table_id' => $alert_args['alert_table_id']), 'alert_table-state');
// echo("I+");
//}
print_debug_vars($alert_args);
print_debug_vars($update_array);
// Multiupdate
$multi_row = [ 'alert_table_id' => $alert_args['alert_table_id'] ];
$changed = FALSE;
foreach ([ 'count', 'state', 'alert_status', 'last_message', 'last_changed',
'last_checked', 'last_ok', 'last_failed', 'last_alerted' ] as $field) {
if (isset($update_array[$field])) {
// Updated
$multi_row[$field] = $update_array[$field];
} else {
// Previous
$multi_row[$field] = $alert_args[$field];
}
if (!in_array($field, [ 'last_checked', 'last_ok' ])) {
$changed = $changed || ($multi_row[$field] != $alert_args[$field]);
}
if (is_null($multi_row[$field])) {
// keep as NULL
$multi_row[$field] = [ 'NULL' ];
}
}
// This store update entry for multi insert latter
if (!$config['alerts']['reduce_db_updates']) {
// Full update (default)
dbUpdateRowMulti($multi_row, 'alert_table', 'alert_table_id');
} elseif ($changed) {
// Reduced update queries (only alerted entries)
dbUpdateRowMulti($multi_row, 'alert_table', 'alert_table_id');
//dbUpdateRowMulti($multi_row, 'alert_table_test', 'alert_table_id');
}
//dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($alert_args['alert_table_id']));
/// FIXME. $update_entry_array not initialised, can be incorrect update results
if (is_array($update_entry_array)) {
dbUpdate($update_entry_array, 'alert_table', '`alert_table_id` = ?', array($alert_args['alert_table_id']));
}
if (TRUE) {
// Write RRD data
if ($update_array['alert_status'] == "1") {
// Status is up
rrdtool_update_ng($device, 'alert', array('status' => 1, 'code' => $update_array['alert_status']), $alert_args);
} else {
rrdtool_update_ng($device, 'alert', array('status' => 0, 'code' => $update_array['alert_status']), $alert_args);
}
}
} else {
$alert_output .= "%RAlert missing!%N";
}
$alert_output .= ("] ");
}
$alert_output .= "%n";
/*
if ($entity_type == "device") {
$cli_level = 1;
} else {
$cli_level = 3;
}
print_cli_data("Checked Alerts", $alert_output, $cli_level);
*/
}
/**
* Build an array of conditions that apply to a supplied device
*
* This takes the array of global conditions and removes associations that don't match the supplied device array
*
* @param array device
* @return array
*/
// TESTME needs unit testing
function cache_device_conditions($device) {
// Return no conditions if the device is ignored or disabled.
if ($device['ignore'] == 1 || $device['disabled'] == 1) {
return [];
}
$conditions = cache_conditions();
$cond_new = [];
foreach ($conditions['assoc'] as $assoc_key => $assoc) {
if (match_device($device, $assoc['device_attribs'])) {
//$assoc['alert_test_id'];
$conditions['cond'][$assoc['alert_test_id']]['assoc'][$assoc_key] = $conditions['assoc'][$assoc_key];
$cond_new['cond'][$assoc['alert_test_id']] = $conditions['cond'][$assoc['alert_test_id']];
} else {
unset($conditions['assoc'][$assoc_key]);
}
}
return $cond_new;
}
/**
* Fetch array of alerts to a supplied device from `alert_table`
*
* This takes device_id as argument and returns an array.
*
* @param integer|string $device_id
* @return array
*/
// TESTME needs unit testing
function cache_device_alert_table($device_id)
{
$alert_table = array();
$sql = "SELECT * FROM `alert_table`";
//$sql .= " LEFT JOIN `alert_table-state` USING(`alert_table_id`)";
$sql .= " WHERE `device_id` = ?";
foreach (dbFetchRows($sql, array($device_id)) as $entry) {
$alert_table[$entry['entity_type']][$entry['entity_id']][$entry['alert_test_id']] = $entry;
}
return $alert_table;
}
// Wrapper function to loop all groups and trigger rebuild for each.
function update_alert_tables($silent = TRUE) {
// System with multiple pollers need to check if update not run on other system
$alerts = cache_alert_rules();
$assocs = cache_alert_assoc();
// Populate associations table into alerts array for legacy association styles
foreach ($assocs as $assoc) {
$alerts[$assoc['alert_test_id']]['assocs'][] = $assoc;
}
foreach ($alerts as $alert) {
update_alert_table($alert, $silent);
}
}
// Regenerate Alert Table
function update_alert_table($alert, $silent = TRUE) {
if (is_numeric($alert)) { // We got an alert_test_id, fetch the array.
$alert = dbFetchRow("SELECT * FROM `alert_tests` WHERE `alert_test_id` = ?", [ $alert ]);
}
if (!safe_empty($alert['alert_assoc'])) {
$query = parse_qb_ruleset($alert['entity_type'], safe_json_decode($alert['alert_assoc']));
//r($query);
$data = dbFetchRows($query);
//$data = dbFetchRows($query, NULL, 'log');
//$error = dbError();
$entities = [];
$field = $GLOBALS['config']['entities'][$alert['entity_type']]['table_fields']['id'];
foreach($data as $datum) {
$entities[$datum[$field]] = array('entity_id' => $datum[$field], 'device_id' => $datum['device_id']);
}
} else {
$entities = get_alert_entities_from_assocs($alert);
}
//r($entities);
//$field = $config['entities'][$alert['entity_type']]['table_fields']['id'];
$existing_entities = get_alert_entities($alert['alert_test_id']);
//print_vars($existing_entities);
//print_vars($entities);
$add = array_diff_key($entities, $existing_entities);
$remove = array_diff_key($existing_entities, $entities);
if (!$silent) {
if (is_cli()) {
print_cli("Alert Checker " . str_pad("'{$alert['alert_name']}'", 30, ' ', STR_PAD_LEFT) . ": " . safe_count($existing_entities) . " existing, " . safe_count($entities) . " new entries (+" . safe_count($add) . "/-" . safe_count($remove) . ").\n");
} else {
print_message(safe_count($existing_entities) . " existing entries.
" .
safe_count($entities) . " new entries.
" .
"(+" . safe_count($add) . "/-" . safe_count($remove) . ")
");
}
}
$db_multi_insert = [];
foreach ($add as $entity_id => $entity) {
$db_multi_insert[] = [ 'device_id' => $entity['device_id'], 'entity_type' => $alert['entity_type'],
'entity_id' => $entity_id, 'alert_test_id' => $alert['alert_test_id'] ];
//dbInsert(array('device_id' => $entity['device_id'], 'entity_type' => $alert['entity_type'], 'entity_id' => $entity_id, 'alert_test_id' => $alert['alert_test_id']), 'alert_table');
}
// Multi insert
if (!empty($db_multi_insert)) {
dbInsertMulti($db_multi_insert, 'alert_table');
}
foreach ($remove as $entity_id => $entity) {
dbDelete('alert_table', 'alert_test_id = ? AND entity_id = ?', array($alert['alert_test_id'], $entity_id));
}
//print_vars($add);
//print_vars($remove);
if (!$silent && !is_cli()) {
print_message("Alert Checker " . $alert['alert_name'] . " regenerated", 'information');
}
}
/**
* Build an array of all alert rules
*
* @return array
*/
// TESTME needs unit testing
function cache_alert_rules($vars = array())
{
$alert_rules = array();
$rules_count = 0;
$where = 'WHERE 1';
$args = array();
if (isset($vars['entity_type']) && $vars['entity_type'] !== "all") {
$where .= ' AND `entity_type` = ?';
$args[] = $vars['entity_type'];
}
foreach (dbFetchRows("SELECT * FROM `alert_tests` " . $where, $args) as $entry) {
if ($entry['alerter'] == '') {
$entry['alerter'] = "default";
}
$alert_rules[$entry['alert_test_id']] = $entry;
$alert_rules[$entry['alert_test_id']]['conditions'] = safe_json_decode($entry['conditions']);
$rules_count++;
}
print_debug("Cached $rules_count alert rules.");
return $alert_rules;
}
// FIXME. Never used, deprecated?
// DOCME needs phpdoc block
// TESTME needs unit testing
function generate_alerter_info($alerter)
{
global $config;
if (is_array($config['alerts']['alerter'][$alerter])) {
$a = $config['alerts']['alerter'][$alerter];
$output = "" . $a['descr'] . "
";
$output .= $a['type'] . ": " . $a['contact'] . "
";
if ($a['enable']) {
$output .= "Enabled";
} else {
$output .= "Disabled";
}
return $output;
} else {
return "Unknown alerter.";
}
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function cache_alert_assoc()
{
$alert_assoc = array();
foreach (dbFetchRows("SELECT * FROM `alert_assoc`") as $entry) {
$entity_attribs = safe_json_decode($entry['entity_attribs']);
$device_attribs = safe_json_decode($entry['device_attribs']);
$alert_assoc[$entry['alert_assoc_id']]['entity_type'] = $entry['entity_type'];
$alert_assoc[$entry['alert_assoc_id']]['entity_attribs'] = $entity_attribs;
$alert_assoc[$entry['alert_assoc_id']]['device_attribs'] = $device_attribs;
$alert_assoc[$entry['alert_assoc_id']]['alert_test_id'] = $entry['alert_test_id'];
}
return $alert_assoc;
}
/**
* Build an array of scheduled maintenances
*
* @return array
*
*/
// TESTME needs unit testing
function cache_alert_maintenance()
{
$return = array();
$now = time();
$maints = dbFetchRows("SELECT * FROM `alerts_maint` WHERE `maint_start` < ? AND `maint_end` > ?", array($now, $now));
if (is_array($maints) && count($maints)) {
$return['count'] = count($maints);
foreach ($maints as $maint) {
if ($maint['maint_global'] == 1) {
$return['global'][$maint['maint_id']] = $maint;
} else {
$assocs = dbFetchRows("SELECT * FROM `alerts_maint_assoc` WHERE `maint_id` = ?", array($maint['maint_id']));
foreach ($assocs as $assoc) {
switch ($assoc['entity_type']) {
case "group": // this is a group, so expand it's members into an array
$group = get_group_by_id($assoc['entity_id']);
$entities = get_group_entities($assoc['entity_id']);
foreach ($entities as $entity) {
$return[$group['entity_type']][$entity] = TRUE;
}
break;
default:
$return[$assoc['entity_type']][$assoc['entity_id']] = TRUE;
break;
}
}
}
}
}
//print_r($return);
return $return;
}
function get_alert_entities($ids)
{
$array = array();
if (!is_array($ids)) { $ids = array($ids); }
foreach ($ids as $alert_id)
{
foreach (dbFetchRows("SELECT * FROM `alert_table` WHERE `alert_test_id` = ?", array($alert_id)) as $entry)
{
$array[$entry['entity_id']] = array('entity_id' => $entry['entity_id'], 'device_id' => $entry['device_id']);
}
}
return $array;
}
function get_maintenance_associations($maint_id = NULL)
{
$return = array();
# if ($maint_id)
# {
$assocs = dbFetchRows("SELECT * FROM `alerts_maint_assoc` WHERE `maint_id` = ?", array($maint_id));
# } else {
# $assocs = dbFetchRows("SELECT * FROM `alerts_maint_assoc`");
# }
foreach ($assocs as $assoc) {
$return[$assoc['entity_type']][$assoc['entity_id']] = TRUE;
}
return $return;
}
/**
* Build an array of all conditions
*
* @return array
*/
// TESTME needs unit testing
function cache_conditions()
{
$cache = array();
foreach (dbFetchRows("SELECT * FROM `alert_tests`") as $entry) {
$cache['cond'][$entry['alert_test_id']] = $entry;
$conditions = safe_json_decode($entry['conditions']);
$cache['cond'][$entry['alert_test_id']]['entity_type'] = $entry['entity_type'];
$cache['cond'][$entry['alert_test_id']]['conditions'] = $conditions;
}
foreach (dbFetchRows("SELECT * FROM `alert_assoc`") as $entry) {
$entity_attribs = safe_json_decode($entry['entity_attribs']);
$device_attribs = safe_json_decode($entry['device_attribs']);
$cache['assoc'][$entry['alert_assoc_id']] = $entry;
$cache['assoc'][$entry['alert_assoc_id']]['entity_attribs'] = $entity_attribs;
$cache['assoc'][$entry['alert_assoc_id']]['device_attribs'] = $device_attribs;
}
return $cache;
}
/**
* Compare two values
*
* @param string $value_a
* @param string $condition
* @param string|array $value_b
* @return boolean
*/
function test_condition($value_a, $condition, $value_b) {
// Clean values
if (is_string($value_a)) {
$value_a = trim($value_a);
}
if (is_string($value_b)) {
$value_b = trim($value_b);
}
// Condition & delimiters
$is_numeric_a = is_numeric($value_a);
$is_numeric = $is_numeric_a && is_numeric($value_b);
$condition = strtolower($condition);
$delimiters = array('/', '!', '@');
// numeric oid patterns (oid b can be partially regex
$oid_pattern_a = '/^(?:(?\.?)(?:\d+(?:\.\d+)+)|\.\d+)$/';
//$oid_pattern_b = '/^(?:(?\.?)(?:\d+(?:\.\d+)+)|\.\d+)(?\.)?$/';
$oid_pattern_b = '/^(?\.?)\d+(?:\.(\d+|[\d\[\]\-]+|[\d\(\)\|]+|[\d\*]+))*(?\.)?$/';
switch ($condition) {
case 'isnull':
case 'null':
$alert = is_null($value_a);
break;
case 'notnull':
case '!null':
$alert = !is_null($value_a);
break;
case 'ge':
case '>=':
if ($is_numeric) {
$alert = $value_a >= unit_string_to_numeric($value_b);
} elseif ($is_numeric_a && safe_empty($value_b)) {
// In case when use empty sensor_limit for compare, see: OBSENT-100
$alert = FALSE;
} else {
$alert = strnatcmp($value_a, unit_string_to_numeric($value_b)) >= 0;
}
break;
case 'le':
case '<=':
if ($is_numeric) {
$alert = $value_a <= unit_string_to_numeric($value_b);
} elseif ($is_numeric_a && safe_empty($value_b)) {
// In case when use empty sensor_limit for compare, see: OBSENT-100
$alert = FALSE;
} else {
$alert = strnatcmp($value_a, unit_string_to_numeric($value_b)) <= 0;
}
break;
case 'gt':
case 'greater':
case '>':
if ($is_numeric) {
$alert = $value_a > unit_string_to_numeric($value_b);
} elseif ($is_numeric_a && safe_empty($value_b)) {
// In case when use empty sensor_limit for compare, see: OBSENT-100
$alert = FALSE;
} else {
$alert = strnatcmp($value_a, unit_string_to_numeric($value_b)) > 0;
}
break;
case 'lt':
case 'less':
case '<':
if ($is_numeric) {
$alert = $value_a < unit_string_to_numeric($value_b);
} elseif ($is_numeric_a && safe_empty($value_b)) {
// In case when use empty sensor_limit for compare, see: OBSENT-100
$alert = FALSE;
} else {
$alert = strnatcmp($value_a, unit_string_to_numeric($value_b)) < 0;
}
break;
case 'notequals':
case 'isnot':
case 'ne':
case '!=':
if ($is_numeric) {
$alert = $value_a != unit_string_to_numeric($value_b);
} elseif ($is_numeric_a && safe_empty($value_b)) {
// In case when use empty sensor_limit for compare, see: OBSENT-100
// FIXME. Not sure, logically this correct
$alert = TRUE;
} else {
$alert = strnatcmp($value_a, unit_string_to_numeric($value_b)) != 0;
}
break;
case 'equals':
case 'eq':
case 'is':
case '==':
case '=':
if ($is_numeric) {
$alert = $value_a == unit_string_to_numeric($value_b);
} elseif ($is_numeric_a && safe_empty($value_b)) {
// In case when use empty sensor_limit for compare, see: OBSENT-100
$alert = FALSE;
} else {
$alert = strnatcmp($value_a, unit_string_to_numeric($value_b)) == 0;
}
break;
case 'match':
case 'matches':
// Numeric OID matches
if (!$is_numeric && preg_match($oid_pattern_a, $value_a) &&
(is_array($value_b) || preg_match($oid_pattern_b, $value_b))) {
return match_oid_num($value_a, $value_b);
}
$value_b = str_replace([ '*', '?' ], [ '.*', '.' ], $value_b);
//$value_b = str_replace('?', '.', $value_b);
foreach ($delimiters as $delimiter) {
if (!str_contains($value_b, $delimiter)) {
break;
}
}
if (preg_match($delimiter . '^' . $value_b . '$' . $delimiter, $value_a)) {
$alert = TRUE;
} else {
$alert = FALSE;
}
break;
case 'notmatches':
case 'notmatch':
case '!match':
// Numeric OID matches
if (!$is_numeric && preg_match('/^(?:(?\.?)(?:\d+(?:\.\d+)+)|\.\d+)$/', $value_a) &&
(is_array($value_b) || preg_match('/^(?:(?\.?)(?:\d+(?:\.\d+)+)|\.\d+)(?\.)?$/', $value_b))) {
return !match_oid_num($value_a, $value_b);
}
$value_b = str_replace([ '*', '?' ], [ '.*', '.' ], $value_b);
//$value_b = str_replace('?', '.', $value_b);
foreach ($delimiters as $delimiter) {
if (!str_contains($value_b, $delimiter)) {
break;
}
}
if (preg_match($delimiter . '^' . $value_b . '$' . $delimiter, $value_a)) {
$alert = FALSE;
} else {
$alert = TRUE;
}
break;
case 'regexp':
case 'regex':
foreach ($delimiters as $delimiter) {
if (!str_contains($value_b, $delimiter)) {
break;
}
}
if (preg_match($delimiter . $value_b . $delimiter, $value_a)) {
$alert = TRUE;
} else {
$alert = FALSE;
}
break;
case 'notregexp':
case 'notregex':
case '!regexp':
case '!regex':
foreach ($delimiters as $delimiter) {
if (!str_contains($value_b, $delimiter)) {
break;
}
}
if (preg_match($delimiter . $value_b . $delimiter, $value_a)) {
$alert = FALSE;
} else {
$alert = TRUE;
}
break;
case 'in':
case 'list':
if (!is_array($value_b)) {
$value_b = array_map('trim', explode(',', $value_b));
}
foreach($value_b as $value) {
if ($value == $value_a) {
$alert = TRUE;
break 2;
}
}
$alert = FALSE;
// in_array doesn't seem to behave how one would expect
//$alert = in_array($value_a, $value_b);
break;
case '!in':
case '!list':
case 'notin':
case 'notlist':
if (!is_array($value_b)) {
$value_b = array_map('trim', explode(',', $value_b));
}
foreach($value_b as $value) {
if ($value == $value_a) {
$alert = FALSE;
break 2; // break out of foreach loop and which
}
}
$alert = TRUE;
// in_array doesn't seem to behave how one would expect
//$alert = !in_array($value_a, $value_b);
break;
case 'between':
case 'notbetween':
$alert = FALSE;
if (!is_array($value_b)) {
$value_b = array_map('trim', explode(',', $value_b));
} // perhaps extend to allow space-separated values
if(isset($value_b[0]) && is_numeric($value_b[0]) && isset($value_b[1]) && is_numeric($value_b[1]))
{
if($condition == "between") {
if ($value_a < $value_b[0] || $value_a > $value_b[1]) { $alert = TRUE; }
} else { // notbetween
if ($value_a > $value_b[0] && $value_a < $value_b[1]) { $alert = TRUE; }
}
} else {
print_debug("ERROR: Invalid values passed to 'between' operator in test_condition().");
}
break;
default:
print_debug("ERROR: Unknown condition '$condition' passed to test_condition().");
$alert = FALSE;
break;
}
return $alert;
}
/**
* Test if a device matches a set of attributes
* Matches using the database entry for the supplied device_id
*
* @param array device
* @param array attributes
* @return boolean
*/
// TESTME needs unit testing
function match_device($device, $attributes, $ignore = TRUE)
{
// Short circuit this check if the device is either disabled or ignored.
if ($ignore && ($device['disable'] == 1 || $device['ignore'] == 1)) {
return FALSE;
}
$query = "SELECT COUNT(*) FROM `devices` AS d";
$join = "";
$where = " WHERE d.`device_id` = ?";
$params = array($device['device_id']);
foreach ($attributes as $attrib) {
switch ($attrib['condition']) {
case 'ge':
case '>=':
$where .= ' AND d.`' . $attrib['attrib'] . '` >= ?';
$params[] = $attrib['value'];
break;
case 'le':
case '<=':
$where .= ' AND d.`' . $attrib['attrib'] . '` <= ?';
$params[] = $attrib['value'];
break;
case 'gt':
case 'greater':
case '>':
$where .= ' AND d.`' . $attrib['attrib'] . '` > ?';
$params[] = $attrib['value'];
break;
case 'lt':
case 'less':
case '<':
$where .= ' AND d.`' . $attrib['attrib'] . '` < ?';
$params[] = $attrib['value'];
break;
case 'notequals':
case 'isnot':
case 'ne':
case '!=':
$where .= ' AND d.`' . $attrib['attrib'] . '` != ?';
$params[] = $attrib['value'];
break;
case 'equals':
case 'eq':
case 'is':
case '==':
case '=':
$where .= ' AND d.`' . $attrib['attrib'] . '` = ?';
$params[] = $attrib['value'];
break;
case 'match':
case 'matches':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") LIKE ?';
$params[] = $attrib['value'];
break;
case 'notmatches':
case 'notmatch':
case '!match':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") NOT LIKE ?';
$params[] = $attrib['value'];
break;
case 'regexp':
case 'regex':
$where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") REGEXP ?';
$params[] = $attrib['value'];
break;
case 'notregexp':
case 'notregex':
case '!regexp':
case '!regex':
$where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") NOT REGEXP ?';
$params[] = $attrib['value'];
break;
case 'in':
case 'list':
$where .= generate_query_values_and(explode(',', $attrib['value']), 'd.' . $attrib['attrib']);
break;
case '!in':
case '!list':
case 'notin':
case 'notlist':
$where .= generate_query_values_and(explode(',', $attrib['value']), 'd.' . $attrib['attrib'], '!=');
break;
case 'include':
case 'includes':
switch ($attrib['attrib']) {
case 'group':
$join .= " INNER JOIN `group_table` USING(`device_id`)";
$join .= " INNER JOIN `groups` USING(`group_id`)";
$where .= " AND `group_name` = ?";
$params[] = $attrib['value'];
break;
case 'group_id':
$join .= " INNER JOIN `group_table` USING(`device_id`)";
$where .= " AND `group_id` = ?";
$params[] = $attrib['value'];
break;
}
break;
}
}
$query .= $join . $where;
$device_count = dbFetchCell($query, $params);
if ($device_count == 0) {
return FALSE;
} else {
return TRUE;
}
}
/**
* Return an array of entities of a certain type which match device_id and entity attribute rules.
*
* @param integer device_id
* @param array attributes
* @param string entity_type
* @return array
*/
// TESTME needs unit testing
function match_device_entities($device_id, $entity_attribs, $entity_type)
{
// FIXME - this is going to be horribly slow.
$e_type = $entity_type;
$entity_type = entity_type_translate_array($entity_type);
if (!is_array($entity_type)) {
return NULL;
} // Do nothing if entity type unknown
$param = array();
$sql = "SELECT * FROM `" . dbEscape($entity_type['table']) . "`"; // FIXME. Not sure why these required escape table name
if(isset($entity_type['parent_table']) && isset($entity_type['parent_id_field']))
{
$sql .= ' LEFT JOIN `'.$entity_type['parent_table'].'` USING (`'.$entity_type['parent_id_field'].'`)';
}
$sql .= " WHERE `" . dbEscape($entity_type['table']) . "`.device_id = ?";
if (isset($entity_type['where'])) {
$sql .= ' AND ' . $entity_type['where'];
}
$param[] = $device_id;
if (isset($entity_type['deleted_field'])) {
$sql .= " AND `" . $entity_type['deleted_field'] . "` != ?";
$param[] = '1';
}
foreach ($entity_attribs as $attrib) {
switch ($attrib['condition']) {
case 'ge':
case '>=':
$sql .= ' AND `' . $attrib['attrib'] . '` >= ?';
$param[] = $attrib['value'];
break;
case 'le':
case '<=':
$sql .= ' AND `' . $attrib['attrib'] . '` <= ?';
$param[] = $attrib['value'];
break;
case 'gt':
case 'greater':
case '>':
$sql .= ' AND `' . $attrib['attrib'] . '` > ?';
$param[] = $attrib['value'];
break;
case 'lt':
case 'less':
case '<':
$sql .= ' AND `' . $attrib['attrib'] . '` < ?';
$param[] = $attrib['value'];
break;
case 'notequals':
case 'isnot':
case 'ne':
case '!=':
$sql .= ' AND `' . $attrib['attrib'] . '` != ?';
$param[] = $attrib['value'];
break;
case 'equals':
case 'eq':
case 'is':
case '==':
case '=':
$sql .= ' AND `' . $attrib['attrib'] . '` = ?';
$param[] = $attrib['value'];
break;
case 'match':
case 'matches':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") LIKE ?';
$param[] = $attrib['value'];
break;
case 'notmatches':
case 'notmatch':
case '!match':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT LIKE ?';
$param[] = $attrib['value'];
break;
case 'regexp':
case 'regex':
$sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") REGEXP ?';
$param[] = $attrib['value'];
break;
case 'notregexp':
case 'notregex':
case '!regexp':
case '!regex':
$sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT REGEXP ?';
$param[] = $attrib['value'];
break;
case 'in':
case 'list':
$sql .= generate_query_values_and(explode(',', $attrib['value']), $attrib['attrib'], NULL, [ 'ifnull' ]);
break;
case '!in':
case '!list':
case 'notin':
case 'notlist':
$sql .= generate_query_values_and(explode(',', $attrib['value']), $attrib['attrib'], '!=', [ 'ifnull' ]);
break;
case 'include':
case 'includes':
switch ($attrib['attrib']) {
case 'group':
$group = get_group_by_name($attrib['value']);
if ($group['entity_type'] == $e_type) {
$attrib['value'] = $group['group_id'];
}
case 'group_id':
$values = get_group_entities($attrib['value']);
$sql .= generate_query_values_and($values, $entity_type['table_fields']['id']);
break;
}
}
}
// print_vars(array($sql, $param));
//logfile('alerts.log', $sql);
//logfile('alerts.log', var_export($param, TRUE));
return dbFetchRows($sql, $param);
}
/**
* Test if an entity matches a set of attributes
* Uses a supplied device array for matching.
*
* @param array entity
* @param array attributes
* @return boolean
*/
// TESTME needs unit testing
function match_entity($entity, $entity_attribs)
{
// FIXME. Never used, deprecated?
#print_vars($entity);
#print_vars($entity_attribs);
$failed = 0;
$success = 0;
$delimiters = array('/', '!', '@');
foreach ($entity_attribs as $attrib) {
switch ($attrib['condition']) {
case 'equals':
if (mb_strtolower($entity[$attrib['attrib']]) == mb_strtolower($attrib['value'])) {
$success++;
} else {
$failed++;
}
break;
case 'match':
$attrib['value'] = str_replace('*', '.*', $attrib['value']);
$attrib['value'] = str_replace('?', '.', $attrib['value']);
foreach ($delimiters as $delimiter) {
if (!str_contains($attrib['value'], $delimiter)) {
break;
}
}
if (preg_match($delimiter . '^' . $attrib['value'] . '$' . $delimiter . 'i', $entity[$attrib['attrib']])) {
$success++;
} else {
$failed++;
}
break;
}
}
if ($failed) {
return FALSE;
} else {
return TRUE;
}
}
// DOCME needs phpdoc block
// CLEANME
/* not used anymore
function update_device_alert_table($device)
{
$dbc = array();
$alert_table = array();
$msg = "Building alerts for device " . $device['hostname'] . ':
';
$msg_class = '';
$msg_enable = FALSE;
$conditions = cache_device_conditions($device);
//foreach ($conditions['cond'] as $test_id => $test)
//{
// #print_vars($test);
// #echo('Matched '.$test['alert_name'].' ');
//}
$db_cs = dbFetchRows("SELECT * FROM `alert_table` WHERE `device_id` = ?", array($device['device_id']));
foreach ($db_cs as $db_c) {
$dbc[$db_c['entity_type']][$db_c['entity_id']][$db_c['alert_test_id']] = $db_c;
}
$msg .= PHP_EOL;
$msg .= ' Checkers matching this device:
';
foreach ($conditions['cond'] as $alert_test_id => $alert_test) {
$msg .= '' . $alert_test['alert_name'] . ' ';
$msg_enable = TRUE;
foreach ($alert_test['assoc'] as $assoc_id => $assoc) {
// Check that the entity_type matches the one we're interested in.
// echo("Matching $assoc_id (".$assoc['entity_type'].")");
list($entity_table, $entity_id_field, $entity_name_field) = entity_type_translate($assoc['entity_type']);
$alert = $conditions['cond'][$assoc['alert_test_id']];
$entities = match_device_entities($device['device_id'], $assoc['entity_attribs'], $assoc['entity_type']);
foreach ($entities AS $id => $entity) {
$alert_table[$assoc['entity_type']][$entity[$entity_id_field]][$assoc['alert_test_id']][] = $assoc_id;
}
// echo(count($entities)." matched".PHP_EOL);
}
}
$msg .= PHP_EOL;
$msg .= ' Matching entities:
';
foreach ($alert_table as $entity_type => $entities) {
foreach ($entities as $entity_id => $entity) {
$entity_name = entity_name($entity_type, $entity_id);
$msg .= '' . htmlentities($entity_name) . ' ';
$msg_enable = TRUE;
foreach ($entity as $alert_test_id => $b) {
# echo(str_pad($entity_type, "20").str_pad($entity_id, "20").str_pad($alert_test_id, "20"));
# echo(str_pad(implode($b,","), "20"));
$msg .= '' . $conditions['cond'][$alert_test_id]['alert_name'] . '
';
$msg_class = 'success';
if (isset($dbc[$entity_type][$entity_id][$alert_test_id]))
{
if ($dbc[$entity_type][$entity_id][$alert_test_id]['alert_assocs'] != implode(",", $b)) {
$update_array = array('alert_assocs' => implode(",", $b));
}
#echo("[".$dbc[$entity_type][$entity_id][$alert_test_id]['alert_assocs']."][".implode($b,",")."]");
if (is_array($update_array)) {
dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($dbc[$entity_type][$entity_id][$alert_test_id]['alert_table_id']));
unset($update_array);
}
unset($dbc[$entity_type][$entity_id][$alert_test_id]);
} else {
$alert_table_id = dbInsert(array('device_id' => $device['device_id'], 'entity_type' => $entity_type, 'entity_id' => $entity_id, 'alert_test_id' => $alert_test_id, 'alert_assocs' => implode(",", $b)), 'alert_table');
//dbInsert(array('alert_table_id' => $alert_table_id), 'alert_table-state');
}
}
}
}
$msg .= PHP_EOL;
$msg .= " Checking for stale entries:
";
foreach ($dbc as $type => $entity) {
foreach ($entity as $entity_id => $alert) {
foreach ($alert as $alert_test_id => $data) {
dbDelete('alert_table', "`alert_table_id` = ?", array($data['alert_table_id']));
//dbDelete('alert_table-state', "`alert_table_id` = ?", array($data['alert_table_id']));
$msg .= "-";
$msg_enable = TRUE;
}
}
}
if ($msg_enable) {
return (array('message' => $msg, 'class' => $msg_class));
}
}
*/
/**
* Check all alerts for a device to see if they should be notified or not
*
* @param array device
* @return NULL
*/
// TESTME needs unit testing
function process_alerts($device) {
global $config, $alert_rules, $alert_assoc;
$pid_info = check_process_run($device); // This just clear stalled DB entries
add_process_info($device); // Store process info
print_cli_heading($device['hostname'] . " [" . $device['device_id'] . "]", 1);
$alert_table = cache_device_alert_table($device['device_id']);
$sql = "SELECT * FROM `alert_table`";
//$sql .= " LEFT JOIN `alert_table-state` ON `alert_table`.`alert_table_id` = `alert_table-state`.`alert_table_id`";
$sql .= " WHERE `device_id` = ? AND `alert_status` IS NOT NULL;";
$db_multi_update = [];
foreach (dbFetchRows($sql, [ $device['device_id'] ]) as $entry) {
print_cli_data_field('Alert: ' . $entry['alert_table_id']);
print_cli('Status: [' . $entry['alert_status'] . '] ', 'color');
// If the alerter is now OK and has previously alerted, send an recovery notice.
if ($entry['alert_status'] == '1' && $entry['has_alerted'] == '1') {
$alert = $alert_rules[$entry['alert_test_id']];
if (!$alert['suppress_recovery']) {
$log_id = log_alert('Recovery notification sent', $device, $entry, 'RECOVER_NOTIFY');
alert_notifier($entry, "recovery", $log_id);
} else {
echo('Recovery suppressed.');
$log_id = log_alert('Recovery notification suppressed', $device, $entry, 'RECOVER_SUPPRESSED');
}
$db_multi_update[] = [
'alert_table_id' => $entry['alert_table_id'],
'last_recovered' => time(),
'last_alerted' => $entry['last_alerted'], // keep previous
'has_alerted' => 0
];
// $update_array['last_recovered'] = time();
// $update_array['has_alerted'] = 0;
// dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($entry['alert_table_id']));
} elseif ($entry['alert_status'] == '0') {
echo('Alert tripped. ');
// Has this been alerted more frequently than the alert interval in the config?
/// FIXME -- this should be configurable per-entity or per-checker
if ((time() - $entry['last_alerted']) < $config['alerts']['interval'] && !isset($GLOBALS['spam'])) {
$entry['suppress_alert'] = TRUE;
}
// Don't re-alert if interval set to 0
if ($config['alerts']['interval'] == 0 && $entry['last_alerted'] != 0) {
$entry['suppress_alert'] = TRUE;
}
// Check if alert has ignore_until set.
if (is_numeric($entry['ignore_until']) && $entry['ignore_until'] > time()) {
$entry['suppress_alert'] = TRUE;
}
// Check if alert has ignore_until_ok set.
if (is_numeric($entry['ignore_until_ok']) && $entry['ignore_until_ok'] == '1') {
$entry['suppress_alert'] = TRUE;
}
if ($entry['suppress_alert'] != TRUE) {
echo('Requires notification. ');
$log_id = log_alert('Alert notification sent', $device, $entry, 'ALERT_NOTIFY');
alert_notifier($entry, "alert", $log_id);
$db_multi_update[] = [
'alert_table_id' => $entry['alert_table_id'],
'last_recovered' => $entry['last_recovered'], // keep previous
'last_alerted' => time(),
'has_alerted' => 1
];
// $update_array['last_alerted'] = time();
// $update_array['has_alerted'] = 1;
// dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($entry['alert_table_id']));
} else {
echo("No notification required. " . (time() - $entry['last_alerted']));
}
} elseif ($entry['alert_status'] == '1') {
echo("Status: OK. ");
} elseif ($entry['alert_status'] == '2') {
echo("Status: Notification Delayed. ");
} elseif ($entry['alert_status'] == '3') {
echo("Status: Notification Suppressed. ");
} else {
echo("Unknown status.");
}
echo(PHP_EOL);
}
if (!empty($db_multi_update)) {
dbUpdateMulti($db_multi_update, 'alert_table');
}
echo(PHP_EOL);
print_cli_heading($device['hostname'] . " [" . $device['device_id'] . "] completed notifications at " . date("Y-m-d H:i:s"), 1);
// Clean
del_process_info($device); // Remove process info
}
/**
* Generate notification queue entries for alert system
*
* @param array $entry Entry
* @param string $type Alert type (alert (default) or syslog)
* @param integer $log_id Alert log entry ID
* @return array List of processed notification ids.
*/
function alert_notifier($entry, $type = "alert", $log_id = NULL)
{
$device = device_by_id_cache($entry['device_id']);
$message_tags = alert_generate_tags($entry, $type);
//logfile('debug.log', var_export($message, TRUE));
$alert_id = $entry['alert_test_id'];
$notify_status = FALSE; // Set alert notify status to FALSE by default
$notification_type = 'alert';
$contacts = get_alert_contacts($device, $alert_id, $notification_type);
$notification_ids = array(); // Init list of Notification IDs
foreach ($contacts as $contact)
{
// Add notification to queue
$notification = array(
'device_id' => $device['device_id'],
'log_id' => $log_id,
'aca_type' => $notification_type,
//'severity' => 6,
'endpoints' => safe_json_encode($contact),
'message_graphs' => $message_tags['ENTITY_GRAPHS_ARRAY'],
'notification_added' => time(),
'notification_lifetime' => 300, // Lifetime in seconds
'notification_entry' => safe_json_encode($entry), // Store full alert entry for use later if required (not sure that this needed)
);
$notification_message_tags = $message_tags;
unset($notification_message_tags['ENTITY_GRAPHS_ARRAY']); // graphs array stored in separate blob column message_graphs, do not duplicate this data
$notification['message_tags'] = safe_json_encode($notification_message_tags);
/// DEVEL
//file_put_contents('/tmp/alert_'.$alert_id.'_'.$message_tags['ALERT_STATE'].'_'.time().'.json', safe_json_encode($notification, JSON_PRETTY_PRINT));
$notification_id = dbInsert($notification, 'notifications_queue');
print_cli_data("Queueing Notification ", "[" . $notification_id . "]");
$notification_ids[] = $notification_id;
}
return $notification_ids;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function alert_generate_subject($device, $prefix, $message_tags) {
$subject = "$prefix: [" . device_name($device) . ']';
if ($message_tags['ENTITY_TYPE']) {
$subject .= ' [' . $message_tags['ENTITY_TYPE'] . ']';
}
if ($message_tags['ENTITY_NAME'] && $message_tags['ENTITY_NAME'] != $device['hostname']) {
$subject .= ' [' . $message_tags['ENTITY_NAME'] . ']';
}
$subject .= ' ' . $message_tags['ALERT_MESSAGE'];
return $subject;
}
function alert_generate_tags($entry, $type = "alert")
{
global $config, $alert_rules;
$alert_unixtime = time(); // Store time when alert processed
$device = device_by_id_cache($entry['device_id']);
$entity = get_entity_by_id_cache($entry['entity_type'], $entry['entity_id']);
$alert = $alert_rules[$entry['alert_test_id']];
/// DEVEL
print_debug_vars($entry);
print_debug_vars($alert);
print_debug_vars($entity);
/*
[conditions] => Array
(
[0] => Array
(
[metric] => sensor_value
[condition] => >
[value] => 30
)
[1] => Array
(
[metric] => sensor_value
[condition] => <
[value] => 2
)
)
*/
//$conditions = json_decode($alert['conditions'], TRUE);
// [state] => {"metrics":{"sensor_value":45},"failed":[{"metric":"sensor_value","condition":">","value":"30"}]}
$state = safe_json_decode($entry['state']);
$condition_array = [];
foreach ($state['failed'] as $failed)
{
$condition_array[] = $failed['metric'] . " " . $failed['condition'] . " " . format_value($failed['value']) . " (" . format_value($state['metrics'][$failed['metric']]) . ")";
}
$metric_array = [];
foreach ($state['metrics'] as $metric => $value)
{
$metric_array[] = $metric . ' = ' . $value;
}
$graphs = [];
$graph_done = [];
foreach ($state['metrics'] as $metric => $value)
{
if ($config['email']['graphs'] !== FALSE
&& is_array($config['entities'][$entry['entity_type']]['metric_graphs'][$metric])
&& !in_array($config['entities'][$entry['entity_type']]['metric_graphs'][$metric]['type'], $graph_done)
)
{
$graph_array = $config['entities'][$entry['entity_type']]['metric_graphs'][$metric];
foreach ($graph_array as $key => $val)
{
// Check to see if we need to do any substitution
if (str_starts($val, '@'))
{
$nval = substr($val, 1);
//echo(" replaced " . $val . " with " . $entity[$nval] . " from entity. " . PHP_EOL . "
");
$graph_array[$key] = $entity[$nval];
}
}
$image_data_uri = generate_alert_graph($graph_array);
$image_url = generate_graph_url($graph_array);
$graphs[] = array('label' => $graph_array['type'], 'type' => $graph_array['type'], 'url' => $image_url, 'data' => $image_data_uri);
$graph_done[] = $graph_array['type'];
}
unset($graph_array);
}
if ($config['email']['graphs'] !== FALSE && empty($graph_done) && is_array($config['entities'][$entry['entity_type']]['graph']))
{
// We can draw a graph for this type/metric pair!
$graph_array = $config['entities'][$entry['entity_type']]['graph'];
foreach ($graph_array as $key => $val)
{
// Check to see if we need to do any substitution
if (str_starts($val, '@'))
{
$nval = substr($val, 1);
//echo(" replaced ".$val." with ". $entity[$nval] ." from entity. ".PHP_EOL."
");
$graph_array[$key] = $entity[$nval];
}
}
//print_vars($graph_array);
$image_data_uri = generate_alert_graph($graph_array);
$image_url = generate_graph_url($graph_array);
$graphs[] = array('label' => $graph_array['type'], 'type' => $graph_array['type'], 'url' => $image_url, 'data' => $image_data_uri);
unset($graph_array);
}
//print_vars($graphs);
if (empty($alert['severity'])) { $alert['severity'] = 'crit'; }
// FIXME. This is how was previously, seems as need something change here?
$alert_duration = $entry['last_ok'] > 0 ? format_uptime($alert_unixtime - $entry['last_ok']) . " (" . format_unixtime($entry['last_ok']) . ")" : "Unknown";
if ($entry['alert_status'] == '1')
{
// RECOVER
$alert_state = 'RECOVER';
$alert_emoji = 'white_check_mark';
$alert_color = '';
}
elseif ($entry['has_alerted'])
{
// ALERT REMINDER by $config['alerts']['interval']
$alert_state = 'ALERT REMINDER';
$alert_emoji = 'repeat';
$alert_color = '';
} else {
// ALERT (first time)
$alert_state = 'ALERT';
$alert_emoji = $config['alert']['severity'][$alert['severity']]['emoji'];
$alert_color = $config['alert']['severity'][$alert['severity']]['color'];
}
$message_tags = [
'ALERT_STATE' => $alert_state,
'ALERT_EMOJI' => get_icon_emoji($alert_emoji), // https://unicodey.com/emoji-data/table.htm
'ALERT_EMOJI_NAME' => $alert_emoji,
'ALERT_STATUS' => $entry['alert_status'], // Tag for templates (0 - ALERT, 1 - RECOVERY, 2 - DELAYED, 3 - SUPPRESSED)
//'ALERT_SEVERITY' => $alert['severity'], // Only: crit(2), warn(4), info(6)
'ALERT_SEVERITY' => $config['alert']['severity'][$alert['severity']]['name'], // Critical, Warning, Informational
'ALERT_COLOR' => ltrim($alert_color, '#'),
'ALERT_URL' => generate_url([ 'page' => 'device',
'device' => $device['device_id'],
'tab' => 'alert',
'alert_entry' => $entry['alert_table_id'] ]),
'ALERT_UNIXTIME' => $alert_unixtime, // Standard unixtime
'ALERT_TIMESTAMP' => date('Y-m-d H:i:s P', $alert_unixtime), // ie: 2000-12-21 16:01:07 +02:00
'ALERT_TIMESTAMP_RFC2822' => date('r', $alert_unixtime), // RFC 2822, ie: Thu, 21 Dec 2000 16:01:07 +0200
'ALERT_TIMESTAMP_RFC3339' => date(DATE_RFC3339, $alert_unixtime), // RFC 3339, ie: 2005-08-15T15:52:01+00:00
'ALERT_ID' => $entry['alert_table_id'],
'ALERT_MESSAGE' => $alert['alert_message'],
'CONDITIONS' => implode(PHP_EOL . ' ', $condition_array),
'METRICS' => implode(PHP_EOL . ' ', $metric_array),
'DURATION' => $alert_duration,
// Entity TAGs
'ENTITY_URL' => generate_entity_url($entry['entity_type'], $entry['entity_id']),
'ENTITY_LINK' => generate_entity_link($entry['entity_type'], $entry['entity_id'], $entity['entity_name']),
'ENTITY_NAME' => $entity['entity_name'],
'ENTITY_ID' => $entity['entity_id'],
'ENTITY_TYPE' => $alert['entity_type'],
'ENTITY_DESCRIPTION' => $entity['entity_descr'],
//'ENTITY_GRAPHS' => $graphs_html, // Predefined/embedded html images
'ENTITY_GRAPHS_ARRAY' => safe_json_encode($graphs), // Json encoded images array
// Device TAGs
'DEVICE_HOSTNAME' => $device['hostname'],
'DEVICE_SYSNAME' => $device['sysName'],
//'DEVICE_SYSDESCR' => $device['sysDescr'],
'DEVICE_DESCRIPTION' => $device['purpose'],
'DEVICE_ID' => $device['device_id'],
'DEVICE_URL' => generate_device_url($device),
'DEVICE_LINK' => generate_device_link($device),
'DEVICE_HARDWARE' => $device['hardware'],
'DEVICE_OS' => $device['os_text'] . ' ' . $device['version'] . ($device['features'] ? ' (' . $device['features'] . ')' : ''),
'DEVICE_TYPE' => $device['type'],
'DEVICE_LOCATION' => $device['location'],
'DEVICE_UPTIME' => deviceUptime($device),
'DEVICE_REBOOTED' => format_unixtime($device['last_rebooted']),
];
$message_tags['TITLE'] = alert_generate_subject($device, $alert_state, $message_tags);
return $message_tags;
}
/**
* Get contacts associated with selected notification type and alert ID
* Currently know notification types: alert, syslog
*
* @param array $device Common device array
* @param int $alert_id Alert ID
* @param string $notification_type Used type for notifications
* @return array Array with transport -> endpoints lists
*/
function get_alert_contacts($device, $alert_id, $notification_type) {
if (!is_array($device)) {
$device = device_by_id_cache($device);
}
$contacts = array();
if ($device['ignore']) {
print_error("Device '{$device['hostname']}' set ignored in Device -> Edit -> Settings.");
return $contacts;
}
if ($GLOBALS['config']['alerts']['disable']['all']) {
print_error("Alert notifications disabled by \$config['alerts']['disable']['all'].");
return $contacts;
}
if (get_dev_attrib($device, 'disable_notify')) {
print_error("Alert notifications disabled for device '{$device['hostname']}' in Device -> Edit -> Alerts.");
return $contacts;
}
// figure out which transport methods apply to an alert
$sql = "SELECT * FROM `alert_contacts`";
$sql .= " WHERE `contact_disabled` = 0 AND `contact_id` IN";
$sql .= " (SELECT `contact_id` FROM `alert_contacts_assoc` WHERE `aca_type` = ? AND `alert_checker_id` = ?);";
$syscontact_exist = $GLOBALS['config']['email']['default_syscontact'];
$syscontact_id = 0;
foreach (dbFetchRows($sql, array($notification_type, $alert_id)) as $contact) {
if ($contact['contact_method'] === 'syscontact') {
$syscontact_exist = !$contact['contact_disabled'];
$syscontact_id = $contact['contact_id'];
continue;
}
$contacts[] = $contact;
}
// append syscontact as email transport
if ($syscontact_exist) {
// default device contact
if (get_dev_attrib($device, 'override_sysContact_bool')) {
$email = get_dev_attrib($device, 'override_sysContact_string');
} else {
if (parse_email($device['sysContact'])) {
$email = $device['sysContact'];
} else {
$email = $GLOBALS['config']['email']['default'];
}
}
foreach (parse_email($email) as $email => $descr) {
$contacts[] = array('contact_endpoint' => '{"email":"' . $email . '"}', 'contact_id' => $syscontact_id, 'contact_descr' => $descr, 'contact_method' => 'email');
print_debug("Added contact by device sysContact ($email, $descr).");
}
}
if (empty($contacts) && $GLOBALS['config']['email']['default_only'] &&
!safe_empty($GLOBALS['config']['email']['default'])) {
// if alert_contacts table is not in use, fall back to default
// hardcoded defaults for when there is no contact configured.
foreach (parse_email($GLOBALS['config']['email']['default']) as $email => $descr) {
$contacts[] = array('contact_endpoint' => '{"email":"' . $email . '"}', 'contact_id' => '0', 'contact_descr' => $descr, 'contact_method' => 'email');
print_debug("Added contact by default email config ($email, $descr).");
}
}
return $contacts;
}
function process_notifications($vars = []) {
global $config;
$result = [];
$params = [];
$sql = 'SELECT * FROM `notifications_queue` WHERE 1';
foreach ($vars as $var => $value) {
switch ($var) {
case 'device_id':
case 'notification_id':
case 'aca_type':
$sql .= generate_query_values_and($value, $var);
//$sql .= ' AND `device_id` = ?';
//$params[] = $value;
break;
}
}
/**
* switch ($notification_type)
* {
* case 'alert':
* case 'syslog':
* // Alerts/syslog required device_id
* $sql .= ' AND `device_id` = ?';
* $params[] = $device['device_id'];
* break;
* case 'web':
* // Currently not used
* break;
* }
**/
foreach (dbFetchRows($sql, $params) as $notification) {
print_debug_vars($notification);
// Recheck if current notification is locked
$locked = dbFetchCell('SELECT `notification_locked` FROM `notifications_queue` WHERE `notification_id` = ?', [ $notification['notification_id'] ]); //ALTER TABLE `notifications_queue` ADD `notification_locked` BOOLEAN NOT NULL DEFAULT FALSE AFTER `notification_entry`;
//if ($locked || $locked === NULL || $locked === FALSE) // If notification not exist or column 'notification_locked' not exist this query return NULL or (possible?) FALSE
if ($locked || $locked === FALSE) {
// Notification already processed by other alerter or has already been sent
print_debug('Notification ID ('.$notification['notification_id'].') locked or not exist anymore in table. Skipped.');
print_debug_vars($notification, 1);
continue;
}
// Lock current notification
dbUpdate([ 'notification_locked' => 1 ], 'notifications_queue', '`notification_id` = ?', [ $notification['notification_id'] ]);
$notification_count = 0;
$endpoint = safe_json_decode($notification['endpoints']);
// If this notification is older than lifetime, unset the endpoints so that it is removed.
if ((time() - $notification['notification_added']) > $notification['notification_lifetime']) {
$endpoint = [];
print_debug('Notification ID ('.$notification['notification_id'].') expired.');
print_debug_vars($notification, 1);
} else {
$notification_age = time() - $notification['notification_added'];
$notification_timeleft = $notification['notification_lifetime'] - $notification_age;
}
$message_tags = safe_json_decode($notification['message_tags']);
$message_graphs = safe_json_decode($notification['message_graphs']);
if (is_array($message_graphs) && count($message_graphs)) {
$message_tags['ENTITY_GRAPHS_ARRAY'] = $message_graphs;
}
if (isset($message_tags['ALERT_UNIXTIME']) && empty($message_tags['DURATION'])) {
$message_tags['DURATION'] = format_uptime(time() - $message_tags['ALERT_UNIXTIME']) . ' (' . $message_tags['ALERT_TIMESTAMP'] . ')';
}
if (isset($GLOBALS['config']['alerts']['disable'][$endpoint['contact_method']]) &&
$GLOBALS['config']['alerts']['disable'][$endpoint['contact_method']]) {
$result[$endpoint['contact_method']] = 'disabled';
unset($endpoint);
continue;
} // Skip if method disabled globally
$transport = $endpoint['contact_method']; // Just set transport name for use in includes
$method_include = $GLOBALS['config']['install_dir'] . '/includes/alerting/' . $transport . '.inc.php';
$is_transport_def = isset($config['transports'][$transport]['notification']); // Is definition for transport
$is_transport_file = is_file($method_include); // Is file based transport
if ($is_transport_def || $is_transport_file) {
//print_cli_data("Notifying", "[" . $endpoint['contact_method'] . "] " . $endpoint['contact_descr'] . ": " . $endpoint['contact_endpoint']);
print_cli_data_field("Notifying");
echo("[" . $endpoint['contact_method'] . "] " . $endpoint['contact_descr'] . ": " . $endpoint['contact_endpoint']);
// Split out endpoint data as stored JSON in the database into array for use in transport
// The original string also remains available as the contact_endpoint key
foreach (safe_json_decode($endpoint['contact_endpoint']) as $field => $value) {
$endpoint[$field] = $value;
}
// Clean data array for use with definition based processing
$data = [];
$message = [];
// File based transport
if ($is_transport_file) {
print_debug("\nUse file-based notification transport");
include($method_include);
}
// Definition based generate transport options, url and process request
if ($is_transport_def) {
print_debug("\nUse definition based notification transport");
$notification_def = $config['transports'][$transport]['notification'];
// Pass message tags to request as ARRAY (example in opsgenie)
if (isset($notification_def['message_tags']) && $notification_def['message_tags']) {
$data = array_merge($data, $message_tags);
unset($data['ENTITY_GRAPHS_ARRAY']);
//print_debug_vars(safe_json_encode($data));
} elseif (isset($notification_def['message_json'])) {
// Pass raw json as $data (example in webhook-json)
$json = array_tag_replace(generate_transport_tags($transport, $endpoint, [], [], array_map('json_escape', $message_tags)), $notification_def['message_json']);
//print_vars(generate_transport_tags($transport, $endpoint, [], [], array_map('json_escape', $message_tags)));
//print_vars($json);
$json_array = safe_json_decode($json);
//print_vars($json_array);
if (!safe_empty($json_array)) {
$data = array_merge($data, $json_array);
}
unset($json, $json_array);
} else {
// Or set common title tag
$message['title'] = $message_tags['TITLE'];
}
// Generate notification message from tags using mustache template system
if (isset($endpoint['contact_message_custom']) && $endpoint['contact_message_custom'] &&
empty(!$endpoint['contact_message_template'])) {
// Use user defined template
print_debug("User-defined message template is used.");
$message['text'] = simple_template($endpoint['contact_message_template'], $message_tags);
} elseif (isset($notification_def['message_template'])) {
print_debug("Definition message template file is used.");
// template can have tags (ie telegram)
if (str_contains($notification_def['message_template'], '%')) {
//print_vars($notification_def['message_template']);
$message_template = array_tag_replace(generate_transport_tags($transport, $endpoint), $notification_def['message_template']);
$message_template = strtolower($message_template);
//print_vars($message_template);
} else {
$message_template = $notification_def['message_template'];
}
// Template in file, see: includes/templates/notification/
$message['text'] = simple_template($message_template, $message_tags, [ 'is_file' => TRUE ]);
//$data['message'] = $message['text'];
} elseif (isset($notification_def['message_text'])) {
print_debug("Definition message template is used.");
// Template in definition
$message['text'] = simple_template($notification_def['message_text'], $message_tags);
//$data['message'] = $message['text'];
}
// Afterall message transform
if (isset($notification_def['message_transform']) && $message['text']) {
$message['text'] = string_transform($message['text'], $notification_def['message_transform']);
}
// Generate transport tags, used for rewrites in definition
$tags = generate_transport_tags($transport, $endpoint, $data, $message, $message_tags);
// Generate context/options with encoded data and transport specific api headers
$options = generate_http_context($transport, $tags, $data);
// Always get response also with bad status
$options['ignore_errors'] = TRUE;
// Retry count (default 1, max 10). See discord definition
$request_retry = 1;
$request_sleep = 0;
if (isset($notification_def['request_retry']) && is_intnum($notification_def['request_retry']) &&
$notification_def['request_retry'] > 1 && $notification_def['request_retry'] <= 10) {
$request_retry = $notification_def['request_retry'];
$request_sleep = 1;
}
// API URL to POST to
$url = generate_http_url($transport, $tags, $data);
// Send out API call and parse response
for ($retry = 1; $retry <= $request_retry; $retry++) {
print_debug("Request #$retry:");
if ($notify_status['success'] = test_http_request($transport, get_http_request($url, $options))) {
// stop for on success
break;
}
// wait little time
sleep($request_sleep);
}
// Clean after transport data and request generation
unset($message, $color, $url, $data, $options, $tags);
}
// FIXME check success
// FIXME log notification + success/failure!
if ($notify_status['success']) {
$result[$transport] = 'ok';
unset($endpoint);
$notification_count++;
print_message(" [%gOK%n]", 'color');
} else {
$result[$transport] = 'false';
print_message(" [%rFALSE%n]", 'color');
if ($notify_status['error']) {
print_cli_data_field('', 4);
print_message("[%y".$notify_status['error']."%n]", 'color');
}
}
} else {
$result[$transport] = 'missing';
unset($endpoint); // Remove it because it's dumb and doesn't exist. Don't retry it if it doesn't exist.
print_cli_data("Missing include", $method_include);
}
// Remove notification from queue,
// currently in any case, lifetime, added time and result status is ignored!
switch ($notification['aca_type']) {
case 'alert':
if ($notification_count) {
dbUpdate([ 'notified' => 1 ], 'alert_log', '`event_id` = ?', [ $notification['log_id'] ]);
}
break;
case 'syslog':
if ($notification_count) {
dbUpdate([ 'notified' => 1 ], 'syslog_alerts', '`lal_id` = ?', [ $notification['log_id'] ]);
}
break;
case 'web':
// Currently not used
break;
}
if (empty($endpoint)) {
dbDelete('notifications_queue', '`notification_id` = ?', [ $notification['notification_id'] ]);
} else {
// Set the endpoints to the remaining un-notified endpoints and unlock the queue entry.
dbUpdate(array('notification_locked' => 0, 'endpoints' => safe_json_encode($endpoint)), 'notifications_queue', '`notification_id` = ?', [ $notification['notification_id'] ]);
}
}
return $result;
}
// Use this function to write to the alert_log table
// Fix me - quite basic.
// DOCME needs phpdoc block
// TESTME needs unit testing
function log_alert($text, $device, $alert, $log_type)
{
$insert = [
'alert_test_id' => $alert['alert_test_id'],
'device_id' => $device['device_id'],
'entity_type' => $alert['entity_type'],
'entity_id' => $alert['entity_id'],
'timestamp' => [ "NOW()" ],
//'status' => $alert['alert_status'],
'log_type' => $log_type,
'message' => $text
];
return dbInsert($insert, 'alert_log');
}
function threshold_string($alert_low, $warn_low, $warn_high, $alert_high, $symbol = NULL)
{
// Generate "pretty" thresholds
if (is_numeric($alert_low))
{
$alert_low_t = format_value($alert_low, $format) . $symbol;
} else {
$alert_low_t = "∞";
}
if (is_numeric($warn_low))
{
$warn_low_t = format_value($warn_low, $format) . $symbol;
} else {
$warn_low_t = NULL;
}
if ($warn_low_t) { $alert_low_t = $alert_low_t . " (".$warn_low_t.")"; }
if (is_numeric($alert_high))
{
$alert_high_t = format_value($alert_high, $format) . $symbol;
} else {
$alert_high_t = "∞";
}
if (is_numeric($warn_high))
{
$warn_high_t = format_value($warn_high, $format) . $symbol;
} else {
$warn_high_t = NULL;
}
if ($warn_high_t) { $alert_high_t = "(".$warn_high_t.") " . $alert_high_t; }
$thresholds = $alert_low_t . ' - ' . $alert_high_t;
return $thresholds;
}
function check_thresholds($alert_low, $warn_low, $warn_high, $alert_high, $value)
{
if (!is_numeric($value)) { return 'alert'; } // Not numeric value always alert
if ((is_numeric($alert_low) && $value <= $alert_low) ||
(is_numeric($alert_high) && $value >= $alert_high))
{
$event = 'alert';
}
elseif ((is_numeric($warn_low) && $value < $warn_low) ||
(is_numeric($warn_high) && $value > $warn_high))
{
$event = 'warning';
} else {
$event = 'ok';
}
/*
if(is_numeric($warn_low) && $warn_low > $value) { $status = 'warn'; }
if(is_numeric($warn_high) && $warn_high < $value) { $status = 'warn'; }
if(is_numeric($alert_low) && $alert_low > $value) { $status = 'alert'; }
if(is_numeric($alert_high) && $alert_high < $value) { $status = 'alert'; }
*/
return $event;
}
/**
* Generate alert transport tags, used for transform any other parts of notification definition.
*
* @global array $config
* @param string $transport Alert transport key (see transports definitions)
* @param array $tags (optional) Contact array and other tags
* @param array $params (optional) Array of requested params with key => value entries (used with request method POST)
* @param array $message (optional) Array with some variants of alert message (ie text, html) and title
* @param array $message_tags (optional) Array with all message tags
* @return array HTTP Context which can used in get_http_request_test() or get_http_request()
*/
function generate_transport_tags($transport, $tags = [], $params = [], $message = [], $message_tags = []) {
global $config;
if (!isset($message['message'])) {
// Just use text version of message (also possible in future html, etc
$message['message'] = $message['text'];
}
$tags = array_merge($tags, $params, $message, $message_tags);
// If transport config options exist, merge it with tags array
// for use in replace/etc, ie: smsbox
if (isset($config[$transport])) {
foreach ($config[$transport] as $param => $value) {
if (!isset($tags[$param]) || $tags[$param] === '') {
$tags[$param] = $value;
}
}
}
// Set defaults and transform params if required
$def_params = [];
// Merge required/global and optional parameters
foreach (array_keys($config['transports'][$transport]['parameters']) as $tmp) {
$def_params = array_merge($def_params, $config['transports'][$transport]['parameters'][$tmp]);
}
foreach ($def_params as $param => $entry) {
// Set default if tag empty
if (isset($entry['default']) && safe_empty($tags[$param])) {
$tags[$param] = $entry['default'];
}
// Transform param if defined
if (isset($entry['transform'], $tags[$param])) {
$tags[$param] = string_transform($tags[$param], $entry['transform']);
}
}
//print_vars($tags);
// if ($config['transports'][$transport]['notification']['request_format'] === 'json') {
// // escape tags for json
// print_debug("Transport TAGs escaped for JSON.");
// if (OBS_DEBUG > 1) {
// print_debug_vars(array_map('json_escape', $tags));
// }
// return array_map('json_escape', $tags);
// }
return $tags;
}
function get_alert_entities_from_assocs($alert)
{
$entity_type_data = entity_type_translate_array($alert['entity_type']);
$entity_type = $alert['entity_type'];
$sql = 'SELECT `'.$entity_type_data['table_fields']['id'] . '` AS `entity_id`';
//We always need device_id and it's always from devices, duh!
//if ($alert['entity_type'] != 'device')
//{
$sql .= ", `devices`.`device_id` as `device_id`";
//}
$sql .= ' FROM `'.$entity_type_data['table'].'` ';
// if (isset($entity_type_data['state_table']))
// {
// $sql .= ' LEFT JOIN `'.$entity_type_data['state_table'].'` USING (`'.$entity_type_data['id_field'].'`)';
// }
if (isset($entity_type_data['parent_table'])) {
$sql .= ' LEFT JOIN `'.$entity_type_data['parent_table'].'` USING (`'.$entity_type_data['parent_id_field'].'`)';
}
if ($alert['entity_type'] !== 'device') {
$sql .= ' LEFT JOIN `devices` ON (`'.$entity_type_data['table'].'`.`device_id` = `devices`.`device_id`) ';
}
$params = [];
foreach ($alert['assocs'] as $assoc)
{
$where = ' (( 1';
foreach ($assoc['device_attribs'] as $attrib)
{
switch ($attrib['condition'])
{
case 'ge':
case '>=':
$where .= ' AND `devices`.`' . $attrib['attrib'] . '` >= ?';
$params[] = $attrib['value'];
break;
case 'le':
case '<=':
$where .= ' AND `devices`.`' . $attrib['attrib'] . '` <= ?';
$params[] = $attrib['value'];
break;
case 'gt':
case 'greater':
case '>':
$where .= ' AND `devices`.`' . $attrib['attrib'] . '` > ?';
$params[] = $attrib['value'];
break;
case 'lt':
case 'less':
case '<':
$where .= ' AND `devices`.`' . $attrib['attrib'] . '` < ?';
$params[] = $attrib['value'];
break;
case 'notequals':
case 'isnot':
case 'ne':
case '!=':
$where .= ' AND `devices`.`' . $attrib['attrib'] . '` != ?';
$params[] = $attrib['value'];
break;
case 'equals':
case 'eq':
case 'is':
case '==':
case '=':
$where .= ' AND `devices`.`' . $attrib['attrib'] . '` = ?';
$params[] = $attrib['value'];
break;
case 'match':
case 'matches':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") LIKE ?';
$params[] = $attrib['value'];
break;
case 'notmatches':
case 'notmatch':
case '!match':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") NOT LIKE ?';
$params[] = $attrib['value'];
break;
case 'regexp':
case 'regex':
$where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") REGEXP ?';
$params[] = $attrib['value'];
break;
case 'notregexp':
case 'notregex':
case '!regexp':
case '!regex':
$where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") NOT REGEXP ?';
$params[] = $attrib['value'];
break;
case 'in':
case 'list':
$where .= generate_query_values_and(get_var_csv($attrib['value']), '`devices`.' . $attrib['attrib'], NULL, [ 'ifnull' ]);
break;
case '!in':
case '!list':
case 'notin':
case 'notlist':
$where .= generate_query_values_and(get_var_csv($attrib['value']), '`devices`.' . $attrib['attrib'], '!=', [ 'ifnull' ]);
break;
case 'include':
case 'includes':
switch ($attrib['attrib'])
{
case 'group':
$attrib['value'] = group_id_by_name($attrib['value']);
case 'group_id':
$values = get_group_entities($attrib['value']);
$where .= generate_query_values_and($values, "`devices`.`device_id`");
break;
}
break;
}
} // End device_attribs
$where .= ") AND ( 1";
foreach ($assoc['entity_attribs'] as $attrib) {
switch ($attrib['condition']) {
case 'ge':
case '>=':
$where .= ' AND `' . $attrib['attrib'] . '` >= ?';
$params[] = $attrib['value'];
break;
case 'le':
case '<=':
$where .= ' AND `' . $attrib['attrib'] . '` <= ?';
$params[] = $attrib['value'];
break;
case 'gt':
case 'greater':
case '>':
$where .= ' AND `' . $attrib['attrib'] . '` > ?';
$params[] = $attrib['value'];
break;
case 'lt':
case 'less':
case '<':
$where .= ' AND `' . $attrib['attrib'] . '` < ?';
$params[] = $attrib['value'];
break;
case 'notequals':
case 'isnot':
case 'ne':
case '!=':
$where .= ' AND `' . $attrib['attrib'] . '` != ?';
$params[] = $attrib['value'];
break;
case 'equals':
case 'eq':
case 'is':
case '==':
case '=':
$where .= ' AND `' . $attrib['attrib'] . '` = ?';
$params[] = $attrib['value'];
break;
case 'match':
case 'matches':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") LIKE ?';
$params[] = $attrib['value'];
break;
case 'notmatches':
case 'notmatch':
case '!match':
$attrib['value'] = str_replace('*', '%', $attrib['value']);
$attrib['value'] = str_replace('?', '_', $attrib['value']);
$where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT LIKE ?';
$params[] = $attrib['value'];
break;
case 'regexp':
case 'regex':
$where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") REGEXP ?';
$params[] = $attrib['value'];
break;
case 'notregexp':
case 'notregex':
case '!regexp':
case '!regex':
$where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT REGEXP ?';
$params[] = $attrib['value'];
break;
case 'in':
case 'list':
$where .= generate_query_values_and(get_var_csv($attrib['value']), $attrib['attrib'], NULL, [ 'ifnull' ]);
break;
case '!in':
case '!list':
case 'notin':
case 'notlist':
$where .= generate_query_values_and(get_var_csv($attrib['value']), $attrib['attrib'], '!=', [ 'ifnull' ]);
break;
case 'include':
case 'includes':
switch ($attrib['attrib']) {
case 'group':
$attrib['value'] = group_id_by_name($attrib['value']);
case 'group_id':
$group = get_group_by_id($attrib['value']);
if($group['entity_type'] == $entity_type)
{
$values = get_group_entities($attrib['value']);
$where .= generate_query_values_and($values, $entity_type['table_fields']['id']);
}
break;
}
}
}
$where .= '))';
$assoc_where[] = $where;
}
if (empty($assoc_where)) {
print_debug('WARNING. Into function '.__FUNCTION__.'() passed incorrect or empty entries.');
return FALSE;
}
$where = "WHERE `devices`.`ignore` = '0' AND `devices`.`disabled` = '0' AND (" . implode(" OR ", $assoc_where) .")";
if (isset($entity_type_data['deleted_field'])) {
$where .= " AND `" . $entity_type_data['deleted_field'] . "` != '1'";
}
$query = $sql;
$query .= $where;
//$entities = dbFetchRows($query, $params, TRUE);
$return = [];
foreach(dbFetchRows($query, $params) as $entry) {
$return[$entry['entity_id']] = [ 'entity_id' => $entry['entity_id'], 'device_id' => $entry['device_id'] ];
}
return $return;
//print_vars($devices);
}
// QB / Alerts/ Groups common functions
// Because the order of key-value objects is uncertain, you can also use an array of one-element objects. (See query builder doc: http://querybuilder.js.org/#filters)
function values_to_json($values) {
$array = [];
//foreach($values as $id => $value) { $array[] = "'".$id."': '".str_replace(array("'", ","), array("\'", "\,")."'"; }
foreach ($values as $id => $value) {
//$array[] = '{ '.safe_json_encode($id).': '.safe_json_encode($value).' }';
// Expanded format with optgroups
// { value: 'one', label: 'Un', optgroup: 'Group 1' },
if (is_array($value)) {
// Our form builder params
$str = '{ value: '.safe_json_encode($id).', label: '.safe_json_encode($value['name']);
if (isset($value['group'])) {
$str .= ', optgroup: ' . safe_json_encode($value['group']);
}
$str .= ' }';
$array[] = $str;
} else {
// Simple value -> label
// { value: 'one', label: 'Un', optgroup: 'Group 1' },
$array[] = '{ value: '.safe_json_encode($id).', label: '.safe_json_encode($value).' }';
}
}
$array = ' [ '.implode(', ', $array).' ] ';
return $array;
}
function generate_attrib_values($attrib, $vars) {
$values = array();
//r($vars);
switch ($attrib)
{
case "device":
$values = generate_form_values('device', NULL, NULL, array('disabled' => TRUE));
//$devices = get_all_devices();
//foreach($devices as $id => $hostname)
//{
// $values[$id] = $hostname;
//}
break;
case "os":
foreach ($GLOBALS['config']['os'] AS $os => $os_array)
{
$values[$os] = $os_array['text'];
}
break;
case "measured_group":
$groups = get_groups_by_type($vars['measured_type']);
foreach ($groups[$vars['measured_type']] as $group_id => $array)
{
$values[$array['group_id']] = $array['group_name'];
}
break;
case "group":
$groups = get_groups_by_type($vars['entity_type']);
foreach ($groups[$vars['entity_type']] as $group_id => $array)
{
$values[$array['group_id']] = $array['group_name'];
}
break;
case "location":
foreach (get_locations() as $location) {
$values[$location] = $location;
}
break;
case "device_type":
foreach ($GLOBALS['config']['device_types'] AS $type)
{
$values[$type['type']] = $type['text'];
}
break;
case "device_vendor":
case "device_hardware":
case "device_distro":
case "device_distro_ver":
list(, $column) = explode('_', $attrib, 2);
$query = "SELECT DISTINCT `$column` FROM `devices`";
foreach (dbFetchColumn($query) as $item)
{
if (strlen($item)) { $values[$item] = $item; }
}
ksort($values);
break;
case "port_type":
$query = "SELECT DISTINCT `ifType` FROM `ports`";
foreach (dbFetchColumn($query) as $item)
{
$name = rewrite_iftype($item);
if ($name != $item)
{
$name = "$item ($name)";
}
if (strlen($item)) { $values[$item] = [ 'name' => $name ]; }
}
break;
/*
case "device_distro_ver":
$query = "SELECT `distro_ver` FROM `devices` GROUP BY `distro_ver` ORDER BY `distro_ver`";
foreach (dbFetchColumn($query) as $item)
{
if (strlen($item)) { $values[$item] = $item; }
}
break;
*/
case "sensor_class":
foreach($GLOBALS['config']['sensor_types'] AS $class => $data)
{
$values[$class] = nicecase($class);
}
break;
case "status_type":
$query = "SELECT `status_type` FROM `status` GROUP BY `status_type` ORDER BY `status_type`";
foreach (dbFetchColumn($query) as $item)
{
if (strlen($item)) { $values[$item] = $item; }
}
break;
}
return $values;
}
function generate_querybuilder_filter($attrib) {
if (isset($attrib['community']) && OBSERVIUM_EDITION === 'community' && !$attrib['community']) {
// Skip attribs not allowed in CE (ie device poller_id)
return '';
}
// Default operators, possible custom list from entity definition (ie group)
if (isset($attrib['operators'])) {
// All possible operators, for validate entity attrib
$operators_array = [ 'equals', 'notequals', 'le', 'ge', 'lt', 'gt', 'match', 'notmatch', 'regexp', 'notregexp', 'in', 'notin', 'isnull', 'isnotnull' ];
// List to array
if (!is_array($attrib['operators'])) {
$attrib['operators'] = explode(',', str_replace(' ', '', $attrib['operators']));
}
$operators = array_intersect($attrib['operators'], $operators_array); // Validate operators list
$text_operators = "['" . implode("', '", $operators) . "']";
} else {
$text_operators = "['equals', 'notequals', 'match', 'notmatch', 'regexp', 'notregexp', 'in', 'notin', 'isnull', 'isnotnull']";
}
$num_operators = "['equals', 'notequals', 'le', 'ge', 'lt', 'gt', 'in', 'notin']";
$list_operators = "['in', 'notin']";
$bool_operators = "['equals', 'notequals']";
$function_operators = "['in', 'notin']";
$attrib['attrib_id'] = ($attrib['entity_type'] === 'device' ? 'device.' : 'entity.').$attrib['attrib_id'];
$attrib['label'] = ($attrib['entity_type'] === 'device' ? 'Device ' : nicecase($attrib['entity_type']).' ').$attrib['label'];
// Clean label duplicates
$attrib['label'] = implode(' ', array_unique(explode(' ', $attrib['label'])));
//$attrib['label'] = str_replace("Device Device", "Device", $attrib['label']);
//r($attrib);
$filter_array[] = "id: '".$attrib['attrib_id']. ($attrib['free'] ? '.free': '')."'";
$filter_array[] = "field: '".$attrib['attrib_id']. "'";
$filter_array[] = "label: '".$attrib['label']. ($attrib['free'] ? ' (Free)': '')."'";
if ($attrib['type'] === 'boolean') {
// Prevent store boolean type as boolean true/false in DB, keep as integer
$filter_array[] = "type: 'integer'";
} else {
$filter_array[] = "type: '".$attrib['type']."'";
}
$filter_array[] = "optgroup: '".nicecase($attrib['entity_type'])."'";
// Plugins options:
$selectpicker_options = "width: '100%', iconBase: '', tickIcon: 'glyphicon glyphicon-ok', showTick: true, selectedTextFormat: 'count>2', ";
$tagsinput_options = "trimValue: true, tagClass: function(item) { return 'label label-default'; }";
if (isset($attrib['values'])) {
if (is_array($attrib['values'])) {
$value_list = [];
foreach ($attrib['values'] as $value) {
$value_list[$value] = $value;
}
} else {
$value_list = generate_attrib_values($attrib['values'], [ 'entity_type' => $attrib['entity_type'], 'measured_type' => $attrib['measured_type'] ]);
}
asort($value_list);
//r($value_list);
if (isset($attrib['tags']) && $attrib['tags']) {
$filter_array[] = "input: 'select'";
$filter_array[] = "plugin: 'tagsinput'";
$filter_array[] = "plugin_config: { $tagsinput_options }";
//$filter_array[] = "value_separator: ','";
$filter_array[] = "valueSetter: function(rule, value) {
var rule_container = rule.\$el.find('.rule-value-container select');
if (typeof value == 'string') {
rule_container.tagsinput('add', value);
} else {
for (i = 0; i < value.length; ++i) { rule_container.tagsinput('add', value[i]); }
}
}";
$filter_array[] = "multiple: true";
$filter_array[] = "operators: ".$function_operators;
// Fake form element generate, for create script
$tmp_item = [
'id' => 'test',
'values' => $value_list
];
generate_form_element($tmp_item, 'tags');
} else {
// Common multiselect list
if (count($value_list) > 7) {
$selectpicker_options .= "liveSearch: true, actionsBox: true, ";
}
$values = values_to_json($value_list);
$filter_array[] = "input: 'select'";
$filter_array[] = "plugin: 'selectpicker'";
$filter_array[] = "plugin_config: { $selectpicker_options }";
$filter_array[] = "values: ".$values;
$filter_array[] = "multiple: true";
$filter_array[] = "operators: ".$list_operators;
}
} elseif (isset($attrib['function']) && function_exists($attrib['function'])) {
if (isset($attrib['tags']) && !$attrib['tags']) {
// Same as values
$value_list = call_user_func($attrib['function']);
// Common multiselect list
if (count($value_list) > 7) {
$selectpicker_options .= "liveSearch: true, actionsBox: true, ";
}
$values = values_to_json($value_list);
$filter_array[] = "input: 'select'";
$filter_array[] = "plugin: 'selectpicker'";
$filter_array[] = "plugin_config: { $selectpicker_options }";
$filter_array[] = "values: ".$values;
$filter_array[] = "multiple: true";
$filter_array[] = "operators: ".$list_operators;
} else {
register_html_resource('js', 'bootstrap-tagsinput.min.js'); // Enable Tags Input JS
register_html_resource('css', 'bootstrap-tagsinput.css'); // Enable Tags Input CSS
$filter_array[] = "input: 'select'";
$filter_array[] = "plugin: 'tagsinput'";
$filter_array[] = "plugin_config: { $tagsinput_options }";
//$filter_array[] = "value_separator: ','";
$filter_array[] = "valueSetter: function(rule, value) {
var rule_container = rule.\$el.find('.rule-value-container select');
if (typeof value == 'string') {
rule_container.tagsinput('add', value);
} else {
for (i = 0; i < value.length; ++i) { rule_container.tagsinput('add', value[i]); }
}
}";
$filter_array[] = "multiple: true";
$filter_array[] = "operators: " . $function_operators;
}
} elseif ($attrib['type'] === 'integer') {
$filter_array[] = "operators: ".$num_operators;
} elseif ($attrib['type'] === 'boolean') {
$values = values_to_json(array(0 => 'False', 1 => 'True'));
$filter_array[] = "input: 'select'";
$filter_array[] = "plugin: 'selectpicker'";
$filter_array[] = "plugin_config: { $selectpicker_options }";
$filter_array[] = "values: ".$values;
$filter_array[] = "multiple: false";
$filter_array[] = "operators: ".$bool_operators;
} else {
$filter_array[] = "operators: ".$text_operators;
//$filter_array[] = "plugin: 'tagsinput'";
//$filter_array[] = "value_separator: ','";
}
return PHP_EOL . '{ '.implode(','.PHP_EOL, $filter_array).' } ';
}
function generate_querybuilder_filters($entity_type, $type = "attribs") {
$type = (($type === "attribs" || $type === "metrics") ? $type : 'attribs');
$def = $GLOBALS['config']['entities'][$entity_type];
if (isset($def['parent_type'])) {
$filter = generate_querybuilder_filters($def['parent_type']);
} elseif($type !== "metrics" && $entity_type !== "device") {
$filter = generate_querybuilder_filters("device");
}
//r($filter);
// Append Group attrib to any entity
if (OBSERVIUM_EDITION !== 'community' &&
$type === 'attribs' && !isset($def[$type]['group_id']) &&
!isset($def['parent_type'])) { // exclude on parent entities (ie for bgp afi/safi)
$add_group = [
'group_id' => array('label' => 'Group', 'descr' => 'Group Membership', 'type' => 'string', 'values' => 'group'),
'group' => array('label' => 'Group (Free)', 'descr' => 'Group Membership', 'type' => 'string', 'operators' => 'match, notmatch')
];
//$config['entities'][$entity]['attribs']['group_id'] = array('label' => 'Group', 'descr' => 'Group Membership', 'type' => 'string', 'values' => 'group');
//$config['entities'][$entity]['attribs']['group'] = array('label' => 'Group (Free)', 'descr' => 'Group Membership', 'type' => 'string', 'operators' => 'match, notmatch');
$def[$type] = array_merge($add_group, $def[$type]);
}
foreach ($def[$type] as $attrib_id => $attrib) {
$attrib['entity_type'] = $entity_type;
$attrib['attrib_id'] = $attrib_id;
$filter[] = generate_querybuilder_filter($attrib);
if (isset($attrib['values']) && !str_ends($attrib['attrib_id'], "_id") && // Don't show freeform variant for device_id, location_id, group_id and etc
(!isset($attrib['free']) || $attrib['free'])) { // Don't show freeform variant if attrib free set to false
unset($attrib['values']);
$attrib['free'] = 1;
$filter[] = generate_querybuilder_filter($attrib);
}
}
//$filters = ' [ '.implode(', ', $filter).' ] ';
//print_vars($filter);
return $filter;
}
function generate_querybuilder_form($entity_type, $type = "attribs", $form_id = 'rules-form', $ruleset = NULL) {
// Set rulesets, with allow invalid!
if (!empty($ruleset)) {
$rulescript = "
var rules = ".$ruleset.";
$('#".$form_id."').queryBuilder('setRules', rules, { allow_invalid: true });
$('#btn-set').on('click', function() {
$('#".$form_id."').queryBuilder('setRules', rules, { allow_invalid: true });
});";
register_html_resource('script', $rulescript);
}
$filters = ' [ '.implode(', ', generate_querybuilder_filters($entity_type, $type)).' ] ';
//$form_id = 'builder-'.$entity_type.'-'.$type;
echo ('
');
// Add CSRF Token
if (isset($_SESSION['requesttoken'])) {
echo generate_form_element([ 'type' => 'hidden', 'id' => 'requesttoken', 'value' => $_SESSION['requesttoken'] ]) . PHP_EOL;
}
echo ("
");
}
function parse_qb_ruleset($entity_type, $rules, $ignore = FALSE) {
$entity_type_data = entity_type_translate_array($entity_type);
$sql = 'SELECT `'.$entity_type_data['table_fields']['id'] . '`';
// Required in update_alert_table()
if ($entity_type !== 'device') {
$sql .= ", `devices`.`device_id` as `device_id`";
}
$sql .= ' FROM `'.$entity_type_data['table'].'` ';
// Join devices before parents
if ($entity_type !== 'device') {
//$sql .= ' LEFT JOIN `devices` USING (`device_id`)';
$sql .= ' LEFT JOIN `devices` ON (`'.$entity_type_data['table'].'`.`device_id` = `devices`.`device_id`) ';
}
// if (isset($entity_type_data['state_table'])) {
// $sql .= ' LEFT JOIN `'.$entity_type_data['state_table'].'` USING (`'.$entity_type_data['id_field'].'`)';
// }
if (isset($entity_type_data['parent_table'])) {
$sql .= ' LEFT JOIN `'.$entity_type_data['parent_table'].'` USING (`'.$entity_type_data['parent_id_field'].'`)';
}
$sql .= " WHERE ";
$sql .= parse_qb_rules($entity_type, $rules, $ignore);
if ($ignore) { // This is for alerting, so filter out ignore/disabled stuff
// Exclude ignored entities
if (isset($entity_type_data['ignore_field'])) {
$sql .= " AND `".$entity_type_data['table']."`.`" . $entity_type_data['ignore_field'] . "` != '1'";
}
// Exclude disabled entities
if (isset($entity_type_data['disable_field'])) {
$sql .= " AND `".$entity_type_data['table']."`.`" . $entity_type_data['disable_field'] . "` != '1'";
}
// Exclude disabled/ignored devices (if not device entity)
if ($entity_type !== 'device') {
$sql .= " AND `devices`.`disabled` != '1'";
$sql .= " AND `devices`.`ignore` != '1'";
}
}
if (isset($entity_type_data['deleted_field'])) {
$sql .= " AND `".$entity_type_data['table']."`.`" . $entity_type_data['deleted_field'] . "` != '1'";
}
//r($sql);
return $sql;
}
function parse_qb_rules($entity_type, $rules, $ignore = FALSE) {
global $config;
$entity_type_data = entity_type_translate_array($entity_type);
$entity_attribs = $config['entities'][$entity_type]['attribs'];
$parts = array();
foreach ($rules['rules'] as $rule) {
if (is_array($rule['rules'])) {
$parts[] = parse_qb_rules($entity_type, $rule);
} else {
//print_r($rule);
list($table, $field) = explode('.', $rule['field']);
if ($table === 'entity' || $table == $entity_type) {
$table_type_data = $entity_type_data;
} else {
// This entity can be not same as main entity!
$table_type_data = entity_type_translate_array($table);
}
// Pre Transform value according to DB field (see port ARP/MAC)
$rule['value_original'] = $rule['value'];
if (isset($entity_attribs[$field]['transform'])) {
$entity_attribs[$field]['transformations'] = $entity_attribs[$field]['transform'];
}
if (isset($entity_attribs[$field]['transformations'])) {
$rule['value'] = string_transform($rule['value'], $entity_attribs[$field]['transformations']);
}
$part = '';
// Check if field is measured entity
$field_measured = isset($entity_attribs[$field]['measured_type']) && // Attrib have measured type param
isset($config['entities'][$entity_attribs[$field]['measured_type']]); // And this entity type exist
if (isset($entity_attribs[$field]['function']) && function_exists($entity_attribs[$field]['function']) &&
(!isset($entity_attribs[$field]['tags']) || $entity_attribs[$field]['tags'])) { // i.e. device poller_id
// Pass original rule value, which translated to entity_id(s) by function call
if (isset($entity_attribs[$field]['function_entity_type'])) {
// Custom entity type
$function_args = [ $entity_attribs[$field]['function_entity_type'], $rule['value'] ];
} else {
$function_args = [ $entity_type, $rule['value'] ];
}
$rule['value'] = call_user_func_array($entity_attribs[$field]['function'], $function_args);
// Override $field by entity_id
$rule['field_quoted'] = '`'.$entity_type_data['table'].'`.`'.$entity_type_data['table_fields']['id'].'`';
//logfile('alerts.log', 'function_args: '.var_export($function_args, TRUE)); /// DEVEL
//logfile('alerts.log', 'rule value: '.var_export($rule['value'], TRUE)); /// DEVEL
//print_vars($function_args);
//print_vars($rule['value']);
} elseif ($field_measured) {
// This attrib is measured entity
//$measured_type = $entity_attribs[$field]['measured_type'];
//$measured_type_data = entity_type_translate_array($measured_type);
switch ($entity_attribs[$field]['values']) {
case 'measured_group':
// When values used as measured group, convert it to entity ids
//logfile('groups.log', 'passed value: '.var_export($rule['value'], TRUE)); /// DEVEL
$group_ids = !is_array($rule['value']) ? explode(',', $rule['value']) : $rule['value'];
$rule['value'] = get_group_entities($group_ids);
//logfile('groups.log', 'groups value: '.var_export($rule['value'], TRUE)); /// DEVEL
break;
default:
//$rule['field_quoted'] = '`'.$table_type_data['table'].'`.`'.$field.'`';
}
// Override $field by measured entity_id
$rule['field_quoted'] = '`'.$table_type_data['table'].'`.`'.$entity_type_data['table_fields']['measured_id'].'`';
//logfile('groups.log', 'value: '.var_export($rule['value'], TRUE)); /// DEVEL
//logfile('groups.log', 'field: '.$rule['field_quoted']); /// DEVEL
} elseif (isset($entity_attribs[$field]['table'])) {
// This attrib specifies a table name (used for oid, since there is no parent)
$rule['field_quoted'] = '`'.$entity_attribs[$field]['table'].'`.`'.$field.'`';
} elseif (!isset($entity_attribs[$field])
&& isset($config['entities'][$entity_type]['parent_type'])
&& isset($config['entities'][$config['entities'][$entity_type]['parent_type']]['attribs'][$field])) {
// This attrib does not exist on this entity && this entity has a parent && this attrib exists on the parent
$rule['field_quoted'] = '`'.$config['entities'][$config['entities'][$entity_type]['parent_type']]['table'].'`.`'.$field.'`';
} else {
//$rule['field_quoted'] = '`'.$field.'`';
// Always use full table.column, for do not get errors ambiguous (after JOINs)
$rule['field_quoted'] = '`'.$table_type_data['table'].'`.`'.$field.'`';
}
$operator_negative = FALSE; // Need for measured
switch ($rule['operator']) {
case 'ge':
$part = ' ' . $rule['field_quoted'] . " >= '" . dbEscape($rule['value']) . "'";
break;
case 'le':
$part = ' ' . $rule['field_quoted'] . " <= '" . dbEscape($rule['value']) . "'";
break;
case 'gt':
$part = ' ' . $rule['field_quoted'] . " > '" . dbEscape($rule['value']) . "'";
break;
case 'lt':
$part = ' ' . $rule['field_quoted'] . " < '" . dbEscape($rule['value']) . "'";
break;
case 'notequals':
$operator_negative = TRUE;
$part = ' ' . $rule['field_quoted'] . " != '" . dbEscape($rule['value']) . "'";
break;
case 'equals':
$part = ' ' . $rule['field_quoted'] . " = '" . dbEscape($rule['value']) . "'";
break;
case 'match':
switch ($field) {
case 'group':
//$group = get_group_by_name($rule['value_original']);
$group_ids = get_group_ids_by_name_match($rule['value_original'], $table);
//$group_ids = get_group_ids_by_name_match($rule['value_original'], $entity_type);
if ($values = get_group_entities($group_ids)) {
//$values = get_group_entities($group['group_id']);
$part = generate_query_values_ng($values, ($table === "device" ? "devices.device_id" : $table_type_data['table'] . '.' . $entity_type_data['table_fields']['id']));
}
break;
default:
$rule['value'] = str_replace([ '*', '?' ], [ '%', '_' ], $rule['value_original']);
//$rule['value'] = str_replace('?', '_', $rule['value']);
$part = ' IFNULL(' . $rule['field_quoted'] . ', "") LIKE' . " '" . dbEscape($rule['value']) . "'";
break;
}
break;
case 'notmatch':
$operator_negative = TRUE;
switch ($field) {
case 'group':
//$group = get_group_by_name($rule['value_original']);
$group_ids = get_group_ids_by_name_match($rule['value_original'], $table);
//$group_ids = get_group_ids_by_name_match($rule['value_original'], $entity_type);
if ($values = get_group_entities($group_ids)) {
//$values = get_group_entities($group['group_id']);
$part = generate_query_values_ng($values, ($table === "device" ? "devices.device_id" : $table_type_data['table'] . '.' . $entity_type_data['table_fields']['id']), '!=');
}
break;
default:
$rule['value'] = str_replace([ '*', '?' ], [ '%', '_' ], $rule['value_original']);
//$rule['value'] = str_replace('?', '_', $rule['value']);
$part = ' IFNULL(' . $rule['field_quoted'] . ', "") NOT LIKE' . " '" . dbEscape($rule['value']) . "'";
break;
}
break;
case 'regexp':
$part = ' IFNULL(' . $rule['field_quoted'] . ', "") REGEXP' . " '" . dbEscape($rule['value_original']) . "'";
break;
case 'notregexp':
$operator_negative = TRUE;
$part = ' IFNULL(' . $rule['field_quoted'] . ', "") NOT REGEXP' . " '" . dbEscape($rule['value_original']) . "'";
break;
case 'isnull':
$part = ' ' .$rule['field_quoted'] . ' IS NULL';
break;
case 'isnotnull':
$part = ' ' .$rule['field_quoted'] . ' IS NOT NULL';
break;
case 'in':
//print_vars($field);
//print_vars($rule);
switch ($field) {
case 'group_id':
$values = get_group_entities($rule['value_original']);
$part = generate_query_values_ng($values, ($table === "device" ? "devices.device_id" : $table_type_data['table'].'.'.$entity_type_data['table_fields']['id']));
break;
default:
if (isset($entity_attribs[$field]['transformations'])) {
$values = get_var_csv($rule['value_original']);
foreach ($values as &$value) {
$value = string_transform($value, $entity_attribs[$field]['transformations']);
}
} else {
// When transformations not used, can use other value overrides, ie function calls
$values = get_var_csv($rule['value']);
}
//r($values);
//logfile('alerts.log', $rule['field_quoted'] . ': ' . var_export($values, TRUE));
//logfile('alerts.log', var_export($rule, TRUE));
$part = generate_query_values_ng($values, $rule['field_quoted'], NULL, [ 'ifnull' ]);
break;
}
//print_vars($parts);
break;
case 'notin':
$operator_negative = TRUE;
switch ($field) {
case 'group_id':
$values = get_group_entities($rule['value_original']);
$part = generate_query_values_ng($values, ($table === "device" ? "devices.device_id" : $table_type_data['table'].'.'.$entity_type_data['table_fields']['id']), '!=');
break;
default:
if (isset($entity_attribs[$field]['transformations'])) {
$values = get_var_csv($rule['value_original']);
foreach ($values as &$value) {
$value = string_transform($value, $entity_attribs[$field]['transformations']);
}
} else {
// When transformations not used, can use other value overrides, ie function calls
$values = get_var_csv($rule['value']);
}
$part = generate_query_values_ng($values, $rule['field_quoted'], '!=', [ 'ifnull' ]);
break;
}
break;
}
// For measured field append measured
if ($field_measured && !safe_empty($part)) {
$measured_type = $entity_attribs[$field]['measured_type'];
$part = ' (`'.$table_type_data['table'].'`.`'.$entity_type_data['table_fields']['measured_type'] .
"` = '" . dbEscape($measured_type) . "' AND (" . $part . '))';
// For negative rule operators append all entities without measured type field
if ($operator_negative) {
$part = ' (`'.$table_type_data['table'].'`.`'.$entity_type_data['table_fields']['measured_type'] . '` IS NULL OR' . $part . ')';
}
}
//if ($field_measured) { logfile('groups.log', $part); } /// DEVEL
if (!safe_empty($part)) {
$parts[] = $part;
}
}
}
$sql = '(' . implode(" " . $rules['condition'], $parts) . ')';
//print_vars($parts);
//print_vars($sql);
//if ($field_measured) { logfile('groups.log', $sql); }
//logfile('groups.log', $sql); /// DEVEL
print_debug_vars($sql);
return $sql;
}
function migrate_assoc_rules($entry) {
$entity_type = $entry['entity_type'];
$ruleset = [];
$ruleset['condition'] = 'OR';
$ruleset['valid'] = 'true';
foreach ($entry['assocs'] as $assoc) {
$x = [];
$x['condition'] = 'AND';
$a = [ 'device' => $assoc['device_attribs'], 'entity' => $assoc['entity_attribs'] ];
foreach ($a as $type => $rules) {
foreach ($rules as $rule) {
if ($rule['attrib'] !== '*') {
if ($type === 'device' || $entity_type === 'device') {
$def = $GLOBALS['config']['entities'][$type]['attribs'][$rule['attrib']];
} else {
$def = $GLOBALS['config']['entities'][$entity_type]['attribs'][$rule['attrib']];
}
$e = [];
$e['id'] = ($type === 'device' ? 'device.' : 'entity.') . $rule['attrib'];
$e['field'] = $e['id'];
$e['type'] = $def['type'];
$e['value'] = $rule['value'];
switch ($rule['condition']) {
case 'ge':
case '>=':
$e['operator'] = "ge";
break;
case 'le':
case '<=':
$e['operator'] = "le";
break;
case 'gt':
case 'greater':
case '>':
$e['operator'] = "gt";
break;
case 'lt':
case 'less':
case '<':
$e['operator'] = "lt";
break;
case 'notequals':
case 'isnot':
case 'ne':
case '!=':
$e['operator'] = "notequals";
break;
case 'equals':
case 'eq':
case 'is':
case '==':
case '=':
$e['operator'] = "equals";
break;
case 'match':
case 'matches':
//$e['value'] = str_replace('*', '%', $e['value']);
//$e['value'] = str_replace('?', '_', $e['value']);
$e['operator'] = "match";
break;
case 'notmatches':
case 'notmatch':
case '!match':
//$e['value'] = str_replace('*', '%', $e['value']);
//$e['value'] = str_replace('?', '_', $e['value']);
$e['operator'] = "notmatch";
break;
case 'regexp':
case 'regex':
$e['operator'] = "regexp";
break;
case 'notregexp':
case 'notregex':
case '!regexp':
case '!regex':
$e['operator'] = "notregexp";
break;
case 'in':
case 'list':
$e['value'] = explode(',', $e['value']);
$e['operator'] = "in";
break;
case '!in':
case '!list':
case 'notin':
case 'notlist':
$e['value'] = explode(',', $e['value']);
$e['operator'] = "notin";
break;
case 'include':
case 'includes':
switch ($rule['attrib']) {
case 'group':
$e['operator'] = "match";
$e['type'] = 'text';
break;
case 'group_id':
$e['operator'] = "in";
$e['value'] = explode(',', $e['value']);
$e['type'] = 'select';
break;
}
break;
}
if (isset($def['values']) &&
in_array($e['operator'], [ "equals", "notequals", "match", "notmatch", "regexp", "notregexp" ])) {
$e['id'] .= ".free";
}
if (in_array($e['operator'], [ 'in', 'notin' ])) {
$e['input'] = 'select';
} elseif ($def['type'] === 'integer') {
$e['input'] = 'number';
} else {
$e['input'] = 'text';
}
$x['rules'][] = $e;
}
}
}
$ruleset['rules'][] = $x;
}
// Collapse group if there is only one entry.
$rules_count = count($ruleset['rules']);
if ($rules_count === 1) {
$ruleset['rules'] = $ruleset['rules'][0]['rules'];
$ruleset['condition'] = 'AND';
} elseif ($rules_count < 1) {
$ruleset['rules'][] = array('id' => 'device.hostname', 'field' => 'device.hostname', 'type' => 'string', 'value' => '*', 'operator' => 'match', 'input' => 'text');
}
return $ruleset;
}
function render_qb_rules($entity_type, $rules) {
$parts = [];
$entity_type_data = entity_type_translate_array($entity_type);
foreach ($rules['rules'] as $rule) {
if (is_array($rule['rules'])) {
$parts[] = render_qb_rules($entity_type, $rule);
} else {
list($table, $field) = explode('.', $rule['field']);
if ($table === "device") {
} elseif ($table === "entity") {
$table = $entity_type;
} elseif ($table === "parent") {
$table = $entity_type_data['parent_type'];
}
// Boolean stored as bool object, can not be displayed
if ($rule['type'] === 'boolean') {
$rule['value'] = (int)$rule['value'];
}
$values = is_array($rule['value']) ? implode('|', $rule['value']) : $rule['value'];
$parts[] = "" . escape_html("$table.$field " . $rule['operator']) . " " .
escape_html($values) . "
";
}
}
$part = implode(($rules['condition'] === "AND" ? ' AND ' : ' OR '), $parts);
if (count($parts) > 1) {
$part = '('.$part.')';
}
return $part;
}
// EOF