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 = "&infin;";
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 = "&infin;";
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, '&lt;').replace(/>/g, '&gt;')
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