1<?php 2 3/** 4 * Observium Network Management and Monitoring System 5 * Copyright (C) 2006-2015, Adam Armstrong - http://www.observium.org 6 * 7 * @package observium 8 * @subpackage alerter 9 * @author Adam Armstrong <adama@observium.org> 10 * @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited 11 * 12 */ 13 14// DOCME needs phpdoc block 15// TESTME needs unit testing 16function get_contact_by_id($contact_id) 17{ 18 if (is_numeric($contact_id)) { 19 $contact = dbFetchRow('SELECT * FROM `alert_contacts` WHERE `contact_id` = ?', array($contact_id)); 20 } 21 if (is_array($contact) && count($contact)) { 22 return $contact; 23 } else { 24 return FALSE; 25 } 26} 27 28// DOCME needs phpdoc block 29// TESTME needs unit testing 30function get_alert_test_by_id($alert_test_id) 31{ 32 if (is_numeric($alert_test_id)) { 33 $alert_test = dbFetchRow('SELECT * FROM `alert_tests` WHERE `alert_test_id` = ?', array($alert_test_id)); 34 } 35 if (is_array($alert_test) && count($alert_test)) { 36 return $alert_test; 37 } else { 38 return FALSE; 39 } 40} 41 42/** 43 * Check an entity against all relevant alerts 44 * 45 * @param string type 46 * @param array entity 47 * @param array data 48 * @return NULL 49 */ 50// TESTME needs unit testing 51function check_entity($entity_type, $entity, $data) 52{ 53 global $config, $alert_rules, $alert_table, $device; 54 55 //r(array($entity_type, $entity, $data)); 56 57 $alert_output = ""; 58 59 $entity_data = entity_type_translate_array($entity_type); 60 $entity_id_field = $entity_data['id_field']; 61 $entity_ignore_field = $entity_data['ignore_field']; 62 63 $entity_id = $entity[$entity_id_field]; 64 65 // Hardcode time and weekday for global use. 66 $data['time'] = date('Hi'); 67 $data['weekday'] = date('N'); 68 69 if (!isset($alert_table[$entity_type][$entity_id])) { 70 return; 71 } // Just return to avoid PHP warnings 72 73 $alert_info = array('entity_type' => $entity_type, 'entity_id' => $entity_id); 74 75 foreach ($alert_table[$entity_type][$entity_id] as $alert_test_id => $alert_args) { 76 if ($alert_rules[$alert_test_id]['and']) { 77 $alert = TRUE; 78 } else { 79 $alert = FALSE; 80 } 81 82 $alert_info['alert_test_id'] = $alert_test_id; 83 84 $alert_checker = $alert_rules[$alert_test_id]; 85 86 $update_array = array(); 87 88 if (is_array($alert_rules[$alert_test_id])) { 89 //echo("Checking alert ".$alert_test_id." associated by ".$alert_args['alert_assocs']."\n"); 90 $alert_output .= $alert_rules[$alert_test_id]['alert_name'] . " ["; 91 92 foreach ($alert_rules[$alert_test_id]['conditions'] as $test_key => $test) { 93 if (substr($test['value'], 0, 1) == "@") { 94 $ent_val = substr($test['value'], 1); 95 $test['value'] = $entity[$ent_val]; 96 //echo(" replaced @".$ent_val." with ". $test['value'] ." from entity. "); 97 } 98 99 //echo("Testing: " . $test['metric']. " ". $test['condition'] . " " .$test['value']); 100 $update_array['state']['metrics'][$test['metric']] = $data[$test['metric']]; 101 102 if (isset($data[$test['metric']])) { 103 //echo(" (value: ".$data[$test['metric']].")"); 104 if (test_condition($data[$test['metric']], $test['condition'], $test['value'])) { 105 // A test has failed. Set the alert variable and make a note of what failed. 106 //print_cli("%R[FAIL]%N"); 107 $update_array['state']['failed'][] = $test; 108 109 if ($alert_rules[$alert_test_id]['and']) { 110 $alert = ($alert && TRUE); 111 } else { 112 $alert = ($alert || TRUE); 113 } 114 } else { 115 if ($alert_rules[$alert_test_id]['and']) { 116 $alert = ($alert && FALSE); 117 } else { 118 $alert = ($alert || FALSE); 119 } 120 //print_cli("%G[OK]%N"); 121 } 122 } else { 123 //echo(" Metric is not present on entity.\n"); 124 if ($alert_rules[$alert_test_id]['and']) { 125 $alert = ($alert && FALSE); 126 } else { 127 $alert = ($alert || FALSE); 128 } 129 } 130 } 131 132 if ($alert) { 133 // Check to see if this alert has been suppressed by anything 134 ## FIXME -- not all of this is implemented 135 136 // Have all alerts been suppressed? 137 if ($config['alerts']['suppress']) { 138 $alert_suppressed = TRUE; 139 $suppressed[] = "GLOBAL"; 140 } 141 142 // Is there a global scheduled maintenance? 143 if (isset($GLOBALS['cache']['maint']['global']) && count($GLOBALS['cache']['maint']['global']) > 0) { 144 $alert_suppressed = TRUE; 145 $suppressed[] = "MNT_GBL"; 146 } 147 148 // Have all alerts on the device been suppressed? 149 if ($device['ignore']) { 150 $alert_suppressed = TRUE; 151 $suppressed[] = "DEV"; 152 } 153 if ($device['ignore_until']) { 154 $device['ignore_until_time'] = strtotime($device['ignore_until']); 155 if ($device['ignore_until_time'] > time()) { 156 $alert_suppressed = TRUE; 157 $suppressed[] = "DEV_U"; 158 } 159 } 160 161 if (isset($GLOBALS['cache']['maint'][$entity_type][$entity[$entity_id_field]])) { 162 $alert_suppressed = TRUE; 163 $suppressed[] = "MNT_ENT"; 164 } 165 166 if (isset($GLOBALS['cache']['maint']['alert_checker'][$alert_test_id])) { 167 $alert_suppressed = TRUE; 168 $suppressed[] = "MNT_CHK"; 169 } 170 171 if (isset($GLOBALS['cache']['maint']['device'][$device['device_id']])) { 172 $alert_suppressed = TRUE; 173 $suppressed[] = "MNT_DEV"; 174 } 175 176 // Have all alerts on the entity been suppressed? 177 if ($entity[$entity_ignore_field]) { 178 $alert_suppressed = TRUE; 179 $suppressed[] = "ENT"; 180 } 181 if (is_numeric($entity['ignore_until']) && $entity['ignore_until'] > time()) { 182 $alert_suppressed = TRUE; 183 $suppressed[] = "ENT_U"; 184 } 185 186 // Have alerts from this alerter been suppressed? 187 if ($alert_rules[$alert_test_id]['ignore']) { 188 $alert_suppressed = TRUE; 189 $suppressed[] = "CHECK"; 190 } 191 if ($alert_rules[$alert_test_id]['ignore_until']) { 192 $alert_rules[$alert_test_id]['ignore_until_time'] = strtotime($alert_rules[$alert_test_id]['ignore_until']); 193 if ($alert_rules[$alert_test_id]['ignore_until_time'] > time()) { 194 $alert_suppressed = TRUE; 195 $suppressed[] = "CHECK_UNTIL"; 196 } 197 } 198 199 // Has this specific alert been suppressed? 200 if ($alert_args['ignore']) { 201 $alert_suppressed = TRUE; 202 $suppressed[] = "ENTRY"; 203 } 204 if ($alert_args['ignore_until']) { 205 $alert_args['ignore_until_time'] = strtotime($alert_args['ignore_until']); 206 if ($alert_args['ignore_until_time'] > time()) { 207 $alert_suppressed = TRUE; 208 $suppressed[] = "ENTRY_UNTIL"; 209 } 210 } 211 212 if (is_numeric($alert_args['ignore_until_ok']) && $alert_args['ignore_until_ok'] == '1') { 213 $alert_suppressed = TRUE; 214 $suppressed[] = "ENTRY_UNTIL_OK"; 215 } 216 217 $update_array['count'] = $alert_args['count'] + 1; 218 219 // Check against the alert test's delay 220 if ($alert_args['count'] >= $alert_rules[$alert_test_id]['delay'] && $alert_suppressed) { 221 // This alert is valid, but has been suppressed. 222 //echo(" Checks failed. Alert suppressed (".implode(', ', $suppressed).").\n"); 223 $alert_output .= "%PFS%N"; 224 $update_array['alert_status'] = '3'; 225 $update_array['last_message'] = 'Checks failed (Suppressed: ' . implode(', ', $suppressed) . ')'; 226 $update_array['last_checked'] = time(); 227 if ($alert_args['alert_status'] != '3' || $alert_args['last_changed'] == '0') { 228 $update_array['last_changed'] = time(); 229 $log_id = log_alert('Checks failed but alert suppressed by [' . implode($suppressed, ',') . ']', $device, $alert_info, 'FAIL_SUPPRESSED'); 230 } 231 $update_array['last_failed'] = time(); 232 } elseif ($alert_args['count'] >= $alert_rules[$alert_test_id]['delay']) { 233 // This is a real alert. 234 //echo(" Checks failed. Generate alert.\n"); 235 $alert_output .= "%PF!%N"; 236 $update_array['alert_status'] = '0'; 237 $update_array['last_message'] = 'Checks failed'; 238 $update_array['last_checked'] = time(); 239 if ($alert_args['alert_status'] != '0' || $alert_args['last_changed'] == '0') { 240 $update_array['last_changed'] = time(); 241 $update_array['last_alerted'] = '0'; 242 $log_id = log_alert('Checks failed', $device, $alert_info, 'FAIL'); 243 } 244 $update_array['last_failed'] = time(); 245 } else { 246 // This is alert needs to exist for longer. 247 //echo(" Checks failed. Delaying alert.\n"); 248 $alert_output .= "%OFD%N"; 249 $update_array['alert_status'] = '2'; 250 $update_array['last_message'] = 'Checks failed (delayed)'; 251 $update_array['last_checked'] = time(); 252 if ($alert_args['alert_status'] != '2' || $alert_args['last_changed'] == '0') { 253 $update_array['last_changed'] = time(); 254 $log_id = log_alert('Checks failed but alert delayed', $device, $alert_info, 'FAIL_DELAYED'); 255 } 256 $update_array['last_failed'] = time(); 257 } 258 } else { 259 $update_array['count'] = 0; 260 // Alert conditions passed. Record that we tested it and update status and other data. 261 $alert_output .= "%gOK%N"; 262 $update_array['alert_status'] = '1'; 263 $update_array['last_message'] = 'Checks OK'; 264 $update_array['last_checked'] = time(); 265 #$update_array['count'] = 0; 266 if ($alert_args['alert_status'] != '1' || $alert_args['last_changed'] == '0') { 267 $update_array['last_changed'] = time(); 268 $log_id = log_alert('Checks succeeded', $device, $alert_info, 'OK'); 269 } 270 $update_array['last_ok'] = time(); 271 272 // Status is OK, so disable ignore_until_ok if it has been enabled 273 if ($alert_args['ignore_until_ok'] != '0') { 274 $update_entry_array['ignore_until_ok'] = '0'; 275 } 276 } 277 278 unset($suppressed); 279 unset($alert_suppressed); 280 281 // json_encode the state array before we put it into MySQL. 282 $update_array['state'] = json_encode($update_array['state']); 283 #$update_array['alert_table_id'] = $alert_args['alert_table_id']; 284 285 /// Perhaps this is better done with SQL replace? 286 #print_vars($alert_args); 287 //if (!$alert_args['state_entry']) 288 //{ 289 // State entry seems to be missing. Insert it before we update it. 290 //dbInsert(array('alert_table_id' => $alert_args['alert_table_id']), 'alert_table-state'); 291 // echo("I+"); 292 //} 293 dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($alert_args['alert_table_id'])); 294 if (is_array($update_entry_array)) { 295 dbUpdate($update_entry_array, 'alert_table', '`alert_table_id` = ?', array($alert_args['alert_table_id'])); 296 } 297 298 if (TRUE) { 299 // Write RRD data 300 if ($update_array['alert_status'] == "1") { 301 // Status is up 302 rrdtool_update_ng($device, 'alert', array('status' => 1, 'code' => $update_array['alert_status']), $alert_args['alert_table_id']); 303 } else { 304 rrdtool_update_ng($device, 'alert', array('status' => 0, 'code' => $update_array['alert_status']), $alert_args['alert_table_id']); 305 } 306 } 307 } else { 308 $alert_output .= "%RAlert missing!%N"; 309 } 310 311 $alert_output .= ("] "); 312 } 313 314 $alert_output .= "%n"; 315 316 if ($entity_type == "device") { 317 $cli_level = 1; 318 } else { 319 $cli_level = 3; 320 } 321 322 //print_cli_data("Checked Alerts", $alert_output, $cli_level); 323} 324 325/** 326 * Build an array of conditions that apply to a supplied device 327 * 328 * This takes the array of global conditions and removes associations that don't match the supplied device array 329 * 330 * @param array device 331 * @return array 332 */ 333// TESTME needs unit testing 334function cache_device_conditions($device) 335{ 336 // Return no conditions if the device is ignored or disabled. 337 338 if ($device['ignore'] == 1 || $device['disabled'] == 1) { 339 return array(); 340 } 341 342 $conditions = cache_conditions(); 343 344 foreach ($conditions['assoc'] as $assoc_key => $assoc) { 345 if (match_device($device, $assoc['device_attribs'])) { 346 $assoc['alert_test_id']; 347 $conditions['cond'][$assoc['alert_test_id']]['assoc'][$assoc_key] = $conditions['assoc'][$assoc_key]; 348 $cond_new['cond'][$assoc['alert_test_id']] = $conditions['cond'][$assoc['alert_test_id']]; 349 } else { 350 unset($conditions['assoc'][$assoc_key]); 351 } 352 } 353 354 return $cond_new; 355} 356 357/** 358 * Fetch array of alerts to a supplied device from `alert_table` 359 * 360 * This takes device_id as argument and returns an array. 361 * 362 * @param device_id 363 * @return array 364 */ 365// TESTME needs unit testing 366function cache_device_alert_table($device_id) 367{ 368 $alert_table = array(); 369 370 $sql = "SELECT * FROM `alert_table`"; 371 //$sql .= " LEFT JOIN `alert_table-state` USING(`alert_table_id`)"; 372 $sql .= " WHERE `device_id` = ?"; 373 374 foreach (dbFetchRows($sql, array($device_id)) as $entry) { 375 $alert_table[$entry['entity_type']][$entry['entity_id']][$entry['alert_test_id']] = $entry; 376 } 377 378 return $alert_table; 379} 380 381// Wrapper function to loop all groups and trigger rebuild for each. 382 383function update_alert_tables($silent = TRUE) 384{ 385 386 $alerts = cache_alert_rules(); 387 $assocs = cache_alert_assoc(); 388 389 // Populate associations table into alerts array for legacy association styles 390 foreach($assocs as $assoc) 391 { 392 $alerts[$assoc['alert_test_id']]['assocs'][] = $assoc; 393 } 394 395 foreach($alerts AS $alert) 396 { 397 update_alert_table($alert, $silent); 398 } 399 400} 401 402// Regenerate Alert Table 403function update_alert_table($alert, $silent = TRUE) 404{ 405 406 if(is_numeric($alert)) // We got an alert_test_id, fetch the array. 407 { 408 $alert = dbFetchRow("SELECT * FROM `alert_tests` WHERE `alert_test_id` = ?", array($alert)); 409 } 410 411 if(strlen($alert['alert_assoc'])) 412 { 413 414 $query = parse_qb_ruleset($alert['entity_type'], json_decode($alert['alert_assoc'], TRUE)); 415 $data = dbFetchRows($query); 416 $error = dbError(); 417 $entities = array(); 418 419 $field = $GLOBALS['config']['entities'][$alert['entity_type']]['table_fields']['id']; 420 421 foreach($data as $datum) 422 { 423 $entities[$datum[$field]] = array('entity_id' => $datum[$field], 'device_id' => $datum['device_id']); 424 } 425 426 } else { 427 $entities = get_alert_entities_from_assocs($alert); 428 } 429 430 $field = $config['entities'][$alert['entity_type']]['table_fields']['id']; 431 432 $existing_entities = get_alert_entities($alert['alert_test_id']); 433 434 //r($existing_entities); 435 //r($entities); 436 437 438 $add = array_diff_key($entities, $existing_entities); 439 $remove = array_diff_key($existing_entities, $entities); 440 441 if(!silent) 442 { 443 echo count($existing_entities) . " existing entries.<br />"; 444 echo count($entities) . " new entries.<br />"; 445 echo "(+" . count($add) . "/-" . count($del) . ")<br />"; 446 } 447 448 foreach($add as $entity_id => $entity) 449 { 450 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'); 451 } 452 453 foreach($remove as $entity_id => $entity) 454 { 455 dbDelete('alert_table', 'alert_test_id = ? AND entity_id = ?', array($alert['alert_test_id'], $entity_id)); 456 } 457 458 //print_vars($add); 459 //print_vars($del); 460 461 if(!is_cli() && !$silent) 462 { 463 print_message("Alert Checker " . $alert['alert_name'] . " regenerated", 'information'); 464 } 465 466} 467 468 469/** 470 * Build an array of all alert rules 471 * 472 * @return array 473 */ 474// TESTME needs unit testing 475function cache_alert_rules($vars = array()) 476{ 477 $alert_rules = array(); 478 $rules_count = 0; 479 $where = 'WHERE 1'; 480 $args = array(); 481 482 if (isset($vars['entity_type']) && $vars['entity_type'] !== "all") { 483 $where .= ' AND `entity_type` = ?'; 484 $args[] = $vars['entity_type']; 485 } 486 487 foreach (dbFetchRows("SELECT * FROM `alert_tests` " . $where, $args) as $entry) { 488 if ($entry['alerter'] == '') { 489 $entry['alerter'] = "default"; 490 } 491 $alert_rules[$entry['alert_test_id']] = $entry; 492 $alert_rules[$entry['alert_test_id']]['conditions'] = json_decode($entry['conditions'], TRUE); 493 $rules_count++; 494 } 495 496 print_debug("Cached $rules_count alert rules."); 497 498 return $alert_rules; 499 500} 501 502// FIXME. Never used, deprecated? 503// DOCME needs phpdoc block 504// TESTME needs unit testing 505function generate_alerter_info($alerter) 506{ 507 global $config; 508 509 if (is_array($config['alerts']['alerter'][$alerter])) { 510 $a = $config['alerts']['alerter'][$alerter]; 511 $output = "<strong>" . $a['descr'] . "</strong><hr />"; 512 $output .= $a['type'] . ": " . $a['contact'] . "<br />"; 513 if ($a['enable']) { 514 $output .= "Enabled"; 515 } else { 516 $output .= "Disabled"; 517 } 518 return $output; 519 } else { 520 return "Unknown alerter."; 521 } 522} 523 524// DOCME needs phpdoc block 525// TESTME needs unit testing 526function cache_alert_assoc() 527{ 528 $alert_assoc = array(); 529 530 foreach (dbFetchRows("SELECT * FROM `alert_assoc`") as $entry) { 531 $entity_attribs = json_decode($entry['entity_attribs'], TRUE); 532 $device_attribs = json_decode($entry['device_attribs'], TRUE); 533 $alert_assoc[$entry['alert_assoc_id']]['entity_type'] = $entry['entity_type']; 534 $alert_assoc[$entry['alert_assoc_id']]['entity_attribs'] = $entity_attribs; 535 $alert_assoc[$entry['alert_assoc_id']]['device_attribs'] = $device_attribs; 536 $alert_assoc[$entry['alert_assoc_id']]['alert_test_id'] = $entry['alert_test_id']; 537 } 538 539 return $alert_assoc; 540} 541 542/** 543 * Build an array of scheduled maintenances 544 * 545 * @return array 546 * 547 */ 548// TESTME needs unit testing 549function cache_alert_maintenance() 550{ 551 552 $return = array(); 553 $now = time(); 554 555 $maints = dbFetchRows("SELECT * FROM `alerts_maint` WHERE `maint_start` < ? AND `maint_end` > ?", array($now, $now)); 556 557 if (is_array($maints) && count($maints)) { 558 559 $return['count'] = count($maints); 560 561 foreach ($maints as $maint) { 562 if ($maint['maint_global'] == 1) { 563 $return['global'][$maint['maint_id']] = $maint; 564 } else { 565 566 $assocs = dbFetchRows("SELECT * FROM `alerts_maint_assoc` WHERE `maint_id` = ?", array($maint['maint_id'])); 567 568 foreach ($assocs as $assoc) { 569 switch ($assoc['entity_type']) { 570 case "group": // this is a group, so expand it's members into an array 571 $group = get_group_by_id($assoc['entity_id']); 572 $entities = get_group_entities($assoc['entity_id']); 573 foreach ($entities as $entity) { 574 $return[$group['entity_type']][$entity] = TRUE; 575 } 576 break; 577 default: 578 $return[$assoc['entity_type']][$assoc['entity_id']] = TRUE; 579 break; 580 } 581 } 582 583 } 584 } 585 } 586 587 //print_r($return); 588 589 return $return; 590 591} 592 593function get_alert_entities($ids) 594{ 595 $array = array(); 596 if (!is_array($ids)) { $ids = array($ids); } 597 598 foreach ($ids as $alert_id) 599 { 600 foreach (dbFetchRows("SELECT * FROM `alert_table` WHERE `alert_test_id` = ?", array($alert_id)) as $entry) 601 { 602 $array[$entry['entity_id']] = array('entity_id' => $entry['entity_id'], 'device_id' => $entry['device_id']); 603 } 604 } 605 606 return $array; 607} 608 609 610function get_maintenance_associations($maint_id = NULL) 611{ 612 $return = array(); 613 614# if ($maint_id) 615# { 616 $assocs = dbFetchRows("SELECT * FROM `alerts_maint_assoc` WHERE `maint_id` = ?", array($maint_id)); 617# } else { 618# $assocs = dbFetchRows("SELECT * FROM `alerts_maint_assoc`"); 619# } 620 621 foreach ($assocs as $assoc) { 622 $return[$assoc['entity_type']][$assoc['entity_id']] = TRUE; 623 } 624 625 return $return; 626 627} 628 629/** 630 * Build an array of all conditions 631 * 632 * @return array 633 */ 634// TESTME needs unit testing 635function cache_conditions() 636{ 637 $cache = array(); 638 639 foreach (dbFetchRows("SELECT * FROM `alert_tests`") as $entry) { 640 $cache['cond'][$entry['alert_test_id']] = $entry; 641 $conditions = json_decode($entry['conditions'], TRUE); 642 $cache['cond'][$entry['alert_test_id']]['entity_type'] = $entry['entity_type']; 643 $cache['cond'][$entry['alert_test_id']]['conditions'] = $conditions; 644 } 645 646 foreach (dbFetchRows("SELECT * FROM `alert_assoc`") as $entry) { 647 $entity_attribs = json_decode($entry['entity_attribs'], TRUE); 648 $device_attribs = json_decode($entry['device_attribs'], TRUE); 649 $cache['assoc'][$entry['alert_assoc_id']] = $entry; 650 $cache['assoc'][$entry['alert_assoc_id']]['entity_attribs'] = $entity_attribs; 651 $cache['assoc'][$entry['alert_assoc_id']]['device_attribs'] = $device_attribs; 652 } 653 654 return $cache; 655} 656 657/** 658 * Compare two values 659 * 660 * @param string $value_a 661 * @param string $condition 662 * @param string $value_b 663 * @return boolean 664 */ 665// TESTME needs unit testing 666function test_condition($value_a, $condition, $value_b) 667{ 668 $value_a = trim($value_a); 669 if (!is_array($value_b)) { 670 $value_b = trim($value_b); 671 } 672 $condition = strtolower($condition); 673 $delimiters = array('/', '!', '@'); 674 675 switch ($condition) { 676 case 'ge': 677 case '>=': 678 if ($value_a >= unit_string_to_numeric($value_b)) { 679 $alert = TRUE; 680 } else { 681 $alert = FALSE; 682 } 683 break; 684 case 'le': 685 case '<=': 686 if ($value_a <= unit_string_to_numeric($value_b)) { 687 $alert = TRUE; 688 } else { 689 $alert = FALSE; 690 } 691 break; 692 case 'gt': 693 case 'greater': 694 case '>': 695 if ($value_a > unit_string_to_numeric($value_b)) { 696 $alert = TRUE; 697 } else { 698 $alert = FALSE; 699 } 700 break; 701 case 'lt': 702 case 'less': 703 case '<': 704 if ($value_a < unit_string_to_numeric($value_b)) { 705 $alert = TRUE; 706 } else { 707 $alert = FALSE; 708 } 709 break; 710 case 'notequals': 711 case 'isnot': 712 case 'ne': 713 case '!=': 714 if ($value_a != unit_string_to_numeric($value_b)) { 715 $alert = TRUE; 716 } else { 717 $alert = FALSE; 718 } 719 break; 720 case 'equals': 721 case 'eq': 722 case 'is': 723 case '==': 724 case '=': 725 if ($value_a == unit_string_to_numeric($value_b)) { 726 $alert = TRUE; 727 } else { 728 $alert = FALSE; 729 } 730 break; 731 case 'match': 732 case 'matches': 733 $value_b = str_replace('*', '.*', $value_b); 734 $value_b = str_replace('?', '.', $value_b); 735 736 foreach ($delimiters as $delimiter) { 737 if (!str_contains($value_b, $delimiter)) { 738 break; 739 } 740 } 741 if (preg_match($delimiter . '^' . $value_b . '$' . $delimiter, $value_a)) { 742 $alert = TRUE; 743 } else { 744 $alert = FALSE; 745 } 746 break; 747 case 'notmatches': 748 case 'notmatch': 749 case '!match': 750 $value_b = str_replace('*', '.*', $value_b); 751 $value_b = str_replace('?', '.', $value_b); 752 753 foreach ($delimiters as $delimiter) { 754 if (!str_contains($value_b, $delimiter)) { 755 break; 756 } 757 } 758 if (preg_match($delimiter . '^' . $value_b . '$' . $delimiter, $value_a)) { 759 $alert = FALSE; 760 } else { 761 $alert = TRUE; 762 } 763 break; 764 case 'regexp': 765 case 'regex': 766 foreach ($delimiters as $delimiter) { 767 if (!str_contains($value_b, $delimiter)) { 768 break; 769 } 770 } 771 if (preg_match($delimiter . $value_b . $delimiter, $value_a)) { 772 $alert = TRUE; 773 } else { 774 $alert = FALSE; 775 } 776 break; 777 case 'notregexp': 778 case 'notregex': 779 case '!regexp': 780 case '!regex': 781 foreach ($delimiters as $delimiter) { 782 if (!str_contains($value_b, $delimiter)) { 783 break; 784 } 785 } 786 if (preg_match($delimiter . $value_b . $delimiter, $value_a)) { 787 $alert = FALSE; 788 } else { 789 $alert = TRUE; 790 } 791 break; 792 case 'in': 793 case 'list': 794 if (!is_array($value_b)) { 795 $value_b = explode(',', $value_b); 796 } 797 $alert = in_array($value_a, $value_b); 798 break; 799 case '!in': 800 case '!list': 801 case 'notin': 802 case 'notlist': 803 if (!is_array($value_b)) { 804 $value_b = explode(',', $value_b); 805 } 806 $alert = !in_array($value_a, $value_b); 807 break; 808 default: 809 $alert = FALSE; 810 break; 811 } 812 813 return $alert; 814} 815 816/** 817 * Test if a device matches a set of attributes 818 * Matches using the database entry for the supplied device_id 819 * 820 * @param array device 821 * @param array attributes 822 * @return boolean 823 */ 824// TESTME needs unit testing 825function match_device($device, $attributes, $ignore = TRUE) 826{ 827 // Short circuit this check if the device is either disabled or ignored. 828 if ($ignore && ($device['disable'] == 1 || $device['ignore'] == 1)) { 829 return FALSE; 830 } 831 832 $query = "SELECT COUNT(*) FROM `devices` AS d"; 833 $join = ""; 834 $where = " WHERE d.`device_id` = ?"; 835 $params = array($device['device_id']); 836 837 foreach ($attributes as $attrib) { 838 switch ($attrib['condition']) { 839 case 'ge': 840 case '>=': 841 $where .= ' AND d.`' . $attrib['attrib'] . '` >= ?'; 842 $params[] = $attrib['value']; 843 break; 844 case 'le': 845 case '<=': 846 $where .= ' AND d.`' . $attrib['attrib'] . '` <= ?'; 847 $params[] = $attrib['value']; 848 break; 849 case 'gt': 850 case 'greater': 851 case '>': 852 $where .= ' AND d.`' . $attrib['attrib'] . '` > ?'; 853 $params[] = $attrib['value']; 854 break; 855 case 'lt': 856 case 'less': 857 case '<': 858 $where .= ' AND d.`' . $attrib['attrib'] . '` < ?'; 859 $params[] = $attrib['value']; 860 break; 861 case 'notequals': 862 case 'isnot': 863 case 'ne': 864 case '!=': 865 $where .= ' AND d.`' . $attrib['attrib'] . '` != ?'; 866 $params[] = $attrib['value']; 867 break; 868 case 'equals': 869 case 'eq': 870 case 'is': 871 case '==': 872 case '=': 873 $where .= ' AND d.`' . $attrib['attrib'] . '` = ?'; 874 $params[] = $attrib['value']; 875 break; 876 case 'match': 877 case 'matches': 878 $attrib['value'] = str_replace('*', '%', $attrib['value']); 879 $attrib['value'] = str_replace('?', '_', $attrib['value']); 880 $where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") LIKE ?'; 881 $params[] = $attrib['value']; 882 break; 883 case 'notmatches': 884 case 'notmatch': 885 case '!match': 886 $attrib['value'] = str_replace('*', '%', $attrib['value']); 887 $attrib['value'] = str_replace('?', '_', $attrib['value']); 888 $where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") NOT LIKE ?'; 889 $params[] = $attrib['value']; 890 break; 891 case 'regexp': 892 case 'regex': 893 $where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") REGEXP ?'; 894 $params[] = $attrib['value']; 895 break; 896 case 'notregexp': 897 case 'notregex': 898 case '!regexp': 899 case '!regex': 900 $where .= ' AND IFNULL(d.`' . $attrib['attrib'] . '`, "") NOT REGEXP ?'; 901 $params[] = $attrib['value']; 902 break; 903 case 'in': 904 case 'list': 905 $where .= generate_query_values(explode(',', $attrib['value']), 'd.' . $attrib['attrib']); 906 break; 907 case '!in': 908 case '!list': 909 case 'notin': 910 case 'notlist': 911 $where .= generate_query_values(explode(',', $attrib['value']), 'd.' . $attrib['attrib'], '!='); 912 break; 913 case 'include': 914 case 'includes': 915 switch ($attrib['attrib']) { 916 case 'group': 917 $join .= " INNER JOIN `group_table` USING(`device_id`)"; 918 $join .= " INNER JOIN `groups` USING(`group_id`)"; 919 $where .= " AND `group_name` = ?"; 920 $params[] = $attrib['value']; 921 break; 922 case 'group_id': 923 $join .= " INNER JOIN `group_table` USING(`device_id`)"; 924 $where .= " AND `group_id` = ?"; 925 $params[] = $attrib['value']; 926 break; 927 928 } 929 break; 930 } 931 } 932 933 $query .= $join . $where; 934 $device_count = dbFetchCell($query, $params); 935 936 if ($device_count == 0) { 937 return FALSE; 938 } else { 939 return TRUE; 940 } 941} 942 943/** 944 * Return an array of entities of a certain type which match device_id and entity attribute rules. 945 * 946 * @param integer device_id 947 * @param array attributes 948 * @param string entity_type 949 * @return array 950 */ 951// TESTME needs unit testing 952function match_device_entities($device_id, $entity_attribs, $entity_type) 953{ 954 // FIXME - this is going to be horribly slow. 955 956 $e_type = $entity_type; 957 $entity_type = entity_type_translate_array($entity_type); 958 959 if (!is_array($entity_type)) { 960 return NULL; 961 } // Do nothing if entity type unknown 962 963 $param = array(); 964 $sql = "SELECT * FROM `" . dbEscape($entity_type['table']) . "`"; // FIXME. Not sure why these required escape table name 965 966 if(isset($entity_type['parent_table']) && isset($entity_type['parent_id_field'])) 967 { 968 $sql .= ' LEFT JOIN `'.$entity_type['parent_table'].'` USING (`'.$entity_type['parent_id_field'].'`)'; 969 } 970 971 $sql .= " WHERE `" . dbEscape($entity_type['table']) . "`.device_id = ?"; 972 973 if (isset($entity_type['where'])) { 974 $sql .= ' AND ' . $entity_type['where']; 975 } 976 977 $param[] = $device_id; 978 979 if (isset($entity_type['deleted_field'])) { 980 $sql .= " AND `" . $entity_type['deleted_field'] . "` != ?"; 981 $param[] = '1'; 982 } 983 984 foreach ($entity_attribs as $attrib) { 985 switch ($attrib['condition']) { 986 case 'ge': 987 case '>=': 988 $sql .= ' AND `' . $attrib['attrib'] . '` >= ?'; 989 $param[] = $attrib['value']; 990 break; 991 case 'le': 992 case '<=': 993 $sql .= ' AND `' . $attrib['attrib'] . '` <= ?'; 994 $param[] = $attrib['value']; 995 break; 996 case 'gt': 997 case 'greater': 998 case '>': 999 $sql .= ' AND `' . $attrib['attrib'] . '` > ?'; 1000 $param[] = $attrib['value']; 1001 break; 1002 case 'lt': 1003 case 'less': 1004 case '<': 1005 $sql .= ' AND `' . $attrib['attrib'] . '` < ?'; 1006 $param[] = $attrib['value']; 1007 break; 1008 case 'notequals': 1009 case 'isnot': 1010 case 'ne': 1011 case '!=': 1012 $sql .= ' AND `' . $attrib['attrib'] . '` != ?'; 1013 $param[] = $attrib['value']; 1014 break; 1015 case 'equals': 1016 case 'eq': 1017 case 'is': 1018 case '==': 1019 case '=': 1020 $sql .= ' AND `' . $attrib['attrib'] . '` = ?'; 1021 $param[] = $attrib['value']; 1022 break; 1023 case 'match': 1024 case 'matches': 1025 $attrib['value'] = str_replace('*', '%', $attrib['value']); 1026 $attrib['value'] = str_replace('?', '_', $attrib['value']); 1027 $sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") LIKE ?'; 1028 $param[] = $attrib['value']; 1029 break; 1030 case 'notmatches': 1031 case 'notmatch': 1032 case '!match': 1033 $attrib['value'] = str_replace('*', '%', $attrib['value']); 1034 $attrib['value'] = str_replace('?', '_', $attrib['value']); 1035 $sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT LIKE ?'; 1036 $param[] = $attrib['value']; 1037 break; 1038 case 'regexp': 1039 case 'regex': 1040 $sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") REGEXP ?'; 1041 $param[] = $attrib['value']; 1042 break; 1043 case 'notregexp': 1044 case 'notregex': 1045 case '!regexp': 1046 case '!regex': 1047 $sql .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT REGEXP ?'; 1048 $param[] = $attrib['value']; 1049 break; 1050 case 'in': 1051 case 'list': 1052 $sql .= generate_query_values(explode(',', $attrib['value']), $attrib['attrib']); 1053 break; 1054 case '!in': 1055 case '!list': 1056 case 'notin': 1057 case 'notlist': 1058 $sql .= generate_query_values(explode(',', $attrib['value']), $attrib['attrib'], '!='); 1059 break; 1060 case 'include': 1061 case 'includes': 1062 switch ($attrib['attrib']) { 1063 case 'group': 1064 $group = get_group_by_name($attrib['value']); 1065 if ($group['entity_type'] == $e_type) { 1066 $attrib['value'] = $group['group_id']; 1067 } 1068 case 'group_id': 1069 $values = get_group_entities($attrib['value']); 1070 $sql .= generate_query_values($values, $entity_type['table_fields']['id']); 1071 break; 1072 } 1073 } 1074 } 1075 1076 // print_vars(array($sql, $param)); 1077 1078 $entities = dbFetchRows($sql, $param); 1079 1080 return $entities; 1081} 1082 1083/** 1084 * Test if an entity matches a set of attributes 1085 * Uses a supplied device array for matching. 1086 * 1087 * @param array entity 1088 * @param array attributes 1089 * @return boolean 1090 */ 1091// TESTME needs unit testing 1092function match_entity($entity, $entity_attribs) 1093{ 1094 // FIXME. Never used, deprecated? 1095 #print_vars($entity); 1096 #print_vars($entity_attribs); 1097 1098 $failed = 0; 1099 $success = 0; 1100 $delimiters = array('/', '!', '@'); 1101 1102 foreach ($entity_attribs as $attrib) { 1103 switch ($attrib['condition']) { 1104 case 'equals': 1105 if (mb_strtolower($entity[$attrib['attrib']]) == mb_strtolower($attrib['value'])) { 1106 $success++; 1107 } else { 1108 $failed++; 1109 } 1110 break; 1111 case 'match': 1112 $attrib['value'] = str_replace('*', '.*', $attrib['value']); 1113 $attrib['value'] = str_replace('?', '.', $attrib['value']); 1114 1115 foreach ($delimiters as $delimiter) { 1116 if (!str_contains($attrib['value'], $delimiter)) { 1117 break; 1118 } 1119 } 1120 if (preg_match($delimiter . '^' . $attrib['value'] . '$' . $delimiter . 'i', $entity[$attrib['attrib']])) { 1121 $success++; 1122 } else { 1123 $failed++; 1124 } 1125 break; 1126 } 1127 } 1128 1129 if ($failed) { 1130 return FALSE; 1131 } else { 1132 return TRUE; 1133 } 1134} 1135 1136// DOCME needs phpdoc block 1137// TESTME needs unit testing 1138function update_device_alert_table($device) 1139{ 1140 $dbc = array(); 1141 $alert_table = array(); 1142 1143 $msg = "<h4>Building alerts for device " . $device['hostname'] . ':</h4>'; 1144 $msg_class = ''; 1145 $msg_enable = FALSE; 1146 $conditions = cache_device_conditions($device); 1147 1148 //foreach ($conditions['cond'] as $test_id => $test) 1149 //{ 1150 // #print_vars($test); 1151 // #echo('<span class="label label-info">Matched '.$test['alert_name'].'</span> '); 1152 //} 1153 1154 $db_cs = dbFetchRows("SELECT * FROM `alert_table` WHERE `device_id` = ?", array($device['device_id'])); 1155 foreach ($db_cs as $db_c) { 1156 $dbc[$db_c['entity_type']][$db_c['entity_id']][$db_c['alert_test_id']] = $db_c; 1157 } 1158 1159 $msg .= PHP_EOL; 1160 $msg .= ' <h5>Checkers matching this device:</h5> '; 1161 1162 foreach ($conditions['cond'] as $alert_test_id => $alert_test) { 1163 $msg .= '<span class="label label-info">' . $alert_test['alert_name'] . '</span> '; 1164 $msg_enable = TRUE; 1165 foreach ($alert_test['assoc'] as $assoc_id => $assoc) { 1166 // Check that the entity_type matches the one we're interested in. 1167 // echo("Matching $assoc_id (".$assoc['entity_type'].")"); 1168 1169 list($entity_table, $entity_id_field, $entity_name_field) = entity_type_translate($assoc['entity_type']); 1170 $alert = $conditions['cond'][$assoc['alert_test_id']]; 1171 $entities = match_device_entities($device['device_id'], $assoc['entity_attribs'], $assoc['entity_type']); 1172 1173 foreach ($entities AS $id => $entity) { 1174 $alert_table[$assoc['entity_type']][$entity[$entity_id_field]][$assoc['alert_test_id']][] = $assoc_id; 1175 } 1176 1177 // echo(count($entities)." matched".PHP_EOL); 1178 } 1179 } 1180 1181 $msg .= PHP_EOL; 1182 $msg .= ' <h5>Matching entities:</h5> '; 1183 1184 foreach ($alert_table as $entity_type => $entities) { 1185 foreach ($entities as $entity_id => $entity) { 1186 $entity_name = entity_name($entity_type, $entity_id); 1187 $msg .= '<span class="label label-ok">' . htmlentities($entity_name) . '</span> '; 1188 $msg_enable = TRUE; 1189 1190 foreach ($entity as $alert_test_id => $b) { 1191# echo(str_pad($entity_type, "20").str_pad($entity_id, "20").str_pad($alert_test_id, "20")); 1192# echo(str_pad(implode($b,","), "20")); 1193 $msg .= '<span class="label label-info">' . $conditions['cond'][$alert_test_id]['alert_name'] . '</span><br >'; 1194 $msg_class = 'success'; 1195 if (isset($dbc[$entity_type][$entity_id][$alert_test_id])) { 1196 if ($dbc[$entity_type][$entity_id][$alert_test_id]['alert_assocs'] != implode($b, ",")) { 1197 $update_array = array('alert_assocs' => implode($b, ",")); 1198 } 1199 #echo("[".$dbc[$entity_type][$entity_id][$alert_test_id]['alert_assocs']."][".implode($b,",")."]"); 1200 if (is_array($update_array)) { 1201 dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($dbc[$entity_type][$entity_id][$alert_test_id]['alert_table_id'])); 1202 unset($update_array); 1203 } 1204 unset($dbc[$entity_type][$entity_id][$alert_test_id]); 1205 } else { 1206 $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'); 1207 //dbInsert(array('alert_table_id' => $alert_table_id), 'alert_table-state'); 1208 } 1209 } 1210 } 1211 } 1212 1213 $msg .= PHP_EOL; 1214 $msg .= " <h5>Checking for stale entries:</h5> "; 1215 1216 foreach ($dbc as $type => $entity) { 1217 foreach ($entity as $entity_id => $alert) { 1218 foreach ($alert as $alert_test_id => $data) { 1219 dbDelete('alert_table', "`alert_table_id` = ?", array($data['alert_table_id'])); 1220 //dbDelete('alert_table-state', "`alert_table_id` = ?", array($data['alert_table_id'])); 1221 $msg .= "-"; 1222 $msg_enable = TRUE; 1223 } 1224 } 1225 } 1226 1227 if ($msg_enable) { 1228 return (array('message' => $msg, 'class' => $msg_class)); 1229 } 1230} 1231 1232/** 1233 * Check all alerts for a device to see if they should be notified or not 1234 * 1235 * @param array device 1236 * @return NULL 1237 */ 1238// TESTME needs unit testing 1239function process_alerts($device) 1240{ 1241 global $config, $alert_rules, $alert_assoc; 1242 1243 $pid_info = check_process_run($device); // This just clear stalled DB entries 1244 add_process_info($device); // Store process info 1245 1246 print_cli_heading($device['hostname'] . " [" . $device['device_id'] . "]", 1); 1247 1248 $alert_table = cache_device_alert_table($device['device_id']); 1249 1250 $sql = "SELECT * FROM `alert_table`"; 1251 //$sql .= " LEFT JOIN `alert_table-state` ON `alert_table`.`alert_table_id` = `alert_table-state`.`alert_table_id`"; 1252 $sql .= " WHERE `device_id` = ? AND `alert_status` IS NOT NULL;"; 1253 1254 foreach (dbFetchRows($sql, array($device['device_id'])) as $entry) { 1255 print_cli_data_field('Alert: ' . $entry['alert_table_id']); 1256 print_cli('Status: [' . $entry['alert_status'] . '] ', 'color'); 1257 1258 // If the alerter is now OK and has previously alerted, send an recovery notice. 1259 if ($entry['alert_status'] == '1' && $entry['has_alerted'] == '1') { 1260 $alert = $alert_rules[$entry['alert_test_id']]; 1261 1262 if (!$alert['suppress_recovery']) { 1263 $log_id = log_alert('Recovery notification sent', $device, $entry, 'RECOVER_NOTIFY'); 1264 alert_notifier($entry, "recovery", $log_id); 1265 } else { 1266 echo('Recovery suppressed.'); 1267 $log_id = log_alert('Recovery notification suppressed', $device, $entry, 'RECOVER_SUPPRESSED'); 1268 } 1269 1270 $update_array['last_recovered'] = time(); 1271 $update_array['has_alerted'] = 0; 1272 dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($entry['alert_table_id'])); 1273 } 1274 1275 if ($entry['alert_status'] == '0') { 1276 echo('Alert tripped. '); 1277 1278 // Has this been alerted more frequently than the alert interval in the config? 1279 /// FIXME -- this should be configurable per-entity or per-checker 1280 if ((time() - $entry['last_alerted']) < $config['alerts']['interval'] && !isset($GLOBALS['spam'])) { 1281 $entry['suppress_alert'] = TRUE; 1282 } 1283 1284 // Don't re-alert if interval set to 0 1285 if ($config['alerts']['interval'] == 0 && $entry['last_alerted'] != 0) { 1286 $entry['suppress_alert'] = TRUE; 1287 } 1288 1289 // Check if alert has ignore_until set. 1290 if (is_numeric($entry['ignore_until']) && $entry['ignore_until'] > time()) { 1291 $entry['suppress_alert'] = TRUE; 1292 } 1293 // Check if alert has ignore_until_ok set. 1294 if (is_numeric($entry['ignore_until_ok']) && $entry['ignore_until_ok'] == '1') { 1295 $entry['suppress_alert'] = TRUE; 1296 } 1297 1298 if ($entry['suppress_alert'] != TRUE) { 1299 echo('Requires notification. '); 1300 1301 $log_id = log_alert('Alert notification sent', $device, $entry, 'ALERT_NOTIFY'); 1302 alert_notifier($entry, "alert", $log_id); 1303 1304 $update_array['last_alerted'] = time(); 1305 $update_array['has_alerted'] = 1; 1306 dbUpdate($update_array, 'alert_table', '`alert_table_id` = ?', array($entry['alert_table_id'])); 1307 1308 } else { 1309 echo("No notification required. " . (time() - $entry['last_alerted'])); 1310 } 1311 } else if ($entry['alert_status'] == '1') { 1312 echo("Status: OK. "); 1313 } else if ($entry['alert_status'] == '2') { 1314 echo("Status: Notification Delayed. "); 1315 } else if ($entry['alert_status'] == '3') { 1316 echo("Status: Notification Suppressed. "); 1317 } else { 1318 echo("Unknown status."); 1319 } 1320 echo(PHP_EOL); 1321 } 1322 1323 echo(PHP_EOL); 1324 print_cli_heading($device['hostname'] . " [" . $device['device_id'] . "] completed notifications at " . date("Y-m-d H:i:s"), 1); 1325 1326 // Clean 1327 del_process_info($device); // Remove process info 1328} 1329 1330/** 1331 * Generate notification queue entries for alert system 1332 * 1333 * @param array $entry Entry 1334 * @param string $type Alert type (alert (default) or syslog) 1335 * @param integer $log_id Alert log entry ID 1336 * @return array List of processed notification ids. 1337 */ 1338function alert_notifier($entry, $type = "alert", $log_id = NULL) 1339{ 1340 global $config, $alert_rules; 1341 1342 $alert_unixtime = time(); // Store time when alert processed 1343 1344 $device = device_by_id_cache($entry['device_id']); 1345 1346 if (empty($log_id) && is_numeric($entry['log_id'])) { 1347 // Log ID can passed as argument or inside entry array 1348 $log_id = $entry['log_id']; 1349 } 1350 1351 $alert = $alert_rules[$entry['alert_test_id']]; 1352 1353 $state = json_decode($entry['state'], TRUE); 1354 $conditions = json_decode($alert['conditions'], TRUE); 1355 1356 $entity = get_entity_by_id_cache($entry['entity_type'], $entry['entity_id']); 1357 1358 $condition_array = array(); 1359 foreach ($state['failed'] as $failed) { 1360 $condition_array[] = $failed['metric'] . " " . $failed['condition'] . " " . $failed['value'] . " (" . $state['metrics'][$failed['metric']] . ")"; 1361 } 1362 1363 $metric_array = array(); 1364 foreach ($state['metrics'] as $metric => $value) { 1365 $metric_array[] = $metric . ' = ' . $value; 1366 } 1367 1368 $graphs = array(); 1369 $graph_done = array(); 1370 foreach ($state['metrics'] as $metric => $value) { 1371 if ($config['email']['graphs'] !== FALSE 1372 && is_array($config['entities'][$entry['entity_type']]['metric_graphs'][$metric]) 1373 && !in_array($config['entities'][$entry['entity_type']]['metric_graphs'][$metric]['type'], $graph_done) 1374 ) { 1375 $graph_array = $config['entities'][$entry['entity_type']]['metric_graphs'][$metric]; 1376 foreach ($graph_array as $key => $val) { 1377 // Check to see if we need to do any substitution 1378 if (substr($val, 0, 1) == '@') { 1379 $nval = substr($val, 1); 1380 //echo(" replaced " . $val . " with " . $entity[$nval] . " from entity. " . PHP_EOL . "<br />"); 1381 $graph_array[$key] = $entity[$nval]; 1382 } 1383 } 1384 1385 $image_data_uri = generate_alert_graph($graph_array); 1386 $image_url = generate_graph_url($graph_array); 1387 1388 $graphs[] = array('label' => $graph_array['type'], 'type' => $graph_array['type'], 'url' => $image_url, 'data' => $image_data_uri); 1389 1390 $graph_done[] = $graph_array['type']; 1391 } 1392 1393 unset($graph_array); 1394 } 1395 1396 if ($config['email']['graphs'] !== FALSE && count($graph_done) == 0 && is_array($config['entities'][$entry['entity_type']]['graph'])) { 1397 // We can draw a graph for this type/metric pair! 1398 1399 $graph_array = $config['entities'][$entry['entity_type']]['graph']; 1400 foreach ($graph_array as $key => $val) { 1401 // Check to see if we need to do any substitution 1402 if (substr($val, 0, 1) == '@') { 1403 $nval = substr($val, 1); 1404 //echo(" replaced ".$val." with ". $entity[$nval] ." from entity. ".PHP_EOL."<br />"); 1405 $graph_array[$key] = $entity[$nval]; 1406 } 1407 } 1408 1409 //print_vars($graph_array); 1410 1411 $image_data_uri = generate_alert_graph($graph_array); 1412 $image_url = generate_graph_url($graph_array); 1413 1414 $graphs[] = array('label' => $graph_array['type'], 'type' => $graph_array['type'], 'url' => $image_url, 'data' => $image_data_uri); 1415 1416 unset($graph_array); 1417 } 1418 1419 /* unsed 1420 $graphs_html = ""; 1421 foreach ($graphs as $graph) { 1422 $graphs_html .= '<h4>' . $graph['type'] . '</h4>'; 1423 $graphs_html .= '<a href="' . $graph['url'] . '"><img src="' . $graph['data'] . '"></a><br />'; 1424 } 1425 */ 1426 1427 //print_vars($graphs); 1428 //print_vars($graphs_html); 1429 //print_vars($entry); 1430 1431 $message_tags = array( 1432 'ALERT_STATE' => ($entry['alert_status'] == '1' ? "RECOVER" : "ALERT"), 1433 'ALERT_URL' => generate_url(array('page' => 'device', 1434 'device' => $device['device_id'], 1435 'tab' => 'alert', 1436 'alert_entry' => $entry['alert_table_id'])), 1437 'ALERT_UNIXTIME' => $alert_unixtime, // Standart unixtime 1438 'ALERT_TIMESTAMP' => date('Y-m-d H:i:s P', $alert_unixtime), // ie: 2000-12-21 16:01:07 +02:00 1439 'ALERT_TIMESTAMP_RFC2822' => date('r', $alert_unixtime), // RFC 2822, ie: Thu, 21 Dec 2000 16:01:07 +0200 1440 'ALERT_TIMESTAMP_RFC3339' => date(DATE_RFC3339, $alert_unixtime), // RFC 3339, ie: 2005-08-15T15:52:01+00:00 1441 'ALERT_ID' => $entry['alert_table_id'], 1442 'ALERT_MESSAGE' => $alert['alert_message'], 1443 'CONDITIONS' => implode(PHP_EOL . ' ', $condition_array), 1444 'METRICS' => implode(PHP_EOL . ' ', $metric_array), 1445 'DURATION' => ($entry['alert_status'] == '1' ? ($entry['last_ok'] > 0 ? format_uptime($alert_unixtime - $entry['last_ok']) . " (" . format_unixtime($entry['last_ok']) . ")" : "Unknown") 1446 : ($entry['last_ok'] > 0 ? format_uptime($alert_unixtime - $entry['last_ok']) . " (" . format_unixtime($entry['last_ok']) . ")" : "Unknown")), 1447 1448 // Entity TAGs 1449 'ENTITY_LINK' => generate_entity_link($entry['entity_type'], $entry['entity_id'], $entity['entity_name']), 1450 'ENTITY_NAME' => $entity['entity_name'], 1451 'ENTITY_ID' => $entity['entity_id'], 1452 'ENTITY_TYPE' => $alert['entity_type'], 1453 'ENTITY_DESCRIPTION' => $entity['entity_descr'], 1454 //'ENTITY_GRAPHS' => $graphs_html, // Predefined/embedded html images 1455 'ENTITY_GRAPHS_ARRAY' => json_encode($graphs), // Json encoded images array 1456 1457 // Device TAGs 1458 'DEVICE_HOSTNAME' => $device['hostname'], 1459 'DEVICE_SYSNAME' => $device['sysName'], 1460 //'DEVICE_SYSDESCR' => $device['sysDescr'], 1461 'DEVICE_ID' => $device['device_id'], 1462 'DEVICE_LINK' => generate_device_link($device), 1463 'DEVICE_HARDWARE' => $device['hardware'], 1464 'DEVICE_OS' => $device['os_text'] . ' ' . $device['version'] . ($device['features'] ? ' (' . $device['features'] . ')' : ''), 1465 //'DEVICE_TYPE' => $device['type'], 1466 'DEVICE_LOCATION' => $device['location'], 1467 'DEVICE_UPTIME' => deviceUptime($device), 1468 'DEVICE_REBOOTED' => format_unixtime($device['last_rebooted']), 1469 ); 1470 1471 //logfile('debug.log', var_export($message, TRUE)); 1472 1473 $title = alert_generate_subject($device, $message_tags['ALERT_STATE'], $message_tags); 1474 $message_tags['TITLE'] = $title; 1475 1476 $alert_id = $entry['alert_test_id']; 1477 1478 $notify_status = FALSE; // Set alert notify status to FALSE by default 1479 1480 $notification_type = 'alert'; 1481 $contacts = get_alert_contacts($device, $alert_id, $notification_type); 1482 1483 $notification_ids = array(); // Init list of Notification IDs 1484 foreach ($contacts as $contact) 1485 { 1486 1487 // Add notification to queue 1488 $notification = array( 1489 'device_id' => $device['device_id'], 1490 'log_id' => $log_id, 1491 'aca_type' => $notification_type, 1492 //'severity' => 6, 1493 'endpoints' => json_encode($contact), 1494 'message_graphs' => $message_tags['ENTITY_GRAPHS_ARRAY'], 1495 'notification_added' => time(), 1496 'notification_lifetime' => 300, // Lifetime in seconds 1497 'notification_entry' => json_encode($entry), // Store full alert entry for use later if required (not sure that this needed) 1498 ); 1499 $notification_message_tags = $message_tags; 1500 unset($notification_message_tags['ENTITY_GRAPHS_ARRAY']); // graphs array stored in separate blob column message_graphs, do not duplicate this data 1501 $notification['message_tags'] = json_encode($notification_message_tags); 1502 /// DEBUG 1503 //file_put_contents('/tmp/alert_'.$alert_id.'_'.$message_tags['ALERT_STATE'].'_'.$alert_unixtime.'.json', json_encode($notification, JSON_PRETTY_PRINT)); 1504 $notification_id = dbInsert($notification, 'notifications_queue'); 1505 1506 print_cli_data("Queueing Notification ", "[" . $notification_id . "]"); 1507 1508 $notification_ids[] = $notification_id; 1509 } 1510 1511 return $notification_ids; 1512} 1513 1514// DOCME needs phpdoc block 1515// TESTME needs unit testing 1516function alert_generate_subject($device, $prefix, $message_tags) 1517{ 1518 $subject = "$prefix: [" . $device['hostname'] . ']'; 1519 1520 if ($message_tags['ENTITY_TYPE']) { 1521 $subject .= ' [' . $message_tags['ENTITY_TYPE'] . ']'; 1522 } 1523 if ($message_tags['ENTITY_NAME'] && $message_tags['ENTITY_NAME'] != $device['hostname']) { 1524 $subject .= ' [' . $message_tags['ENTITY_NAME'] . ']'; 1525 } 1526 $subject .= ' ' . $message_tags['ALERT_MESSAGE']; 1527 1528 return $subject; 1529} 1530 1531/** 1532 * Get contacts associated with selected notification type and alert ID 1533 * Currently know notification types: alert, syslog 1534 * 1535 * @param array $device Common device array 1536 * @param int $alert_id Alert ID 1537 * @param string $notification_type Used type for notifications 1538 * @return array Array with transport -> endpoints lists 1539 */ 1540function get_alert_contacts($device, $alert_id, $notification_type) 1541{ 1542 if (!is_array($device)) { 1543 $device = device_by_id_cache($device); 1544 } 1545 1546 $contacts = array(); 1547 1548 if (!$device['ignore'] && !get_dev_attrib($device, 'disable_notify') && !$GLOBALS['config']['alerts']['disable']['all']) { 1549 // figure out which transport methods apply to an alert 1550 1551 $sql = "SELECT * FROM `alert_contacts`"; 1552 $sql .= " WHERE `contact_disabled` = 0 AND `contact_id` IN"; 1553 $sql .= " (SELECT `contact_id` FROM `alert_contacts_assoc` WHERE `aca_type` = ? AND `alert_checker_id` = ?);"; 1554 1555 foreach (dbFetchRows($sql, array($notification_type, $alert_id)) as $contact) { 1556 $contacts[] = $contact; 1557 } 1558 1559 if (empty($contacts)) { 1560 // if alert_contacts table is not in use, fall back to default 1561 // hardcoded defaults for when there is no contact configured. 1562 1563 $email = NULL; 1564 1565 if ($GLOBALS['config']['email']['default_only']) { 1566 // default only mail 1567 $email = $GLOBALS['config']['email']['default']; 1568 } else { 1569 // default device contact 1570 if (get_dev_attrib($device, 'override_sysContact_bool')) { 1571 $email = get_dev_attrib($device, 'override_sysContact_string'); 1572 } else { 1573 if (parse_email($device['sysContact'])) { 1574 $email = $device['sysContact']; 1575 } else { 1576 $email = $GLOBALS['config']['email']['default']; 1577 } 1578 } 1579 } 1580 1581 if ($email != NULL) { 1582 $emails = parse_email($email); 1583 1584 foreach ($emails as $email => $descr) { 1585 $contacts[] = array('contact_endpoint' => '{"email":"' . $email . '"}', 'contact_id' => '0', 'contact_descr' => $descr, 'contact_method' => 'email'); 1586 } 1587 } 1588 } 1589 } 1590 1591 return $contacts; 1592} 1593 1594function process_notifications($vars = array()) 1595{ 1596 global $config; 1597 1598 $result = array(); 1599 $params = array(); 1600 1601 $sql = 'SELECT * FROM `notifications_queue` WHERE 1'; 1602 1603 foreach ($vars as $var => $value) 1604 { 1605 switch ($var) 1606 { 1607 case 'device_id': 1608 case 'notification_id': 1609 case 'aca_type': 1610 $sql .= generate_query_values($value, $var); 1611 //$sql .= ' AND `device_id` = ?'; 1612 //$params[] = $value; 1613 break; 1614 } 1615 } 1616 1617 /** 1618 * switch ($notification_type) 1619 * { 1620 * case 'alert': 1621 * case 'syslog': 1622 * // Alerts/syslog required device_id 1623 * $sql .= ' AND `device_id` = ?'; 1624 * $params[] = $device['device_id']; 1625 * break; 1626 * case 'web': 1627 * // Currently not used 1628 * break; 1629 * } 1630 **/ 1631 1632 foreach (dbFetchRows($sql, $params) as $notification) 1633 { 1634 1635 print_debug_vars($notification); 1636 1637 // Recheck if current notification is locked 1638 $locked = dbFetchCell('SELECT `notification_locked` FROM `notifications_queue` WHERE `notification_id` = ?', array($notification['notification_id'])); //ALTER TABLE `notifications_queue` ADD `notification_locked` BOOLEAN NOT NULL DEFAULT FALSE AFTER `notification_entry`; 1639 //if ($locked || $locked === NULL || $locked === FALSE) // If notification not exist or column 'notification_locked' not exist this query return NULL or (possible?) FALSE 1640 if ($locked || $locked === FALSE) 1641 { 1642 // Notification already processed by other alerter or has already been sent 1643 print_debug('Notification ID ('.$notification['notification_id'].') locked or not exist anymore in table. Skipped.'); 1644 print_debug_vars($notification, 1); 1645 continue; 1646 } else { 1647 // Lock current notification 1648 dbUpdate(array('notification_locked' => 1), 'notifications_queue', '`notification_id` = ?', array($notification['notification_id'])); 1649 } 1650 1651 $notification_count = 0; 1652 $endpoint = json_decode($notification['endpoints'], TRUE); 1653 1654 // If this notification is older than lifetime, unset the endpoints so that it is removed. 1655 if ((time() - $notification['notification_added']) > $notification['notification_lifetime']) { 1656 $endpoint = array(); 1657 print_debug('Notification ID ('.$notification['notification_id'].') expired.'); 1658 print_debug_vars($notification, 1); 1659 } else { 1660 $notification_age = time() - $notification['notification_added']; 1661 $notification_timeleft = $notification['notification_lifetime'] - $notification_age; 1662 } 1663 1664 $message_tags = json_decode($notification['message_tags'], TRUE); 1665 $message_graphs = json_decode($notification['message_graphs'], TRUE); 1666 if (is_array($message_graphs) && count($message_graphs)) 1667 { 1668 $message_tags['ENTITY_GRAPHS_ARRAY'] = $message_graphs; 1669 } 1670 if (isset($message_tags['ALERT_UNIXTIME']) && empty($message_tags['DURATION'])) 1671 { 1672 $message_tags['DURATION'] = format_uptime(time() - $message_tags['ALERT_UNIXTIME']) . ' (' . $message_tags['ALERT_TIMESTAMP'] . ')'; 1673 } 1674 1675 if (isset($GLOBALS['config']['alerts']['disable'][$endpoint['contact_method']]) && $GLOBALS['config']['alerts']['disable'][$endpoint['contact_method']]) { 1676 $result[$method] = 'disabled'; 1677 unset($endpoint); 1678 continue; 1679 } // Skip if method disabled globally 1680 1681 $method_include = $GLOBALS['config']['install_dir'] . '/includes/alerting/' . $endpoint['contact_method'] . '.inc.php'; 1682 1683 if (is_file($method_include)) 1684 { 1685 $transport = $endpoint['contact_method']; // Just set transport name for use in includes 1686 1687 //print_cli_data("Notifying", "[" . $endpoint['contact_method'] . "] " . $endpoint['contact_descr'] . ": " . $endpoint['contact_endpoint']); 1688 print_cli_data_field("Notifying"); 1689 echo("[" . $endpoint['contact_method'] . "] " . $endpoint['contact_descr'] . ": " . $endpoint['contact_endpoint']); 1690 1691 // Split out endpoint data as stored JSON in the database into array for use in transport 1692 // The original string also remains available as the contact_endpoint key 1693 foreach (json_decode($endpoint['contact_endpoint']) as $field => $value) { 1694 $endpoint[$field] = $value; 1695 } 1696 1697 include($method_include); 1698 1699 // FIXME check success 1700 // FIXME log notification + success/failure! 1701 if ($notify_status['success']) 1702 { 1703 $result[$method] = 'ok'; 1704 unset($endpoint); 1705 $notification_count++; 1706 print_message(" [%gOK%n]", 'color'); 1707 } else { 1708 $result[$method] = 'false'; 1709 print_message(" [%rFALSE%n]", 'color'); 1710 if ($notify_status['error']) 1711 { 1712 print_cli_data_field('', 4); 1713 print_message("[%y".$notify_status['error']."%n]", 'color'); 1714 } 1715 } 1716 } else { 1717 $result[$method] = 'missing'; 1718 unset($endpoint); // Remove it because it's dumb and doesn't exist. Don't retry it if it doesn't exist. 1719 print_cli_data("Missing include", $method_include); 1720 } 1721 1722 // Remove notification from queue, 1723 // currently in any case, lifetime, added time and result status is ignored! 1724 switch ($notification_type) { 1725 case 'alert': 1726 if ($notification_count) { 1727 dbUpdate(array('notified' => 1), 'alert_log', '`event_id` = ?', array($notification['log_id'])); 1728 } 1729 break; 1730 case 'syslog': 1731 if ($notification_count) { 1732 dbUpdate(array('notified' => 1), 'syslog_alerts', '`lal_id` = ?', array($notification['log_id'])); 1733 } 1734 break; 1735 case 'web': 1736 // Currently not used 1737 break; 1738 } 1739 1740 if (empty($endpoint)) { 1741 dbDelete('notifications_queue', '`notification_id` = ?', array($notification['notification_id'])); 1742 } else { 1743 // Set the endpoints to the remaining un-notified endpoints and unlock the queue entry. 1744 dbUpdate(array('notification_locked' => 0, 'endpoints' => json_encode($endpoint)), 'notifications_queue', '`notification_id` = ?', array($notification['notification_id'])); 1745 } 1746 } 1747 1748 return $result; 1749} 1750 1751// Use this function to write to the alert_log table 1752// Fix me - quite basic. 1753// DOCME needs phpdoc block 1754// TESTME needs unit testing 1755function log_alert($text, $device, $alert, $log_type) 1756{ 1757 $insert = array('alert_test_id' => $alert['alert_test_id'], 1758 'device_id' => $device['device_id'], 1759 'entity_type' => $alert['entity_type'], 1760 'entity_id' => $alert['entity_id'], 1761 'timestamp' => array("NOW()"), 1762 //'status' => $alert['alert_status'], 1763 'log_type' => $log_type, 1764 'message' => $text); 1765 1766 $id = dbInsert($insert, 'alert_log'); 1767 1768 return $id; 1769} 1770 1771function threshold_string($alert_low, $warn_low, $warn_high, $alert_high, $symbol = NULL) 1772{ 1773 1774 // Generate "pretty" thresholds 1775 if (is_numeric($alert_low)) 1776 { 1777 $alert_low_t = format_value($alert_low, $format) . $symbol; 1778 } else { 1779 $alert_low_t = "∞"; 1780 } 1781 1782 if (is_numeric($warn_low)) 1783 { 1784 $warn_low_t = format_value($warn_low, $format) . $symbol; 1785 } else { 1786 $warn_low_t = NULL; 1787 } 1788 1789 if ($warn_low_t) { $alert_low_t = $alert_low_t . " (".$warn_low_t.")"; } 1790 1791 if (is_numeric($alert_high)) 1792 { 1793 $alert_high_t = format_value($alert_high, $format) . $symbol; 1794 } else { 1795 $alert_high_t = "∞"; 1796 } 1797 1798 if (is_numeric($warn_high)) 1799 { 1800 $warn_high_t = format_value($warn_high, $format) . $symbol; 1801 } else { 1802 $warn_high_t = NULL; 1803 } 1804 1805 if ($warn_high_t) { $alert_high_t = "(".$warn_high_t.") " . $alert_high_t; } 1806 1807 $thresholds = $alert_low_t . ' - ' . $alert_high_t; 1808 1809 return $thresholds; 1810 1811} 1812 1813function check_thresholds($alert_low, $warn_low, $warn_high, $alert_high, $value) 1814{ 1815 1816 if (!is_numeric($value)) { return 'alert'; } // Not numeric value always alert 1817 1818 if ((is_numeric($alert_low) && $value <= $alert_low) || 1819 (is_numeric($alert_high) && $value >= $alert_high)) 1820 { 1821 $event = 'alert'; 1822 } 1823 elseif ((is_numeric($warn_low) && $value < $warn_low) || 1824 (is_numeric($warn_high) && $value > $warn_high)) 1825 { 1826 $event = 'warning'; 1827 } else { 1828 $event = 'ok'; 1829 } 1830 1831 /* 1832 if(is_numeric($warn_low) && $warn_low > $value) { $status = 'warn'; } 1833 if(is_numeric($warn_high) && $warn_high < $value) { $status = 'warn'; } 1834 1835 if(is_numeric($alert_low) && $alert_low > $value) { $status = 'alert'; } 1836 if(is_numeric($alert_high) && $alert_high < $value) { $status = 'alert'; } 1837 */ 1838 1839 return $event; 1840 1841} 1842 1843function get_alert_entities_from_assocs($alert) 1844{ 1845 1846 $entity_type_data = entity_type_translate_array($alert['entity_type']); 1847 1848 $entity_type = $alert['entity_type']; 1849 1850 $sql = 'SELECT `'.$entity_type_data['table_fields']['id'] . '` AS `entity_id`'; 1851 1852 //We always need device_id and it's always from devices, duh! 1853 //if ($alert['entity_type'] != 'device') 1854 //{ 1855 $sql .= ", `devices`.`device_id` as `device_id`"; 1856 //} 1857 1858 $sql .= ' FROM `'.$entity_type_data['table'].'` '; 1859 1860// if (isset($entity_type_data['state_table'])) 1861// { 1862// $sql .= ' LEFT JOIN `'.$entity_type_data['state_table'].'` USING (`'.$entity_type_data['id_field'].'`)'; 1863// } 1864 1865 if (isset($entity_type_data['parent_table'])) 1866 { 1867 $sql .= ' LEFT JOIN `'.$entity_type_data['parent_table'].'` USING (`'.$entity_type_data['parent_id_field'].'`)'; 1868 } 1869 1870 if ($alert['entity_type'] != 'device') 1871 { 1872 $sql .= ' LEFT JOIN `devices` ON (`'.$entity_type_data['table'].'`.`device_id` = `devices`.`device_id`) '; 1873 } 1874 1875 1876 1877 foreach ($alert['assocs'] as $assoc) 1878 { 1879 1880 $where = ' (( 1'; 1881 1882 foreach ($assoc['device_attribs'] as $attrib) 1883 { 1884 switch ($attrib['condition']) 1885 { 1886 case 'ge': 1887 case '>=': 1888 $where .= ' AND `devices`.`' . $attrib['attrib'] . '` >= ?'; 1889 $params[] = $attrib['value']; 1890 break; 1891 case 'le': 1892 case '<=': 1893 $where .= ' AND `devices`.`' . $attrib['attrib'] . '` <= ?'; 1894 $params[] = $attrib['value']; 1895 break; 1896 case 'gt': 1897 case 'greater': 1898 case '>': 1899 $where .= ' AND `devices`.`' . $attrib['attrib'] . '` > ?'; 1900 $params[] = $attrib['value']; 1901 break; 1902 case 'lt': 1903 case 'less': 1904 case '<': 1905 $where .= ' AND `devices`.`' . $attrib['attrib'] . '` < ?'; 1906 $params[] = $attrib['value']; 1907 break; 1908 case 'notequals': 1909 case 'isnot': 1910 case 'ne': 1911 case '!=': 1912 $where .= ' AND `devices`.`' . $attrib['attrib'] . '` != ?'; 1913 $params[] = $attrib['value']; 1914 break; 1915 case 'equals': 1916 case 'eq': 1917 case 'is': 1918 case '==': 1919 case '=': 1920 $where .= ' AND `devices`.`' . $attrib['attrib'] . '` = ?'; 1921 $params[] = $attrib['value']; 1922 break; 1923 case 'match': 1924 case 'matches': 1925 $attrib['value'] = str_replace('*', '%', $attrib['value']); 1926 $attrib['value'] = str_replace('?', '_', $attrib['value']); 1927 $where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") LIKE ?'; 1928 $params[] = $attrib['value']; 1929 break; 1930 case 'notmatches': 1931 case 'notmatch': 1932 case '!match': 1933 $attrib['value'] = str_replace('*', '%', $attrib['value']); 1934 $attrib['value'] = str_replace('?', '_', $attrib['value']); 1935 $where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") NOT LIKE ?'; 1936 $params[] = $attrib['value']; 1937 break; 1938 case 'regexp': 1939 case 'regex': 1940 $where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") REGEXP ?'; 1941 $params[] = $attrib['value']; 1942 break; 1943 case 'notregexp': 1944 case 'notregex': 1945 case '!regexp': 1946 case '!regex': 1947 $where .= ' AND IFNULL(`devices`.`' . $attrib['attrib'] . '`, "") NOT REGEXP ?'; 1948 $params[] = $attrib['value']; 1949 break; 1950 case 'in': 1951 case 'list': 1952 $where .= generate_query_values(explode(',', $attrib['value']), '`devices`.' . $attrib['attrib']); 1953 break; 1954 case '!in': 1955 case '!list': 1956 case 'notin': 1957 case 'notlist': 1958 $where .= generate_query_values(explode(',', $attrib['value']), '`devices`.' . $attrib['attrib'], '!='); 1959 break; 1960 case 'include': 1961 case 'includes': 1962 switch ($attrib['attrib']) 1963 { 1964 case 'group': 1965 $attrib['value'] = group_id_by_name($attrib['value']); 1966 case 'group_id': 1967 $values = get_group_entities($attrib['value']); 1968 $where .= generate_query_values($values, "`devices`.`device_id`"); 1969 break; 1970 } 1971 break; 1972 } 1973 1974 } // End device_attribs 1975 1976 1977 $where .= ") AND ( 1"; 1978 1979 foreach ($assoc['entity_attribs'] as $attrib) { 1980 switch ($attrib['condition']) { 1981 case 'ge': 1982 case '>=': 1983 $where .= ' AND `' . $attrib['attrib'] . '` >= ?'; 1984 $params[] = $attrib['value']; 1985 break; 1986 case 'le': 1987 case '<=': 1988 $where .= ' AND `' . $attrib['attrib'] . '` <= ?'; 1989 $params[] = $attrib['value']; 1990 break; 1991 case 'gt': 1992 case 'greater': 1993 case '>': 1994 $where .= ' AND `' . $attrib['attrib'] . '` > ?'; 1995 $params[] = $attrib['value']; 1996 break; 1997 case 'lt': 1998 case 'less': 1999 case '<': 2000 $where .= ' AND `' . $attrib['attrib'] . '` < ?'; 2001 $params[] = $attrib['value']; 2002 break; 2003 case 'notequals': 2004 case 'isnot': 2005 case 'ne': 2006 case '!=': 2007 $where .= ' AND `' . $attrib['attrib'] . '` != ?'; 2008 $params[] = $attrib['value']; 2009 break; 2010 case 'equals': 2011 case 'eq': 2012 case 'is': 2013 case '==': 2014 case '=': 2015 $where .= ' AND `' . $attrib['attrib'] . '` = ?'; 2016 $params[] = $attrib['value']; 2017 break; 2018 case 'match': 2019 case 'matches': 2020 $attrib['value'] = str_replace('*', '%', $attrib['value']); 2021 $attrib['value'] = str_replace('?', '_', $attrib['value']); 2022 $where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") LIKE ?'; 2023 $params[] = $attrib['value']; 2024 break; 2025 case 'notmatches': 2026 case 'notmatch': 2027 case '!match': 2028 $attrib['value'] = str_replace('*', '%', $attrib['value']); 2029 $attrib['value'] = str_replace('?', '_', $attrib['value']); 2030 $where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT LIKE ?'; 2031 $params[] = $attrib['value']; 2032 break; 2033 case 'regexp': 2034 case 'regex': 2035 $where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") REGEXP ?'; 2036 $params[] = $attrib['value']; 2037 break; 2038 case 'notregexp': 2039 case 'notregex': 2040 case '!regexp': 2041 case '!regex': 2042 $where .= ' AND IFNULL(`' . $attrib['attrib'] . '`, "") NOT REGEXP ?'; 2043 $params[] = $attrib['value']; 2044 break; 2045 case 'in': 2046 case 'list': 2047 $where .= generate_query_values(explode(',', $attrib['value']), $attrib['attrib']); 2048 break; 2049 case '!in': 2050 case '!list': 2051 case 'notin': 2052 case 'notlist': 2053 $where .= generate_query_values(explode(',', $attrib['value']), $attrib['attrib'], '!='); 2054 break; 2055 case 'include': 2056 case 'includes': 2057 switch ($attrib['attrib']) { 2058 2059 case 'group': 2060 $attrib['value'] = group_id_by_name($attrib['value']); 2061 case 'group_id': 2062 $group = get_group_by_id($attrib['value']); 2063 if($group['entity_type'] == $entity_type) 2064 { 2065 $values = get_group_entities($attrib['value']); 2066 $where .= generate_query_values($values, $entity_type['table_fields']['id']); 2067 } 2068 break; 2069 } 2070 } 2071 } 2072 2073 $where .= '))'; 2074 2075 $assoc_where[] = $where; 2076 2077 } 2078 2079 if (empty($assoc_where)) 2080 { 2081 print_debug('WARNING. Into function '.__FUNCTION__.'() passed incorrect or empty entries.'); 2082 return FALSE; 2083 } 2084 2085 $where = "WHERE `devices`.`ignore` = '0' AND `devices`.`disabled` = '0' AND (" . implode(" OR ", $assoc_where) .")"; 2086 2087 if (isset($entity_type_data['deleted_field'])) { 2088 $where .= " AND `" . $entity_type_data['deleted_field'] . "` != '1'"; 2089 } 2090 2091 $query = $sql; 2092 $query .= $where; 2093 2094 $entities = dbFetchRows($query, $params); 2095 //$entities = dbFetchRows($query, $params, TRUE); 2096 2097 $return = []; 2098 foreach($entities as $entry) 2099 { 2100 $return[$entry['entity_id']] = array('entity_id' => $entry['entity_id'], 'device_id' => $entry['device_id']); 2101 } 2102 2103 return $return; 2104 2105 //print_vars($devices); 2106} 2107 2108// QB / Alerts/ Groups common functions 2109 2110// 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) 2111function values_to_json($values) 2112{ 2113 2114 //foreach($values as $id => $value) { $array[] = "'".$id."': '".str_replace(array("'", ","), array("\'", "\,")."'"; } 2115 foreach ($values as $id => $value) 2116 { 2117 //$array[] = '{ '.json_encode($id, OBS_JSON_ENCODE).': '.json_encode($value, OBS_JSON_ENCODE).' }'; 2118 // Expanded format with optgroups 2119 // { value: 'one', label: 'Un', optgroup: 'Group 1' }, 2120 if (is_array($value)) 2121 { 2122 // Our form builder params 2123 $str = '{ value: '.json_encode($id, OBS_JSON_ENCODE).', label: '.json_encode($value['name'], OBS_JSON_ENCODE); 2124 if (isset($value['group'])) 2125 { 2126 $str .= ', optgroup: ' . json_encode($value['group'], OBS_JSON_ENCODE); 2127 } 2128 $str .= ' }'; 2129 $array[] = $str; 2130 } else { 2131 // Simple value -> label 2132 // { value: 'one', label: 'Un', optgroup: 'Group 1' }, 2133 $array[] = '{ value: '.json_encode($id, OBS_JSON_ENCODE).', label: '.json_encode($value, OBS_JSON_ENCODE).' }'; 2134 } 2135 } 2136 2137 $array = ' [ '.implode(', ', $array).' ] '; 2138 2139 return $array; 2140} 2141 2142function generate_attrib_values($attrib, $vars) 2143{ 2144 2145 $values = array(); 2146 //r($vars); 2147 2148 switch ($attrib) 2149 { 2150 case "device": 2151 $values = generate_form_values('device', NULL, NULL, array('disabled' => TRUE)); 2152 //$devices = get_all_devices(); 2153 //foreach($devices as $id => $hostname) 2154 //{ 2155 // $values[$id] = $hostname; 2156 //} 2157 break; 2158 case "os": 2159 foreach ($GLOBALS['config']['os'] AS $os => $os_array) 2160 { 2161 $values[$os] = $os_array['text']; 2162 } 2163 break; 2164 case "measured_group": 2165 $groups = get_groups_by_type($vars['measured_type']); 2166 foreach ($groups[$vars['measured_type']] as $group_id => $array) 2167 { 2168 $values[$array['group_id']] = $array['group_name']; 2169 } 2170 break; 2171 case "group": 2172 $groups = get_groups_by_type($vars['entity_type']); 2173 foreach ($groups[$vars['entity_type']] as $group_id => $array) 2174 { 2175 $values[$array['group_id']] = $array['group_name']; 2176 } 2177 break; 2178 case "location": 2179 $values = get_locations(); 2180 break; 2181 case "device_type": 2182 foreach ($GLOBALS['config']['device_types'] AS $type) 2183 { 2184 $values[$type['type']] = $type['text']; 2185 } 2186 break; 2187 case "device_vendor": 2188 case "device_hardware": 2189 case "device_distro": 2190 case "device_distro_ver": 2191 list(, $column) = explode('_', $attrib, 2); 2192 $query = "SELECT DISTINCT `$column` FROM `devices`"; 2193 foreach (dbFetchColumn($query) as $item) 2194 { 2195 if (strlen($item)) { $values[$item] = $item; } 2196 } 2197 ksort($values); 2198 break; 2199 /* 2200 case "device_distro": 2201 $query = "SELECT `distro` FROM `devices` GROUP BY `distro` ORDER BY `distro`"; 2202 foreach (dbFetchColumn($query) as $item) 2203 { 2204 if (strlen($item)) { $values[$item] = $item; } 2205 } 2206 break; 2207 case "device_distro_ver": 2208 $query = "SELECT `distro_ver` FROM `devices` GROUP BY `distro_ver` ORDER BY `distro_ver`"; 2209 foreach (dbFetchColumn($query) as $item) 2210 { 2211 if (strlen($item)) { $values[$item] = $item; } 2212 } 2213 break; 2214 */ 2215 case "sensor_class": 2216 foreach($GLOBALS['config']['sensor_types'] AS $class => $data) 2217 { 2218 $values[$class] = nicecase($class); 2219 } 2220 break; 2221 case "status_type": 2222 $query = "SELECT `status_type` FROM `status` GROUP BY `status_type` ORDER BY `status_type`"; 2223 foreach (dbFetchColumn($query) as $item) 2224 { 2225 if (strlen($item)) { $values[$item] = $item; } 2226 } 2227 break; 2228 } 2229 2230 return $values; 2231 2232} 2233 2234function generate_querybuilder_filter($attrib) 2235{ 2236 2237 // Default operators, possible custom list from entity definition (ie group) 2238 if (isset($attrib['operators'])) 2239 { 2240 // All possible operators, for validate entity attrib 2241 $operators_array = array('equals', 'notequals', 'le', 'ge', 'lt', 'gt', 'match', 'notmatch', 'regexp', 'notregexp', 'in', 'notin', 'isnull', 'isnotnull'); 2242 2243 // List to array 2244 if (!is_array($attrib['operators'])) 2245 { 2246 $attrib['operators'] = explode(',', str_replace(' ', '', $attrib['operators'])); 2247 } 2248 2249 $operators = array_intersect($attrib['operators'], $operators_array); // Validate operators list 2250 $text_operators = "['" . implode("', '", $operators) . "']"; 2251 } else { 2252 $text_operators = "['equals', 'notequals', 'match', 'notmatch', 'regexp', 'notregexp', 'in', 'notin', 'isnull', 'isnotnull']"; 2253 } 2254 $num_operators = "['equals', 'notequals', 'le', 'ge', 'lt', 'gt', 'in', 'notin']"; 2255 $list_operators = "['in', 'notin']"; 2256 $bool_operators = "['equals', 'notequals']"; 2257 $function_operators = "['in', 'notin']"; 2258 2259 $attrib['attrib_id'] = ($attrib['entity_type'] == 'device' ? 'device.' : 'entity.').$attrib['attrib_id']; 2260 $attrib['label'] = ($attrib['entity_type'] == 'device' ? 'Device ' : nicecase($attrib['entity_type']).' ').$attrib['label']; 2261 2262 // Clean label duplicates 2263 $attrib['label'] = implode(' ', array_unique(explode(' ', $attrib['label']))); 2264 //$attrib['label'] = str_replace("Device Device", "Device", $attrib['label']); 2265 //r($attrib); 2266 2267 $filter_array[] = "id: '".$attrib['attrib_id']. ($attrib['free'] ? '.free': '')."'"; 2268 $filter_array[] = "field: '".$attrib['attrib_id']. "'"; 2269 $filter_array[] = "label: '".$attrib['label']. ($attrib['free'] ? ' (Free)': '')."'"; 2270 if ($attrib['type'] == 'boolean') 2271 { 2272 // Prevent store boolean type as boolean true/false in DB, keep as integer 2273 $filter_array[] = "type: 'integer'"; 2274 } else { 2275 $filter_array[] = "type: '".$attrib['type']."'"; 2276 } 2277 $filter_array[] = "optgroup: '".nicecase($attrib['entity_type'])."'"; 2278 2279 // Plugins options: 2280 $selectpicker_options = "width: '100%', iconBase: '', tickIcon: 'glyphicon glyphicon-ok', showTick: true, selectedTextFormat: 'count>2', "; 2281 $tagsinput_options = "trimValue: true, tagClass: function(item) { return 'label label-default'; }"; 2282 2283 if (isset($attrib['values'])) 2284 { 2285 2286 if (is_array($attrib['values'])) { 2287 2288 $value_list = array(); 2289 foreach($attrib['values'] AS $value) { 2290 $value_list[$value] = $value; 2291 } 2292 2293 } else { 2294 $value_list = generate_attrib_values($attrib['values'], array('entity_type' => $attrib['entity_type'], 'measured_type' => $attrib['measured_type'])); 2295 } 2296 2297 asort($value_list); 2298 //r($value_list); 2299 if (count($value_list) > 7) 2300 { 2301 $selectpicker_options .= "liveSearch: true, actionsBox: true, "; 2302 } 2303 $values = values_to_json($value_list); 2304 $filter_array[] = "input: 'select'"; 2305 $filter_array[] = "plugin: 'selectpicker'"; 2306 $filter_array[] = "plugin_config: { $selectpicker_options }"; 2307 $filter_array[] = "values: ".$values; 2308 $filter_array[] = "multiple: true"; 2309 $filter_array[] = "operators: ".$list_operators; 2310 } else { 2311 2312 if (isset($attrib['function'])) 2313 { 2314 register_html_resource('js', 'bootstrap-tagsinput.min.js'); // Enable Tags Input JS 2315 register_html_resource('css', 'bootstrap-tagsinput.css'); // Enable Tags Input CSS 2316 $filter_array[] = "input: 'select'"; 2317 $filter_array[] = "plugin: 'tagsinput'"; 2318 $filter_array[] = "plugin_config: { $tagsinput_options }"; 2319 //$filter_array[] = "value_separator: ','"; 2320 $filter_array[] = "valueSetter: function(rule, value) { 2321 var rule_container = rule.\$el.find('.rule-value-container select'); 2322 if (typeof value == 'string') { 2323 rule_container.tagsinput('add', value); 2324 } else { 2325 for (i = 0; i < value.length; ++i) { rule_container.tagsinput('add', value[i]); } 2326 } 2327 }"; 2328 $filter_array[] = "multiple: true"; 2329 $filter_array[] = "operators: ".$function_operators; 2330 } 2331 else if ($attrib['type'] == 'integer') 2332 { 2333 $filter_array[] = "operators: ".$num_operators; 2334 } 2335 else if ($attrib['type'] == 'boolean') 2336 { 2337 $values = values_to_json(array(0 => 'False', 1 => 'True')); 2338 $filter_array[] = "input: 'select'"; 2339 $filter_array[] = "plugin: 'selectpicker'"; 2340 $filter_array[] = "plugin_config: { $selectpicker_options }"; 2341 $filter_array[] = "values: ".$values; 2342 $filter_array[] = "multiple: false"; 2343 $filter_array[] = "operators: ".$bool_operators; 2344 } else { 2345 $filter_array[] = "operators: ".$text_operators; 2346 //$filter_array[] = "plugin: 'tagsinput'"; 2347 //$filter_array[] = "value_separator: ','"; 2348 2349 } 2350 } 2351 2352 $filter = PHP_EOL . '{ '.implode(','.PHP_EOL, $filter_array).' } '; 2353 2354 return $filter; 2355 2356} 2357 2358function generate_querybuilder_filters($entity_type, $type = "attribs") 2359{ 2360 2361 $type = (($type == "attribs" || $type == "metrics") ? $type : 'attribs'); 2362 2363 if (isset($GLOBALS['config']['entities'][$entity_type]['parent_type'])) 2364 { 2365 $filter = generate_querybuilder_filters($GLOBALS['config']['entities'][$entity_type]['parent_type']); 2366 } 2367 else if($type != "metrics" && $entity_type != "device") 2368 { 2369 $filter = generate_querybuilder_filters("device"); 2370 } 2371 2372 foreach($GLOBALS['config']['entities'][$entity_type][$type] AS $attrib_id => $attrib) 2373 { 2374 $attrib['entity_type'] = $entity_type; 2375 $attrib['attrib_id'] = $attrib_id; 2376 2377 $filter[] = generate_querybuilder_filter($attrib); 2378 2379 if (isset($attrib['values']) && !str_ends($attrib['attrib_id'], "_id") && // Don't show freeform variant for device_id, location_id, group_id and etc 2380 (!isset($attrib['free']) || $attrib['free'])) // Don't show freeform variant if attrib free set to false 2381 { 2382 unset($attrib['values']); 2383 $attrib['free'] = 1; 2384 $filter[] = generate_querybuilder_filter($attrib); 2385 } 2386 } 2387 2388 //$filters = ' [ '.implode(', ', $filter).' ] '; 2389 2390 //print_vars($filter); 2391 return $filter; 2392 2393} 2394 2395function generate_querybuilder_form($entity_type, $type = "attribs", $form_id = 'rules-form', $ruleset = NULL) 2396{ 2397 2398 // Set rulesets, with allow invalid! 2399 if (!empty($ruleset)) 2400 { 2401 $rulescript = " 2402 var rules = ".$ruleset."; 2403 2404 $('#".$form_id."').queryBuilder('setRules', rules, { allow_invalid: true }); 2405 2406 $('#btn-set').on('click', function() { 2407 $('#".$form_id."').queryBuilder('setRules', rules, { allow_invalid: true }); 2408 });"; 2409 2410 register_html_resource('script', $rulescript); 2411 } 2412 2413 $filters = ' [ '.implode(', ', generate_querybuilder_filters($entity_type, $type)).' ] '; 2414 2415 //$form_id = 'builder-'.$entity_type.'-'.$type; 2416 2417 echo (' 2418 2419 <div class="box box-solid"> 2420 <!-- <div class="box-header with-border"> 2421 <h3>' . nicecase($entity_type) . ' '.nicecase($type).' Rules Builder</h3> 2422 </div> --> 2423 2424 <div id="'.$form_id.'"></div> 2425 2426 <!-- 2427 <div class="box-footer"> 2428 <div class="btn-group pull-right"> 2429 <button class="btn btn-sm btn-danger" id="btn-reset" data-target="'.$form_id.'">Reset</button> 2430 <button class="btn btn-sm btn-success" id="btn-set" data-target="'.$form_id.'">Set rules</button> 2431 <button class="btn btn-sm btn-success" id="btn-get" data-target="'.$form_id.'">Show JSON</button> 2432 <button class="btn btn-sm btn-primary" id="btn-save" data-target="'.$form_id.'">Save Rules</button> 2433 </div> 2434 </div> --> 2435 </div>'); 2436 2437 2438 echo ("<div class='box box-solid' id='output'></div> 2439 2440<script> 2441 2442 $('#".$form_id."').queryBuilder({ 2443 plugins: { 2444 'bt-selectpicker': { 2445 style: 'btn-inverse btn', 2446 width: '100%', 2447 liveSearch: true, 2448 }, 2449 'sortable': null, 2450 }, 2451 filters: ".$filters.", 2452 2453 //operators: $.fn.queryBuilder.constructor.DEFAULTS.operators.concat([ 2454 operators: ([ 2455 { type: 'le', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2456 { type: 'ge', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2457 { type: 'lt', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2458 { type: 'gt', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2459 { type: 'equals', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2460 { type: 'notequals', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2461 { type: 'match', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2462 { type: 'notmatch', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2463 { type: 'regexp', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2464 { type: 'notregexp', nb_inputs: 1, multiple: false, apply_to: ['string'] }, 2465 { type: 'in', nb_inputs: 1, multiple: true, apply_to: ['string'] }, 2466 { type: 'notin', nb_inputs: 1, multiple: true, apply_to: ['string'] }, 2467 { type: 'isnull', nb_inputs: 0, apply_to: ['string'] }, 2468 { type: 'isnotnull', nb_inputs: 0, apply_to: ['string'] } 2469 ]), 2470 lang: { 2471 operators: { 2472 le: 'less or equal', 2473 ge: 'greater or equal', 2474 lt: 'less than', 2475 gt: 'greater than', 2476 equals: 'equals', 2477 notequals: 'not equals', 2478 match: 'match', 2479 notmatch: 'not match', 2480 regexp: 'regexp', 2481 notregexp: 'not regexp', 2482 in: 'in', 2483 notin: 'not in', 2484 isnull: 'is null', 2485 isnotnull: 'not null' 2486 } 2487 }, 2488 }); 2489 2490 2491$('#btn-reset').on('click', function() { 2492 $('#".$form_id."').queryBuilder('reset'); 2493}); 2494 2495$('#btn-get').on('click', function() { 2496 var result = $('#".$form_id."').queryBuilder('getRules'); 2497 2498 if (!$.isEmptyObject(result)) { 2499 bootbox.alert({ 2500 title: $(this).text(), 2501 message: '<pre class=\"code-popup\">' + format4popup(result) + '</pre>' 2502 }); 2503 } 2504}); 2505 2506function format4popup(object) { 2507 return JSON.stringify(object, null, 2).replace(/</g, '<').replace(/>/g, '>') 2508} 2509 2510</script> 2511 2512"); 2513 2514} 2515 2516function parse_qb_ruleset($entity_type, $rules, $ignore = FALSE) 2517{ 2518 2519 $entity_type_data = entity_type_translate_array($entity_type); 2520 2521 $sql = 'SELECT `'.$entity_type_data['table_fields']['id'] . '`'; 2522 2523 if ($entity_type != 'device') 2524 { 2525 $sql .= ", `devices`.`device_id` as `device_id`"; 2526 } 2527 2528 $sql .= ' FROM `'.$entity_type_data['table'].'` '; 2529 2530// if (isset($entity_type_data['state_table'])) 2531// { 2532// $sql .= ' LEFT JOIN `'.$entity_type_data['state_table'].'` USING (`'.$entity_type_data['id_field'].'`)'; 2533// } 2534 2535 if (isset($entity_type_data['parent_table'])) 2536 { 2537 $sql .= ' LEFT JOIN `'.$entity_type_data['parent_table'].'` USING (`'.$entity_type_data['parent_id_field'].'`)'; 2538 } 2539 2540 if ($entity_type != 'device') 2541 { 2542 $sql .= ' LEFT JOIN `devices` ON (`'.$entity_type_data['table'].'`.`device_id` = `devices`.`device_id`) '; 2543 } 2544 2545 $sql .= " WHERE "; 2546 2547 $sql .= parse_qb_rules($entity_type, $rules, $ignore); 2548 2549 if ($ignore) // This is for alerting, so filter out ignore/disabled stuff 2550 { 2551 // Exclude ignored entities 2552 if (isset($entity_type_data['ignore_field'])) 2553 { 2554 $sql .= " AND `".$entity_type_data['table']."`.`" . $entity_type_data['ignore_field'] . "` != '1'"; 2555 } 2556 // Exclude disabled entities 2557 if (isset($entity_type_data['disable_field'])) 2558 { 2559 $sql .= " AND `".$entity_type_data['table']."`.`" . $entity_type_data['disable_field'] . "` != '1'"; 2560 } 2561 // Exclude disabled/ignored devices (if not device entity) 2562 if ($entity_type != 'device') 2563 { 2564 $sql .= " AND `devices`.`disabled` != '1'"; 2565 $sql .= " AND `devices`.`ignore` != '1'"; 2566 } 2567 } 2568 2569 if (isset($entity_type_data['deleted_field'])) { 2570 $sql .= " AND `".$entity_type_data['table']."`.`" . $entity_type_data['deleted_field'] . "` != '1'"; 2571 } 2572 2573 //r($sql); 2574 2575 return $sql; 2576} 2577 2578function parse_qb_rules($entity_type, $rules, $ignore = FALSE) 2579{ 2580 global $config; 2581 2582 $entity_type_data = entity_type_translate_array($entity_type); 2583 $entity_attribs = $config['entities'][$entity_type]['attribs']; 2584 $parts = array(); 2585 foreach ($rules['rules'] as $rule) 2586 { 2587 2588 if (is_array($rule['rules'])) 2589 { 2590 2591 $parts[] = parse_qb_rules($entity_type, $rule); 2592 2593 } else { 2594 2595 //print_r($rule); 2596 2597 list($table, $field) = explode('.', $rule['field']); 2598 2599 if ($table == 'entity' || $table == $entity_type) 2600 { 2601 $table_type_data = $entity_type_data; 2602 } else { 2603 // This entity can be not same as main entity! 2604 $table_type_data = entity_type_translate_array($table); 2605 } 2606 2607 // Pre Transform value according to DB field (see port ARP/MAC) 2608 if (isset($entity_attribs[$field]['transformations'])) 2609 { 2610 $rule['value'] = string_transform($rule['value'], $entity_attribs[$field]['transformations']); 2611 } 2612 2613 $part = ''; 2614 // Check if field is measured entity 2615 $field_measured = isset($entity_attribs[$field]['measured_type']) && // Attrib have measured type param 2616 isset($config['entities'][$entity_attribs[$field]['measured_type']]); // And this entity type exist 2617 2618 if (isset($entity_attribs[$field]['function']) && 2619 function_exists($entity_attribs[$field]['function'])) 2620 { 2621 // Pass original rule value, which translated to entity_id(s) by function call 2622 $function_args = array($entity_type, $rule['value']); 2623 $rule['value'] = call_user_func_array($entity_attribs[$field]['function'], $function_args); 2624 // Override $field by entity_id 2625 $rule['field_quoted'] = '`'.$entity_type_data['table'].'`.`'.$entity_type_data['table_fields']['id'].'`'; 2626 2627 //print_vars($function_args); 2628 //print_vars($rule['value']); 2629 } 2630 else if ($field_measured) { 2631 // This attrib is measured entity 2632 //$measured_type = $entity_attribs[$field]['measured_type']; 2633 //$measured_type_data = entity_type_translate_array($measured_type); 2634 2635 switch ($entity_attribs[$field]['values']) { 2636 case 'measured_group': 2637 // When values used as measured group, convert it to entity ids 2638 //logfile('groups.log', 'passed value: '.var_export($rule['value'], TRUE)); /// DEVEL 2639 $group_ids = !is_array($rule['value']) ? explode(',', $rule['value']) : $rule['value']; 2640 $rule['value'] = get_group_entities($group_ids); 2641 //logfile('groups.log', 'groups value: '.var_export($rule['value'], TRUE)); /// DEVEL 2642 break; 2643 default: 2644 //$rule['field_quoted'] = '`'.$table_type_data['table'].'`.`'.$field.'`'; 2645 } 2646 // Override $field by measured entity_id 2647 $rule['field_quoted'] = '`'.$table_type_data['table'].'`.`'.$entity_type_data['table_fields']['measured_id'].'`'; 2648 //logfile('groups.log', 'value: '.var_export($rule['value'], TRUE)); /// DEVEL 2649 //logfile('groups.log', 'field: '.$rule['field_quoted']); /// DEVEL 2650 2651 } 2652 else if (isset($entity_attribs[$field]['table'])) 2653 { 2654 // This attrib specifies a table name (used for oid, since there is no parent) 2655 $rule['field_quoted'] = '`'.$entity_attribs[$field]['table'].'`.`'.$field.'`'; 2656 } 2657 else if (!isset($entity_attribs[$field]) 2658 && isset($config['entities'][$entity_type]['parent_type']) 2659 && isset($config['entities'][$config['entities'][$entity_type]['parent_type']]['attribs'][$field])) 2660 { 2661 // This attrib does not exist on this entity && this entity has a parent && this attrib exists on the parent 2662 $rule['field_quoted'] = '`'.$config['entities'][$config['entities'][$entity_type]['parent_type']]['table'].'`.`'.$field.'`'; 2663 2664 } else { 2665 2666 //$rule['field_quoted'] = '`'.$field.'`'; 2667 // Always use full table.column, for do not get errors ambiguous (after JOINs) 2668 2669 $rule['field_quoted'] = '`'.$table_type_data['table'].'`.`'.$field.'`'; 2670 } 2671 2672 2673 $operator_negative = FALSE; // Need for measured 2674 switch ($rule['operator']) 2675 { 2676 case 'ge': 2677 $part = ' ' . $rule['field_quoted'] . " >= '" . dbEscape($rule['value']) . "'"; 2678 break; 2679 case 'le': 2680 $part = ' ' . $rule['field_quoted'] . " <= '" . dbEscape($rule['value']) . "'"; 2681 break; 2682 case 'gt': 2683 $part = ' ' . $rule['field_quoted'] . " > '" . dbEscape($rule['value']) . "'"; 2684 break; 2685 case 'lt': 2686 $part = ' ' . $rule['field_quoted'] . " < '" . dbEscape($rule['value']) . "'"; 2687 break; 2688 case 'notequals': 2689 $operator_negative = TRUE; 2690 $part = ' ' . $rule['field_quoted'] . " != '" . dbEscape($rule['value']) . "'"; 2691 break; 2692 case 'equals': 2693 $part = ' ' . $rule['field_quoted'] . " = '" . dbEscape($rule['value']) . "'"; 2694 break; 2695 case 'match': 2696 switch ($field) 2697 { 2698 case 'group': 2699 $group = get_group_by_name($rule['value']); 2700 if ($group['entity_type'] == $table) { 2701 $values = get_group_entities($group['group_id']); 2702 $part = generate_query_values($values, ($table == "device" ? "devices.device_id" : $table_type_data['table'].'.'.$entity_type_data['table_fields']['id']), NULL, FALSE); 2703 } 2704 break; 2705 default: 2706 $rule['value'] = str_replace('*', '%', $rule['value']); 2707 $rule['value'] = str_replace('?', '_', $rule['value']); 2708 $part = ' IFNULL(' . $rule['field_quoted'] . ', "") LIKE' . " '" . dbEscape($rule['value']) . "'"; 2709 break; 2710 } 2711 break; 2712 case 'notmatch': 2713 $operator_negative = TRUE; 2714 switch ($field) 2715 { 2716 case 'group': 2717 $group = get_group_by_name($rule['value']); 2718 if ($group['entity_type'] == $table) { 2719 $values = get_group_entities($group['group_id']); 2720 $part = generate_query_values($values, ($table == "device" ? "devices.device_id" : $table_type_data['table'].'.'.$entity_type_data['table_fields']['id']), '!=', FALSE); 2721 } 2722 break; 2723 default: 2724 $rule['value'] = str_replace('*', '%', $rule['value']); 2725 $rule['value'] = str_replace('?', '_', $rule['value']); 2726 $part = ' IFNULL(' . $rule['field_quoted'] . ', "") NOT LIKE' . " '" . dbEscape($rule['value']) . "'"; 2727 break; 2728 } 2729 break; 2730 case 'regexp': 2731 $part = ' IFNULL(' . $rule['field_quoted'] . ', "") REGEXP' . " '" . dbEscape($rule['value']) . "'"; 2732 break; 2733 case 'notregexp': 2734 $operator_negative = TRUE; 2735 $part = ' IFNULL(' . $rule['field_quoted'] . ', "") NOT REGEXP' . " '" . dbEscape($rule['value']) . "'"; 2736 break; 2737 case 'isnull': 2738 $part = ' ' .$rule['field_quoted'] . ' IS NULL'; 2739 break; 2740 case 'isnotnull': 2741 $part = ' ' .$rule['field_quoted'] . ' IS NOT NULL'; 2742 break; 2743 case 'in': 2744 //print_vars($field); 2745 //print_vars($rule); 2746 switch ($field) 2747 { 2748 case 'group_id': 2749 $values = get_group_entities($rule['value']); 2750 $part = generate_query_values($values, ($table == "device" ? "devices.device_id" : $table_type_data['table'].'.'.$entity_type_data['table_fields']['id']), NULL, FALSE); 2751 break; 2752 default: 2753 $part = generate_query_values($rule['value'], $rule['field_quoted'], NULL, FALSE); 2754 break; 2755 } 2756 //print_vars($parts); 2757 break; 2758 case 'notin': 2759 $operator_negative = TRUE; 2760 switch ($field) { 2761 case 'group_id': 2762 $values = get_group_entities($rule['value']); 2763 $part = generate_query_values($values, ($table == "device" ? "devices.device_id" : $table_type_data['table'].'.'.$entity_type_data['table_fields']['id']), '!=', FALSE); 2764 break; 2765 default; 2766 $part = generate_query_values($rule['value'], $rule['field_quoted'], '!=', FALSE); 2767 break; 2768 } 2769 break; 2770 } 2771 // For measured field append measured 2772 if ($field_measured && strlen($part)) { 2773 $measured_type = $entity_attribs[$field]['measured_type']; 2774 $part = ' (`'.$table_type_data['table'].'`.`'.$entity_type_data['table_fields']['measured_type'] . 2775 "` = '" . dbEscape($measured_type) . "' AND (" . $part . '))'; 2776 // For negative rule operators append all entities without measured type field 2777 if ($operator_negative) { 2778 $part = ' (`'.$table_type_data['table'].'`.`'.$entity_type_data['table_fields']['measured_type'] . '` IS NULL OR' . $part . ')'; 2779 } 2780 } 2781 //if ($field_measured) { logfile('groups.log', $part); } /// DEVEL 2782 if (strlen($part)) 2783 { 2784 $parts[] = $part; 2785 } 2786 2787 } 2788 } 2789 2790 $sql = '(' . implode(" " . $rules['condition'], $parts) . ')'; 2791 2792 //print_vars($parts); 2793 //print_vars($sql); 2794 //if ($field_measured) { logfile('groups.log', $sql); } 2795 //logfile('groups.log', $sql); /// DEVEL 2796 print_debug_vars($sql); 2797 2798 return $sql; 2799 2800} 2801 2802function migrate_assoc_rules($entry) 2803{ 2804 2805 $entity_type = $entry['entity_type']; 2806 2807 $ruleset = array(); 2808 $ruleset['condition'] = 'OR'; 2809 $ruleset['valid'] = 'true'; 2810 2811 foreach ($entry['assocs'] as $assoc) 2812 { 2813 2814 $x = array(); 2815 $x['condition'] = 'AND'; 2816 2817 $a = array('device' => $assoc['device_attribs'], 'entity' => $assoc['entity_attribs']); 2818 2819 foreach ($a as $type => $rules) 2820 { 2821 2822 foreach ($rules as $rule) 2823 { 2824 2825 if ($rule['attrib'] != '*') 2826 { 2827 2828 if ($type == 'device' || $entity_type == 'device') 2829 { 2830 $def = $GLOBALS['config']['entities'][$type]['attribs'][$rule['attrib']]; 2831 } else { 2832 $def = $GLOBALS['config']['entities'][$entity_type]['attribs'][$rule['attrib']]; 2833 } 2834 2835 $e = array(); 2836 $e['id'] = ($type == 'device' ? 'device.' : 'entity.') . $rule['attrib']; 2837 $e['field'] = $e['id']; 2838 $e['type'] = $def['type']; 2839 $e['value'] = $rule['value']; 2840 2841 switch ($rule['condition']) { 2842 case 'ge': 2843 case '>=': 2844 $e['operator'] = "ge"; 2845 break; 2846 case 'le': 2847 case '<=': 2848 $e['operator'] = "le"; 2849 break; 2850 case 'gt': 2851 case 'greater': 2852 case '>': 2853 $e['operator'] = "gt"; 2854 break; 2855 case 'lt': 2856 case 'less': 2857 case '<': 2858 $e['operator'] = "lt"; 2859 break; 2860 case 'notequals': 2861 case 'isnot': 2862 case 'ne': 2863 case '!=': 2864 $e['operator'] = "notequals"; 2865 break; 2866 case 'equals': 2867 case 'eq': 2868 case 'is': 2869 case '==': 2870 case '=': 2871 $e['operator'] = "equals"; 2872 break; 2873 case 'match': 2874 case 'matches': 2875 //$e['value'] = str_replace('*', '%', $e['value']); 2876 //$e['value'] = str_replace('?', '_', $e['value']); 2877 $e['operator'] = "match"; 2878 break; 2879 case 'notmatches': 2880 case 'notmatch': 2881 case '!match': 2882 //$e['value'] = str_replace('*', '%', $e['value']); 2883 //$e['value'] = str_replace('?', '_', $e['value']); 2884 $e['operator'] = "notmatch"; 2885 break; 2886 case 'regexp': 2887 case 'regex': 2888 $e['operator'] = "regexp"; 2889 break; 2890 case 'notregexp': 2891 case 'notregex': 2892 case '!regexp': 2893 case '!regex': 2894 $e['operator'] = "notregexp"; 2895 break; 2896 case 'in': 2897 case 'list': 2898 $e['value'] = explode(',', $e['value']); 2899 $e['operator'] = "in"; 2900 break; 2901 case '!in': 2902 case '!list': 2903 case 'notin': 2904 case 'notlist': 2905 $e['value'] = explode(',', $e['value']); 2906 $e['operator'] = "notin"; 2907 break; 2908 case 'include': 2909 case 'includes': 2910 switch ($rule['attrib']) { 2911 case 'group': 2912 $e['operator'] = "match"; 2913 $e['type'] = 'text'; 2914 break; 2915 case 'group_id': 2916 $e['operator'] = "in"; 2917 $e['value'] = explode(',', $e['value']); 2918 $e['type'] = 'select'; 2919 break; 2920 2921 } 2922 break; 2923 } 2924 2925 if (isset($def['values']) && 2926 in_array($e['operator'], array("equals", "notequals", "match", "notmatch", "regexp", "notregexp"))) 2927 { 2928 $e['id'] .= ".free"; 2929 } 2930 2931 if (in_array($e['operator'], array('in', 'notin'))) 2932 { 2933 $e['input'] = 'select'; 2934 } 2935 else if ($def['type'] == 'integer') 2936 { 2937 $e['input'] = 'number'; 2938 } else { 2939 $e['input'] = 'text'; 2940 } 2941 2942 $x['rules'][] = $e; 2943 2944 } 2945 2946 } 2947 2948 } 2949 $ruleset['rules'][] = $x; 2950 } 2951 2952 // Collapse group if there is only one entry. 2953 if (count($ruleset['rules']) == 1) 2954 { 2955 $ruleset['rules'] = $ruleset['rules'][0]['rules']; 2956 $ruleset['condition'] = 'AND'; 2957 } 2958 2959 if (count($ruleset['rules']) < 1) 2960 { 2961 $ruleset['rules'][] = array('id' => 'device.hostname', 'field' => 'device.hostname', 'type' => 'string', 'value' => '*', 'operator' => 'match', 'input' => 'text'); 2962 } 2963 2964 return $ruleset; 2965 2966} 2967 2968function render_qb_rules($entity_type, $rules) 2969{ 2970 2971 $parts = array(); 2972 2973 $entity_type_data = entity_type_translate_array($entity_type); 2974 2975 foreach ($rules['rules'] as $rule) 2976 { 2977 if (is_array($rule['rules'])) 2978 { 2979 $parts[] = render_qb_rules($entity_type, $rule); 2980 2981 } else { 2982 2983 list($table, $field) = explode('.', $rule['field']); 2984 2985 if ($table == "device") 2986 { 2987 2988 } elseif ($table == "entity") { 2989 2990 $table = $entity_type; 2991 2992 } elseif ($table == "parent") { 2993 2994 $table = $entity_type_data['parent_type']; 2995 2996 } 2997 2998 // Boolean stored as bool object, can not be displayed 2999 if ($rule['type'] == 'boolean') 3000 { 3001 $rule['value'] = intval($rule['value']); 3002 } 3003 3004 $parts[] = "<code style='margin: 1px'>$table.$field " . $rule['operator'] . " " . (is_array($rule['value']) ? implode($rule['value'], 3005 '|') : $rule['value']) . "</code>"; 3006 } 3007 3008 } 3009 3010 $part = implode('' . ($rules['condition'] == "AND" ? ' <span class="label label-primary">AND</span> ' : ' <span class="label label-info">OR</span> ') . '',$parts); 3011 3012 if(count($parts) > 1) 3013 { 3014 $part = '<b style="font-size: 1.2em">(</b>'.$part.'<b>)</b>'; 3015 } 3016 3017 3018 return $part; 3019} 3020 3021// EOF 3022