Passed
Push — master ( 698671...851994 )
by Tony
40:30 queued 28:04
created

RunAlerts::extTransports()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 42
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 27
c 1
b 0
f 0
dl 0
loc 42
rs 8.5546
cc 7
nc 24
nop 1
1
<?php
2
/*
3
 * RunAlerts.php
4
 * 
5
 * Copyright (C) 2014 Daniel Preussker <[email protected]>
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 * Original Code:
20
 * @author Daniel Preussker <[email protected]>
21
 * @copyright 2014 f0o, LibreNMS
22
 * @license GPL
23
 * @package LibreNMS
24
 * @subpackage Alerts
25
 *
26
 * Modified by:
27
 * @author Heath Barnhart <[email protected]>
28
 * 
29
 */
30
31
namespace LibreNMS\Alert;
32
33
use App\Models\DevicePerf;
34
use LibreNMS\Config;
35
use LibreNMS\Util\Time;
36
use Log;
37
38
class RunAlerts
39
{
40
41
    /**
42
     * Populate variables
43
     * @param string  $txt  Text with variables
44
     * @param boolean $wrap Wrap variable for text-usage (default: true)
45
     * @return string
46
     */
47
    public function populate($txt, $wrap = true)
48
    {
49
        preg_match_all('/%([\w\.]+)/', $txt, $m);
50
        foreach ($m[1] as $tmp) {
51
            $orig = $tmp;
52
            $rep  = false;
53
            if ($tmp == 'key' || $tmp == 'value') {
54
                $rep = '$'.$tmp;
55
            } else {
56
                if (strstr($tmp, '.')) {
57
                    $tmp = explode('.', $tmp, 2);
58
                    $pre = '$'.$tmp[0];
59
                    $tmp = $tmp[1];
60
                } else {
61
                    $pre = '$obj';
62
                }
63
64
                $rep = $pre."['".str_replace('.', "']['", $tmp)."']";
65
                if ($wrap) {
66
                    $rep = '{'.$rep.'}';
67
                }
68
            }
69
70
            $txt = str_replace('%'.$orig, $rep, $txt);
71
        }
72
        return $txt;
73
    }
74
75
    /**
76
     * Describe Alert
77
     * @param array $alert Alert-Result from DB
78
     * @return array|boolean
79
     */
80
    public function describeAlert($alert)
81
    {
82
        $obj         = array();
83
        $i           = 0;
84
        $device      = dbFetchRow('SELECT hostname, sysName, sysDescr, sysContact, os, type, ip, hardware, version, purpose, notes, uptime, status, status_reason, locations.location FROM devices LEFT JOIN locations ON locations.id = devices.location_id WHERE device_id = ?', array($alert['device_id']));
85
        $attribs     = get_dev_attribs($alert['device_id']);
86
87
        $obj['hostname']      = $device['hostname'];
88
        $obj['sysName']       = $device['sysName'];
89
        $obj['sysDescr']      = $device['sysDescr'];
90
        $obj['sysContact']    = $device['sysContact'];
91
        $obj['os']            = $device['os'];
92
        $obj['type']          = $device['type'];
93
        $obj['ip']            = inet6_ntop($device['ip']);
94
        $obj['hardware']      = $device['hardware'];
95
        $obj['version']       = $device['version'];
96
        $obj['location']      = $device['location'];
97
        $obj['uptime']        = $device['uptime'];
98
        $obj['uptime_short']  = Time::formatInterval($device['uptime'], 'short');
99
        $obj['uptime_long']   = Time::formatInterval($device['uptime']);
100
        $obj['description']   = $device['purpose'];
101
        $obj['notes']         = $device['notes'];
102
        $obj['alert_notes']   = $alert['note'];
103
        $obj['device_id']     = $alert['device_id'];
104
        $obj['rule_id']       = $alert['rule_id'];
105
        $obj['status']        = $device['status'];
106
        $obj['status_reason'] = $device['status_reason'];
107
        if (can_ping_device($attribs)) {
108
            $ping_stats = DevicePerf::where('device_id', $alert['device_id'])->latest('timestamp')->first();
109
            $obj['ping_timestamp'] = $ping_stats->template;
0 ignored issues
show
Bug introduced by
The property template does not seem to exist on App\Models\DevicePerf. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
110
            $obj['ping_loss']      = $ping_stats->loss;
111
            $obj['ping_min']       = $ping_stats->min;
112
            $obj['ping_max']       = $ping_stats->max;
113
            $obj['ping_avg']       = $ping_stats->avg;
114
            $obj['debug']          = json_decode($ping_stats->debug, true);
115
        }
116
        $extra               = $alert['details'];
117
118
        $tpl                 = new Template;
119
        $template            = $tpl->getTemplate($obj);
120
121
        if ($alert['state'] >= 1) {
122
            $obj['title'] = $template->title ?: 'Alert for device '.$device['hostname'].' - '.($alert['name'] ? $alert['name'] : $alert['rule']);
123
            if ($alert['state'] == 2) {
124
                $obj['title'] .= ' got acknowledged';
125
            } elseif ($alert['state'] == 3) {
126
                $obj['title'] .= ' got worse';
127
            } elseif ($alert['state'] == 4) {
128
                $obj['title'] .= ' got better';
129
            }
130
131
            foreach ($extra['rule'] as $incident) {
132
                $i++;
133
                $obj['faults'][$i] = $incident;
134
                $obj['faults'][$i]['string'] = null;
135
                foreach ($incident as $k => $v) {
136
                    if (!empty($v) && $k != 'device_id' && (stristr($k, 'id') || stristr($k, 'desc') || stristr($k, 'msg')) && substr_count($k, '_') <= 1) {
137
                        $obj['faults'][$i]['string'] .= $k.' = '.$v.'; ';
138
                    }
139
                }
140
            }
141
            $obj['elapsed'] = $this->timeFormat(time() - strtotime($alert['time_logged']));
142
            if (!empty($extra['diff'])) {
143
                $obj['diff'] = $extra['diff'];
144
            }
145
        } elseif ($alert['state'] == 0) {
146
            // Alert is now cleared
147
            $id = dbFetchRow('SELECT alert_log.id,alert_log.time_logged,alert_log.details FROM alert_log WHERE alert_log.state != 2 && alert_log.state != 0 && alert_log.rule_id = ? && alert_log.device_id = ? && alert_log.id < ? ORDER BY id DESC LIMIT 1', array($alert['rule_id'], $alert['device_id'], $alert['id']));
148
            if (empty($id['id'])) {
149
                return false;
150
            }
151
152
            $extra = [];
153
            if (!empty($id['details'])) {
154
                $extra = json_decode(gzuncompress($id['details']), true);
155
            }
156
157
            // Reset count to 0 so alerts will continue
158
            $extra['count'] = 0;
159
            dbUpdate(array('details' => gzcompress(json_encode($id['details']), 9)), 'alert_log', 'id = ?', array($alert['id']));
160
161
            $obj['title'] = $template->title_rec ?: 'Device '.$device['hostname'].' recovered from '.($alert['name'] ? $alert['name'] : $alert['rule']);
162
            $obj['elapsed'] = $this->timeFormat(strtotime($alert['time_logged']) - strtotime($id['time_logged']));
163
            $obj['id']      = $id['id'];
164
            foreach ($extra['rule'] as $incident) {
165
                $i++;
166
                $obj['faults'][$i] = $incident;
167
                foreach ($incident as $k => $v) {
168
                    if (!empty($v) && $k != 'device_id' && (stristr($k, 'id') || stristr($k, 'desc') || stristr($k, 'msg')) && substr_count($k, '_') <= 1) {
169
                        $obj['faults'][$i]['string'] .= $k.' => '.$v.'; ';
170
                    }
171
                }
172
            }
173
        } else {
174
            return 'Unknown State';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'Unknown State' returns the type string which is incompatible with the documented return type array|boolean.
Loading history...
175
        }//end if
176
        $obj['builder']   = $alert['builder'];
177
        $obj['uid']       = $alert['id'];
178
        $obj['alert_id']  = $alert['alert_id'];
179
        $obj['severity']  = $alert['severity'];
180
        $obj['rule']      = $alert['rule'];
181
        $obj['name']      = $alert['name'];
182
        $obj['timestamp'] = $alert['time_logged'];
183
        $obj['contacts']  = $extra['contacts'];
184
        $obj['state']     = $alert['state'];
185
        $obj['template']  = $template;
186
        return $obj;
187
    }
188
189
    /**
190
     * Format Elapsed Time
191
     * @param integer $secs Seconds elapsed
192
     * @return string
193
     */
194
    public function timeFormat($secs)
195
    {
196
        $bit = array(
197
            'y' => $secs / 31556926 % 12,
198
            'w' => $secs / 604800 % 52,
199
            'd' => $secs / 86400 % 7,
200
            'h' => $secs / 3600 % 24,
201
            'm' => $secs / 60 % 60,
202
            's' => $secs % 60,
203
        );
204
        $ret = array();
205
        foreach ($bit as $k => $v) {
206
            if ($v > 0) {
207
                $ret[] = $v.$k;
208
            }
209
        }
210
211
        if (empty($ret)) {
212
            return 'none';
213
        }
214
215
        return join(' ', $ret);
216
    }
217
218
    public function clearStaleAlerts()
219
    {
220
        $sql = "SELECT `alerts`.`id` AS `alert_id`, `devices`.`hostname` AS `hostname` FROM `alerts` LEFT JOIN `devices` ON `alerts`.`device_id`=`devices`.`device_id`  RIGHT JOIN `alert_rules` ON `alerts`.`rule_id`=`alert_rules`.`id` WHERE `alerts`.`state`!=0 AND `devices`.`hostname` IS NULL";
221
        foreach (dbFetchRows($sql) as $alert) {
222
            if (empty($alert['hostname']) && isset($alert['alert_id'])) {
223
                dbDelete('alerts', '`id` = ?', array($alert['alert_id']));
224
                echo "Stale-alert: #{$alert['alert_id']}" . PHP_EOL;
225
            }
226
        }
227
    }
228
229
    /**
230
     * Re-Validate Rule-Mappings
231
     * @param integer $device_id Device-ID
232
     * @param integer $rule   Rule-ID
233
     * @return boolean
234
     */
235
    public function isRuleValid($device_id, $rule)
236
    {
237
        global $rulescache;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
238
        if (empty($rulescache[$device_id]) || !isset($rulescache[$device_id])) {
239
            foreach (AlertUtil::GetRules($device_id) as $chk) {
240
                $rulescache[$device_id][$chk['id']] = true;
241
            }
242
        }
243
244
        if ($rulescache[$device_id][$rule] === true) {
245
            return true;
246
        }
247
248
        return false;
249
    }
250
251
252
    /**
253
     * Issue Alert-Object
254
     * @param array $alert
255
     * @return boolean
256
     */
257
    public function issueAlert($alert)
258
    {
259
        if (dbFetchCell('SELECT attrib_value FROM devices_attribs WHERE attrib_type = "disable_notify" && device_id = ?', array($alert['device_id'])) == '1') {
260
            return true;
261
        }
262
263
        if (Config::get('alert.fixed-contacts') == false) {
264
            if (empty($alert['query'])) {
265
                $alert['query'] = AlertDB::genSQL($alert['rule'], $alert['builder']);
266
            }
267
            $sql = $alert['query'];
268
            $qry = dbFetchRows($sql, array($alert['device_id']));
269
            $alert['details']['contacts'] = AlertUtil::GetContacts($qry);
270
        }
271
272
        $obj = $this->describeAlert($alert);
273
        if (is_array($obj)) {
0 ignored issues
show
introduced by
The condition is_array($obj) is always true.
Loading history...
274
            echo 'Issuing Alert-UID #'.$alert['id'].'/'.$alert['state'].':' . PHP_EOL;
275
            $this->extTransports($obj);
276
277
            echo "\r\n";
278
        }
279
280
        return true;
281
    }
282
283
284
    /**
285
     * Issue ACK notification
286
     * @return void
287
     */
288
    public function runAcks()
289
    {
290
291
        foreach ($this->loadAlerts('alerts.state = 2 && alerts.open = 1') as $alert) {
292
            $this->issueAlert($alert);
293
            dbUpdate(array('open' => 0), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id']));
294
        }
295
    }
296
297
    /**
298
     * Run Follow-Up alerts
299
     * @return void
300
     */
301
    public function runFollowUp()
302
    {
303
        foreach ($this->loadAlerts('alerts.state > 0 && alerts.open = 0') as $alert) {
304
            if ($alert['state'] != 2 || ($alert['info']['until_clear'] === false)) {
305
                $rextra = json_decode($alert['extra'], true);
306
                if ($rextra['invert']) {
307
                    continue;
308
                }
309
310
                if (empty($alert['query'])) {
311
                    $alert['query'] = AlertDB::genSQL($alert['rule'], $alert['builder']);
312
                }
313
                $chk = dbFetchRows($alert['query'], array($alert['device_id']));
314
                //make sure we can json_encode all the datas later
315
                $cnt = count($chk);
316
                for ($i = 0; $i < $cnt; $i++) {
317
                    if (isset($chk[$i]['ip'])) {
318
                        $chk[$i]['ip'] = inet6_ntop($chk[$i]['ip']);
319
                    }
320
                }
321
                $o = sizeof($alert['details']['rule']);
322
                $n = sizeof($chk);
323
                $ret = 'Alert #' . $alert['id'];
324
                $state = 0;
325
                if ($n > $o) {
326
                    $ret .= ' Worsens';
327
                    $state = 3;
328
                    $alert['details']['diff'] = array_diff($chk, $alert['details']['rule']);
329
                } elseif ($n < $o) {
330
                    $ret .= ' Betters';
331
                    $state = 4;
332
                    $alert['details']['diff'] = array_diff($alert['details']['rule'], $chk);
333
                }
334
335
                if ($state > 0 && $n > 0) {
336
                    $alert['details']['rule'] = $chk;
337
                    if (dbInsert(array(
338
                        'state' => $state,
339
                        'device_id' => $alert['device_id'],
340
                        'rule_id' => $alert['rule_id'],
341
                        'details' => gzcompress(json_encode($alert['details']), 9)
342
                    ), 'alert_log')) {
343
                        dbUpdate(array('state' => $state, 'open' => 1, 'alerted' => 1), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id']));
344
                    }
345
346
                    echo $ret . ' (' . $o . '/' . $n . ")\r\n";
347
                }
348
            }
349
        }
350
    }
351
352
    public function loadAlerts($where)
353
    {
354
        $alerts = [];
355
        foreach (dbFetchRows("SELECT alerts.id, alerts.device_id, alerts.rule_id, alerts.state, alerts.note, alerts.info FROM alerts WHERE $where") as $alert_status) {
356
            $alert = dbFetchRow(
357
                'SELECT alert_log.id,alert_log.rule_id,alert_log.device_id,alert_log.state,alert_log.details,alert_log.time_logged,alert_rules.rule,alert_rules.severity,alert_rules.extra,alert_rules.name,alert_rules.query,alert_rules.builder FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 ORDER BY alert_log.id DESC LIMIT 1',
358
                array($alert_status['device_id'], $alert_status['rule_id'])
359
            );
360
361
            if (empty($alert['rule_id']) || !$this->isRuleValid($alert_status['device_id'], $alert_status['rule_id'])) {
362
                echo 'Stale-Rule: #' . $alert_status['rule_id'] . '/' . $alert_status['device_id'] . "\r\n";
363
                // Alert-Rule does not exist anymore, let's remove the alert-state.
364
                dbDelete('alerts', 'rule_id = ? && device_id = ?', [$alert_status['rule_id'], $alert_status['device_id']]);
365
            } else {
366
                $alert['alert_id'] = $alert_status['id'];
367
                $alert['state'] = $alert_status['state'];
368
                $alert['note'] = $alert_status['note'];
369
                if (!empty($alert['details'])) {
370
                    $alert['details'] = json_decode(gzuncompress($alert['details']), true);
371
                }
372
                $alert['info'] = json_decode($alert_status['info'], true);
373
                $alerts[] = $alert;
374
            }
375
        }
376
377
        return $alerts;
378
    }
379
380
    /**
381
     * Run all alerts
382
     * @return void
383
     */
384
    public function runAlerts()
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
385
    {
386
        foreach ($this->loadAlerts('alerts.state != 2 && alerts.open = 1') as $alert) {
387
            $noiss            = false;
388
            $noacc            = false;
389
            $updet            = false;
390
            $rextra           = json_decode($alert['extra'], true);
391
            if (!isset($rextra['recovery'])) {
392
                // backwards compatibility check
393
                $rextra['recovery'] = true;
394
            }
395
396
            $chk = dbFetchRow('SELECT alerts.alerted,devices.ignore,devices.disabled FROM alerts,devices WHERE alerts.device_id = ? && devices.device_id = alerts.device_id && alerts.rule_id = ?', array($alert['device_id'], $alert['rule_id']));
397
398
            if ($chk['alerted'] == $alert['state']) {
399
                $noiss = true;
400
            }
401
402
            $tolerence_window = Config::get('alert.tolerance_window');
403
            if (!empty($rextra['count']) && empty($rextra['interval'])) {
404
                // This check below is for compat-reasons
405
                if (!empty($rextra['delay'])) {
406
                    if ((time() - strtotime($alert['time_logged']) + $tolerence_window) < $rextra['delay'] || (!empty($alert['details']['delay']) && (time() - $alert['details']['delay'] + $tolerence_window) < $rextra['delay'])) {
407
                        continue;
408
                    } else {
409
                        $alert['details']['delay'] = time();
410
                        $updet = true;
411
                    }
412
                }
413
414
                if ($alert['state'] == 1 && !empty($rextra['count']) && ($rextra['count'] == -1 || $alert['details']['count']++ < $rextra['count'])) {
415
                    if ($alert['details']['count'] < $rextra['count']) {
416
                        $noacc = true;
417
                    }
418
419
                    $updet = true;
420
                    $noiss = false;
421
                }
422
            } else {
423
                // This is the new way
424
                if (!empty($rextra['delay']) && (time() - strtotime($alert['time_logged']) + $tolerence_window) < $rextra['delay']) {
425
                    continue;
426
                }
427
428
                if (!empty($rextra['interval'])) {
429
                    if (!empty($alert['details']['interval']) && (time() - $alert['details']['interval'] + $tolerence_window) < $rextra['interval']) {
430
                        continue;
431
                    } else {
432
                        $alert['details']['interval'] = time();
433
                        $updet = true;
434
                    }
435
                }
436
437
                if (in_array($alert['state'], [1,3,4]) && !empty($rextra['count']) && ($rextra['count'] == -1 || $alert['details']['count']++ < $rextra['count'])) {
438
                    if ($alert['details']['count'] < $rextra['count']) {
439
                        $noacc = true;
440
                    }
441
442
                    $updet = true;
443
                    $noiss = false;
444
                }
445
            }
446
            if ($chk['ignore'] == 1 || $chk['disabled'] == 1) {
447
                $noiss = true;
448
                $updet = false;
449
                $noacc = false;
450
            }
451
452
            if (AlertUtil::IsMaintenance($alert['device_id']) > 0) {
453
                $noiss = true;
454
                $noacc = true;
455
            }
456
457
            if ($updet) {
458
                dbUpdate(array('details' => gzcompress(json_encode($alert['details']), 9)), 'alert_log', 'id = ?', array($alert['id']));
459
            }
460
461
            if (!empty($rextra['mute'])) {
462
                echo 'Muted Alert-UID #'.$alert['id']."\r\n";
463
                $noiss = true;
464
            }
465
466
            if ($this->isParentDown($alert['device_id'])) {
467
                $noiss = true;
468
                Log::event('Skipped alerts because all parent devices are down', $alert['device_id'], 'alert', 1);
469
            }
470
471
            if ($alert['state'] == 0 && $rextra['recovery'] == false) {
472
                // Rule is set to not send a recovery alert
473
                $noiss = true;
474
            }
475
476
            if (!$noiss) {
477
                $this->issueAlert($alert);
478
                dbUpdate(array('alerted' => $alert['state']), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id']));
479
            }
480
481
            if (!$noacc) {
482
                dbUpdate(array('open' => 0), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id']));
483
            }
484
        }
485
    }
486
487
488
    /**
489
     * Run external transports
490
     * @param array $obj Alert-Array
491
     * @return void
492
     */
493
    public function extTransports($obj)
494
    {
495
        $type  = new Template;
496
497
        // If alert transport mapping exists, override the default transports
498
        $transport_maps = AlertUtil::getAlertTransports($obj['alert_id']);
499
500
        if (!$transport_maps) {
501
            $transport_maps = AlertUtil::getDefaultAlertTransports();
502
        }
503
504
        // alerting for default contacts, etc
505
        if (Config::get('alert.transports.mail') === true && !empty($obj['contacts'])) {
506
            $transport_maps[] = [
507
                'transport_id' => null,
508
                'transport_type' => 'mail',
509
                'opts' => $obj,
510
            ];
511
        }
512
513
        foreach ($transport_maps as $item) {
514
            $class = 'LibreNMS\\Alert\\Transport\\'.ucfirst($item['transport_type']);
515
            if (class_exists($class)) {
516
                //FIXME remove Deprecated transport
517
                $transport_title = "Transport {$item['transport_type']}";
518
                $obj['transport'] = $item['transport_type'];
519
                $obj['transport_name'] = $item['transport_name'];
520
                $obj['alert']     = new AlertData($obj);
521
                $obj['title']     = $type->getTitle($obj);
522
                $obj['alert']['title'] = $obj['title'];
523
                $obj['msg']       = $type->getBody($obj);
524
                c_echo(" :: $transport_title => ");
525
                $instance = new $class($item['transport_id']);
526
                $tmp = $instance->deliverAlert($obj, $item['opts']);
527
                $this->alertLog($tmp, $obj, $obj['transport']);
528
                unset($instance);
529
                echo PHP_EOL;
530
            }
531
        }
532
533
        if (count($transport_maps) === 0) {
534
            echo 'No configured transports';
535
        }
536
    }
537
538
    // Log alert event
539
    public function alertLog($result, $obj, $transport)
540
    {
541
        $prefix = [
542
            0 => "recovery",
543
            1 => $obj['severity']." alert",
544
            2 => "acknowledgment"
545
        ];
546
        $prefix[3] = &$prefix[0];
547
        $prefix[4] = &$prefix[0];
548
        if ($result === true) {
549
            echo 'OK';
550
            Log::event('Issued ' . $prefix[$obj['state']] . " for rule '" . $obj['name'] . "' to transport '" . $transport . "'", $obj['device_id'], 'alert', 1);
551
        } elseif ($result === false) {
552
            echo 'ERROR';
553
            Log::event('Could not issue ' . $prefix[$obj['state']] . " for rule '" . $obj['name'] . "' to transport '" . $transport . "'", $obj['device_id'], null, 5);
554
        } else {
555
            echo "ERROR: $result\r\n";
556
            Log::event('Could not issue ' . $prefix[$obj['state']] . " for rule '" . $obj['name'] . "' to transport '" . $transport . "' Error: " . $result, $obj['device_id'], 'error', 5);
557
        }
558
        return;
559
    }
560
561
    /**
562
     * Check if a device's all parent are down
563
     * Returns true if all parents are down
564
     * @param int $device Device-ID
565
     * @return bool
566
     */
567
    public function isParentDown($device)
568
    {
569
        $parent_count = dbFetchCell("SELECT count(*) from `device_relationships` WHERE `child_device_id` = ?", array($device));
570
        if (!$parent_count) {
571
            return false;
572
        }
573
574
575
        $down_parent_count = dbFetchCell("SELECT count(*) from devices as d LEFT JOIN devices_attribs as a ON d.device_id=a.device_id LEFT JOIN device_relationships as r ON d.device_id=r.parent_device_id WHERE d.status=0 AND d.ignore=0 AND d.disabled=0 AND r.child_device_id=? AND (d.status_reason='icmp' OR (a.attrib_type='override_icmp_disable' AND a.attrib_value=true))", array($device));
576
        if ($down_parent_count == $parent_count) {
577
            return true;
578
        }
579
580
        return false;
581
    }
582
}
583