1 | <?php |
||
2 | /* |
||
3 | * Copyright (C) 2015 Daniel Preussker <[email protected]> |
||
4 | * This program is free software: you can redistribute it and/or modify |
||
5 | * it under the terms of the GNU General Public License as published by |
||
6 | * the Free Software Foundation, either version 3 of the License, or |
||
7 | * (at your option) any later version. |
||
8 | * |
||
9 | * This program is distributed in the hope that it will be useful, |
||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the |
||
12 | * GNU General Public License for more details. |
||
13 | * |
||
14 | * You should have received a copy of the GNU General Public License |
||
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
16 | */ |
||
17 | |||
18 | /** |
||
19 | * Device-Grouping |
||
20 | * @author Daniel Preussker <[email protected]> |
||
21 | * @author Tony Murray <[email protected]> |
||
22 | * @copyright 2016 f0o, murrant, LibreNMS |
||
23 | * @license GPL |
||
24 | * @package LibreNMS |
||
25 | * @subpackage Devices |
||
26 | */ |
||
27 | |||
28 | use LibreNMS\Config; |
||
29 | |||
30 | /** |
||
31 | * Add a new device group |
||
32 | * @param $pattern |
||
33 | * @param $name |
||
34 | * @param $desc |
||
35 | * @return int|string |
||
36 | */ |
||
37 | function AddDeviceGroup($name, $desc, $pattern) |
||
38 | { |
||
39 | $group_id = dbInsert(array('name' => $name, 'desc' => $desc, 'pattern' => $pattern), 'device_groups'); |
||
40 | if ($group_id) { |
||
41 | UpdateDeviceGroup($group_id); |
||
42 | } |
||
43 | return $group_id; |
||
44 | } |
||
45 | |||
46 | /** |
||
47 | * Update a device group |
||
48 | * @param $group_id |
||
49 | * @param $pattern |
||
50 | * @param $name |
||
51 | * @param $desc |
||
52 | * @return bool |
||
53 | */ |
||
54 | function EditDeviceGroup($group_id, $name = null, $desc = null, $pattern = null) |
||
55 | { |
||
56 | $vars = array(); |
||
57 | if (!is_null($name)) { |
||
58 | $vars['name'] = $name; |
||
59 | } |
||
60 | if (!is_null($desc)) { |
||
61 | $vars['desc'] = $desc; |
||
62 | } |
||
63 | if (!is_null($pattern)) { |
||
64 | $vars['pattern'] = $pattern; |
||
65 | } |
||
66 | |||
67 | $success = dbUpdate($vars, 'device_groups', 'id=?', array($group_id)) >= 0; |
||
68 | |||
69 | if ($success) { |
||
70 | UpdateDeviceGroup($group_id); |
||
71 | } |
||
72 | return $success; |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Generate SQL from Group-Pattern |
||
77 | * @param string $pattern Pattern to generate SQL for |
||
78 | * @param string $search What to searchid for |
||
79 | * @param int $extra |
||
80 | * @return string sql to perform the search |
||
81 | */ |
||
82 | function GenGroupSQL($pattern, $search = '', $extra = 0) |
||
83 | { |
||
84 | $pattern = RunGroupMacros($pattern); |
||
85 | if ($pattern === false) { |
||
86 | return false; |
||
87 | } |
||
88 | |||
89 | if (starts_with($pattern, '%')) { |
||
90 | // v1 pattern |
||
91 | $tables = array(); |
||
92 | $words = explode(' ', $pattern); |
||
93 | foreach ($words as $word) { |
||
94 | if (starts_with($word, '%') && str_contains($word, '.')) { |
||
95 | list($table, $column) = explode('.', $word, 2); |
||
96 | $table = str_replace('%', '', $table); |
||
97 | $tables[] = mres(str_replace('(', '', $table)); |
||
98 | $pattern = str_replace($word, $table . '.' . $column, $pattern); |
||
99 | } |
||
100 | } |
||
101 | $tables = array_keys(array_flip($tables)); |
||
102 | } else { |
||
103 | // v2 pattern |
||
104 | $tables = getTablesFromPattern($pattern); |
||
105 | } |
||
106 | |||
107 | $pattern = rtrim($pattern, '&|'); |
||
108 | |||
109 | if ($tables[0] != 'devices' && dbFetchCell('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?', array($tables[0],'device_id')) != 1) { |
||
110 | //Our first table has no valid glue, prepend the 'devices' table to it! |
||
111 | array_unshift($tables, 'devices'); |
||
112 | $tables = array_unique($tables); // remove devices from later in the array if it exists |
||
113 | } |
||
114 | $x = sizeof($tables)-1; |
||
115 | $i = 0; |
||
116 | $join = ""; |
||
117 | while ($i < $x) { |
||
118 | if (isset($tables[$i+1])) { |
||
119 | $gtmp = ResolveGlues(array($tables[$i+1]), 'device_id'); |
||
120 | if ($gtmp === false) { |
||
121 | //Cannot resolve glue-chain. Rule is invalid. |
||
122 | return false; |
||
123 | } |
||
124 | $last = ""; |
||
125 | $qry = ""; |
||
126 | foreach ($gtmp as $glue) { |
||
127 | if (empty($last)) { |
||
128 | list($tmp,$last) = explode('.', $glue); |
||
129 | $qry .= $glue.' = '; |
||
130 | } else { |
||
131 | $parts = explode('.', $glue); |
||
132 | if (count($parts) == 3) { |
||
133 | list($tmp, $new, $last) = $parts; |
||
134 | } else { |
||
135 | list($tmp,$new) = $parts; |
||
136 | } |
||
137 | $qry .= $tmp.'.'.$last.' && '.$tmp.'.'.$new.' = '; |
||
138 | $last = $new; |
||
139 | } |
||
140 | if (!in_array($tmp, $tables)) { |
||
141 | $tables[] = $tmp; |
||
142 | } |
||
143 | } |
||
144 | $join .= "( ".$qry.$tables[0].".device_id ) && "; |
||
145 | } |
||
146 | $i++; |
||
147 | } |
||
148 | if ($extra === 1) { |
||
149 | $sql_extra = ",`devices`.*"; |
||
150 | } |
||
151 | if (!empty($search)) { |
||
152 | $search = str_replace("(", "", $tables[0]).'.'.$search. ' AND'; |
||
153 | } |
||
154 | if (!empty($join)) { |
||
155 | $join = '('.rtrim($join, '& ').') &&'; |
||
156 | } |
||
157 | $sql = 'SELECT DISTINCT('.str_replace('(', '', $tables[0]).'.device_id)'.$sql_extra.' FROM '.implode(',', $tables).' WHERE '.$join.' '.$search.' ('.str_replace(array('%', '@', '!~', '~'), array('', '.*', 'NOT REGEXP', 'REGEXP'), $pattern).')'; |
||
158 | return $sql; |
||
159 | }//end GenGroupSQL() |
||
160 | |||
161 | |||
162 | /** |
||
163 | * Extract an array of tables in a pattern |
||
164 | * |
||
165 | * @param string $pattern |
||
166 | * @return array |
||
167 | */ |
||
168 | function getTablesFromPattern($pattern) |
||
169 | { |
||
170 | preg_match_all('/[A-Za-z_]+(?=\.[A-Za-z_]+ )/', $pattern, $tables); |
||
171 | if (is_null($tables)) { |
||
172 | return array(); |
||
173 | } |
||
174 | return array_keys(array_flip($tables[0])); // unique tables only |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Run the group queries again to get fresh list of devices for this group |
||
179 | * @param integer $group_id Group-ID |
||
180 | * @return string |
||
181 | */ |
||
182 | function QueryDevicesFromGroup($group_id) |
||
183 | { |
||
184 | $group = dbFetchRow('SELECT pattern,params FROM device_groups WHERE id = ?', array($group_id)); |
||
185 | $pattern = rtrim($group['pattern'], '&|'); |
||
186 | $params = (array)json_decode($group['params']); |
||
187 | if (!empty($pattern)) { |
||
188 | $result = dbFetchColumn(GenGroupSQL($pattern), $params); |
||
189 | return $result; |
||
190 | } |
||
191 | |||
192 | return false; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
193 | }//end QueryDevicesFromGroup() |
||
194 | |||
195 | /** |
||
196 | * Get an array of all the device ids belonging to this group_id |
||
197 | * @param $group_id |
||
198 | * @param bool $nested Return an array of arrays containing 'device_id'. (for API compatibility) |
||
199 | * @param string $full Return all fields from devices_id |
||
200 | * @return array |
||
201 | */ |
||
202 | function GetDevicesFromGroup($group_id, $nested = false, $full = '') |
||
203 | { |
||
204 | if ($full) { |
||
205 | $query = 'SELECT `device_groups`.`name`, `devices`.* FROM `device_groups` INNER JOIN `device_group_device` ON `device_groups`.`id` = `device_group_device`.`device_group_id` INNER JOIN `devices` ON `device_group_device`.`device_id` = `devices`.`device_id` WHERE `device_groups`.`id` = ?'; |
||
206 | } else { |
||
207 | $query = 'SELECT `device_id` FROM `device_group_device` WHERE `device_group_id` = ? '; |
||
208 | } |
||
209 | if ($nested) { |
||
210 | return dbFetchRows($query, array($group_id)); |
||
211 | } else { |
||
212 | return dbFetchColumn($query, array($group_id)); |
||
213 | } |
||
214 | }//end GetDevicesFromGroup() |
||
215 | |||
216 | /** |
||
217 | * Get all Device-Groups |
||
218 | * @return array |
||
219 | */ |
||
220 | function GetDeviceGroups() |
||
221 | { |
||
222 | return dbFetchRows('SELECT * FROM device_groups ORDER BY name'); |
||
223 | }//end GetDeviceGroups() |
||
224 | |||
225 | /** |
||
226 | * Run the group queries again to get fresh list of groups for this device |
||
227 | * @param integer $device_id Device-ID |
||
228 | * @param int $extra Return extra info about the groups (name, desc, pattern) |
||
229 | * @return array |
||
230 | */ |
||
231 | function QueryGroupsFromDevice($device_id, $extra = 0) |
||
232 | { |
||
233 | $ret = array(); |
||
234 | foreach (GetDeviceGroups() as $group) { |
||
235 | $params = (array)json_decode($group['params']); |
||
236 | array_unshift($params, $device_id); |
||
237 | if (dbFetchCell(GenGroupSQL($group['pattern'], 'device_id=?', $extra).' LIMIT 1', $params) == $device_id) { |
||
238 | if ($extra === 0) { |
||
239 | $ret[] = $group['id']; |
||
240 | } else { |
||
241 | $ret[] = $group; |
||
242 | } |
||
243 | } |
||
244 | } |
||
245 | |||
246 | return $ret; |
||
247 | }//end QueryGroupsFromDevice() |
||
248 | |||
249 | /** |
||
250 | * Get the Device Group IDs of a Device from the database |
||
251 | * @param $device_id |
||
252 | * @param int $extra Return extra info about the groups (name, desc, pattern) |
||
253 | * @return array |
||
254 | */ |
||
255 | function GetGroupsFromDevice($device_id, $extra = 0) |
||
256 | { |
||
257 | $ret = array(); |
||
258 | if ($extra === 0) { |
||
259 | $ret = dbFetchColumn('SELECT `device_group_id` FROM `device_group_device` WHERE `device_id`=?', array($device_id)); |
||
260 | } else { |
||
261 | $ret = dbFetchRows('SELECT `device_groups`.* FROM `device_group_device` LEFT JOIN `device_groups` ON `device_group_device`.`device_group_id`=`device_groups`.`id` WHERE `device_group_device`.`device_id`=?', array($device_id)); |
||
262 | } |
||
263 | return $ret; |
||
264 | }//end GetGroupsFromDeviceDB() |
||
265 | |||
266 | /** |
||
267 | * Process Macros |
||
268 | * @param string $rule Rule to process |
||
269 | * @param int $x Recursion-Anchor |
||
270 | * @return string|boolean |
||
271 | */ |
||
272 | function RunGroupMacros($rule, $x = 1) |
||
273 | { |
||
274 | $macros = Config::get('alert.macros.group', []); |
||
275 | |||
276 | krsort($macros); |
||
277 | foreach ($macros as $macro => $value) { |
||
278 | if (!strstr($macro, " ")) { |
||
279 | $rule = str_replace('%macros.'.$macro, '('.$value.')', $rule); |
||
280 | } |
||
281 | } |
||
282 | if (strstr($rule, "%macros")) { |
||
283 | if (++$x < 30) { |
||
284 | $rule = RunGroupMacros($rule, $x); |
||
285 | } else { |
||
286 | return false; |
||
287 | } |
||
288 | } |
||
289 | return $rule; |
||
290 | }//end RunGroupMacros() |
||
291 | |||
292 | |||
293 | /** |
||
294 | * Update device-group relationship for the given device id |
||
295 | * @param $device_id |
||
296 | */ |
||
297 | function UpdateGroupsForDevice($device_id) |
||
298 | { |
||
299 | d_echo("### Start Device Groups ###\n"); |
||
300 | $queried_groups = QueryGroupsFromDevice($device_id); |
||
301 | $db_groups = GetGroupsFromDevice($device_id); |
||
302 | |||
303 | // compare the arrays to get the added and removed groups |
||
304 | $added_groups = array_diff($queried_groups, $db_groups); |
||
305 | $removed_groups = array_diff($db_groups, $queried_groups); |
||
306 | |||
307 | d_echo("Groups Added: ".implode(',', $added_groups).PHP_EOL); |
||
308 | d_echo("Groups Removed: ".implode(',', $removed_groups).PHP_EOL); |
||
309 | |||
310 | // insert new groups |
||
311 | $insert = array(); |
||
312 | foreach ($added_groups as $group_id) { |
||
313 | $insert[] = array('device_id' => $device_id, 'device_group_id' => $group_id); |
||
314 | } |
||
315 | if (!empty($insert)) { |
||
316 | dbBulkInsert($insert, 'device_group_device'); |
||
317 | } |
||
318 | |||
319 | // remove old groups |
||
320 | if (!empty($removed_groups)) { |
||
321 | dbDelete('device_group_device', '`device_id`=? AND `device_group_id` IN ' . dbGenPlaceholders(count($removed_groups)), array_merge([$device_id], $removed_groups)); |
||
322 | } |
||
323 | d_echo("### End Device Groups ###\n"); |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Update the device-group relationship for the given group id |
||
328 | * @param $group_id |
||
329 | */ |
||
330 | function UpdateDeviceGroup($group_id) |
||
331 | { |
||
332 | $queried_devices = QueryDevicesFromGroup($group_id); |
||
333 | $db_devices = GetDevicesFromGroup($group_id); |
||
334 | |||
335 | // compare the arrays to get the added and removed devices |
||
336 | $added_devices = array_diff($queried_devices, $db_devices); |
||
337 | $removed_devices = array_diff($db_devices, $queried_devices); |
||
338 | |||
339 | // insert new devices |
||
340 | $insert = array(); |
||
341 | foreach ($added_devices as $device_id) { |
||
342 | $insert[] = array('device_id' => $device_id, 'device_group_id' => $group_id); |
||
343 | } |
||
344 | if (!empty($insert)) { |
||
345 | dbBulkInsert($insert, 'device_group_device'); |
||
346 | } |
||
347 | |||
348 | // remove old devices |
||
349 | if (!empty($removed_devices)) { |
||
350 | dbDelete('device_group_device', '`device_group_id`=? AND `device_id` IN ' . dbGenPlaceholders(count($removed_devices)), array_merge([$group_id], $removed_devices)); |
||
351 | } |
||
352 | } |
||
353 | |||
354 | /** |
||
355 | * Fill in params into the pattern, replacing placeholders (?) |
||
356 | * If $params is empty or null, just returns $pattern |
||
357 | * |
||
358 | * @return string |
||
359 | */ |
||
360 | function formatDeviceGroupPattern($pattern, $params) |
||
361 | { |
||
362 | // fill in parameters |
||
363 | foreach ((array)$params as $value) { |
||
364 | if (!is_numeric($value) && !starts_with($value, "'")) { |
||
365 | $value = "'".$value."'"; |
||
366 | } |
||
367 | $pattern = preg_replace('/\?/', $value, $pattern, 1); |
||
368 | } |
||
369 | |||
370 | return $pattern; |
||
371 | } |
||
372 |