1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 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 2 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, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22require_once dirname(__FILE__).'/../../include/blocks.inc.php'; 23 24class CControllerWidgetNavTreeView extends CControllerWidget { 25 26 private $problems_per_severity_tpl; 27 28 public function __construct() { 29 parent::__construct(); 30 31 $this->setType(WIDGET_NAV_TREE); 32 $this->setValidationRules([ 33 'name' => 'string', 34 'uniqueid' => 'required|string', 35 'widgetid' => 'db widget.widgetid', 36 'initial_load' => 'in 0,1', 37 'fields' => 'json' 38 ]); 39 } 40 41 protected function getNumberOfProblemsBySysmap(array $navtree_items = []) { 42 $response = []; 43 $sysmapids = []; 44 45 foreach ($navtree_items as $navtree_item) { 46 $sysmapids[$navtree_item['sysmapid']] = true; 47 } 48 unset($sysmapids[0]); 49 50 $sysmaps = $sysmapids 51 ? API::Map()->get([ 52 'output' => ['sysmapid', 'severity_min'], 53 'selectLinks' => ['linktriggers', 'permission'], 54 'selectSelements' => ['elements', 'elementtype', 'permission'], 55 'sysmapids' => array_keys($sysmapids), 56 'preservekeys' => true 57 ]) 58 : []; 59 60 if ($sysmaps) { 61 $triggers_per_hosts = []; 62 $triggers_per_host_groups = []; 63 $problems_per_trigger = []; 64 $submaps_relations = []; 65 $submaps_found = []; 66 $host_groups = []; 67 $hosts = []; 68 69 // Gather submaps from all selected maps. 70 foreach ($sysmaps as $map) { 71 foreach ($map['selements'] as $selement) { 72 if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) { 73 if (($element = reset($selement['elements'])) !== false) { 74 $submaps_relations[$map['sysmapid']][] = $element['sysmapid']; 75 $submaps_found[] = $element['sysmapid']; 76 } 77 } 78 } 79 } 80 81 // Gather maps added as submaps for each of map in any depth. 82 $sysmaps_resolved = array_keys($sysmaps); 83 while ($diff = array_diff($submaps_found, $sysmaps_resolved)) { 84 $submaps = API::Map()->get([ 85 'output' => ['sysmapid', 'severity_min'], 86 'selectLinks' => ['linktriggers', 'permission'], 87 'selectSelements' => ['elements', 'elementtype', 'permission'], 88 'sysmapids' => $diff, 89 'preservekeys' => true 90 ]); 91 92 $sysmaps_resolved = array_merge($sysmaps_resolved, $diff); 93 94 foreach ($submaps as $submap) { 95 $sysmaps[$submap['sysmapid']] = $submap; 96 97 foreach ($submap['selements'] as $selement) { 98 if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) { 99 $element = reset($selement['elements']); 100 if ($element) { 101 $submaps_relations[$submap['sysmapid']][] = $element['sysmapid']; 102 $submaps_found[] = $element['sysmapid']; 103 } 104 } 105 } 106 } 107 } 108 109 // Gather elements from all maps selected. 110 foreach ($sysmaps as $map) { 111 // Collect triggers from map links. 112 foreach ($map['links'] as $link) { 113 foreach ($link['linktriggers'] as $linktrigger) { 114 $problems_per_trigger[$linktrigger['triggerid']] = $this->problems_per_severity_tpl; 115 } 116 } 117 118 // Collect map elements. 119 foreach ($map['selements'] as $selement) { 120 switch ($selement['elementtype']) { 121 case SYSMAP_ELEMENT_TYPE_HOST_GROUP: 122 if (($element = reset($selement['elements'])) !== false) { 123 $host_groups[$element['groupid']] = true; 124 } 125 break; 126 127 case SYSMAP_ELEMENT_TYPE_TRIGGER: 128 foreach (zbx_objectValues($selement['elements'], 'triggerid') as $triggerid) { 129 $problems_per_trigger[$triggerid] = $this->problems_per_severity_tpl; 130 } 131 break; 132 133 case SYSMAP_ELEMENT_TYPE_HOST: 134 if (($element = reset($selement['elements'])) !== false) { 135 $hosts[$element['hostid']] = true; 136 } 137 break; 138 } 139 } 140 } 141 142 // Select lowest severity to reduce amount of data returned by API. 143 $severity_min = min(zbx_objectValues($sysmaps, 'severity_min')); 144 145 // Get triggers related to host groups. 146 if ($host_groups) { 147 $triggers = API::Trigger()->get([ 148 'output' => ['triggerid'], 149 'groupids' => array_keys($host_groups), 150 'skipDependent' => true, 151 'selectGroups' => ['groupid'], 152 'preservekeys' => true 153 ]); 154 155 foreach ($triggers as $trigger) { 156 foreach ($trigger['groups'] as $host_group) { 157 $triggers_per_host_groups[$host_group['groupid']][$trigger['triggerid']] = true; 158 } 159 $problems_per_trigger[$trigger['triggerid']] = $this->problems_per_severity_tpl; 160 } 161 162 unset($host_groups); 163 } 164 165 // Get triggers related to hosts. 166 if ($hosts) { 167 $triggers = API::Trigger()->get([ 168 'output' => ['triggerid'], 169 'selectHosts' => ['hostid'], 170 'hostids' => array_keys($hosts), 171 'skipDependent' => true, 172 'preservekeys' => true, 173 'monitored' => true 174 ]); 175 176 foreach ($triggers as $trigger) { 177 if (($host = reset($trigger['hosts'])) !== false) { 178 $triggers_per_hosts[$host['hostid']][$trigger['triggerid']] = true; 179 $problems_per_trigger[$trigger['triggerid']] = $this->problems_per_severity_tpl; 180 } 181 } 182 183 unset($hosts); 184 } 185 186 // Count problems per trigger. 187 if ($problems_per_trigger) { 188 $triggers = API::Trigger()->get([ 189 'output' => [], 190 'selectGroups' => ['groupid'], 191 'triggerids' => array_keys($problems_per_trigger), 192 'skipDependent' => true, 193 'preservekeys' => true, 194 'monitored' => true 195 ]); 196 197 $problems = API::Problem()->get([ 198 'output' => ['objectid', 'severity'], 199 'source' => EVENT_SOURCE_TRIGGERS, 200 'object' => EVENT_OBJECT_TRIGGER, 201 'objectids' => array_keys($triggers), 202 'severities' => range($severity_min, TRIGGER_SEVERITY_COUNT - 1), 203 'preservekeys' => true 204 ]); 205 206 if ($problems) { 207 foreach ($problems as $problem) { 208 $problems_per_trigger[$problem['objectid']][$problem['severity']]++; 209 } 210 } 211 } 212 213 // Count problems in each submap included in navigation tree: 214 foreach ($navtree_items as $id => $navtree_item) { 215 $maps_need_to_count_in = $navtree_item['child_sysmapids']; 216 if ($navtree_item['sysmapid'] != 0) { 217 $maps_need_to_count_in[$navtree_item['sysmapid']] = true; 218 } 219 220 $response[$id] = $this->problems_per_severity_tpl; 221 $problems_counted = []; 222 223 foreach (array_keys($maps_need_to_count_in) as $sysmapid) { 224 if (array_key_exists($sysmapid, $sysmaps)) { 225 $map = $sysmaps[$sysmapid]; 226 227 // Count problems occurred in linked elements. 228 foreach ($map['selements'] as $selement) { 229 if ($selement['permission'] >= PERM_READ) { 230 $problems = $this->getElementProblems($selement, $problems_per_trigger, $sysmaps, 231 $submaps_relations, $map['severity_min'], $problems_counted, $triggers_per_hosts, 232 $triggers_per_host_groups 233 ); 234 235 if ($problems !== null) { 236 $response[$id] = self::sumArrayValues($response[$id], $problems); 237 } 238 } 239 } 240 241 // Count problems occurred in triggers which are related to links. 242 foreach ($map['links'] as $link) { 243 $uncounted_problem_triggers = array_diff_key( 244 array_flip(zbx_objectValues($link['linktriggers'], 'triggerid')), 245 $problems_counted 246 ); 247 248 foreach ($uncounted_problem_triggers as $triggerid => $var) { 249 $problems_to_add = $problems_per_trigger[$triggerid]; 250 $problems_counted[$triggerid] = true; 251 252 // Remove problems which are less important than map's min-severity. 253 if ($map['severity_min'] > 0) { 254 foreach ($problems_to_add as $sev => $probl) { 255 if ($map['severity_min'] > $sev) { 256 $problems_to_add[$sev] = 0; 257 } 258 } 259 } 260 261 $response[$id] = self::sumArrayValues($response[$id], $problems_to_add); 262 } 263 unset($uncounted_problem_triggers); 264 } 265 } 266 } 267 } 268 } 269 270 foreach ($response as &$row) { 271 // Reduce the amount of data transferred over Ajax. 272 if ($row === $this->problems_per_severity_tpl) { 273 $row = 0; 274 } 275 } 276 unset($row); 277 278 return $response; 279 } 280 281 protected function getElementProblems(array $selement, array $problems_per_trigger, array $sysmaps, 282 array $submaps_relations, $severity_min = 0, array &$problems_counted = [], array $triggers_per_hosts = [], 283 array $triggers_per_host_groups = []) { 284 $problems = null; 285 286 switch ($selement['elementtype']) { 287 case SYSMAP_ELEMENT_TYPE_HOST_GROUP: 288 $problems = $this->problems_per_severity_tpl; 289 290 if (($element = reset($selement['elements'])) !== false) { 291 if (array_key_exists($element['groupid'], $triggers_per_host_groups)) { 292 $uncounted_problem_triggers = array_diff_key($triggers_per_host_groups[$element['groupid']], 293 $problems_counted 294 ); 295 foreach ($uncounted_problem_triggers as $triggerid => $var) { 296 $problems_counted[$triggerid] = true; 297 $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); 298 } 299 unset($uncounted_problem_triggers); 300 } 301 } 302 break; 303 304 case SYSMAP_ELEMENT_TYPE_TRIGGER: 305 $problems = $this->problems_per_severity_tpl; 306 $uncounted_problem_triggers = array_diff_key( 307 array_flip(zbx_objectValues($selement['elements'], 'triggerid')), 308 $problems_counted 309 ); 310 foreach ($uncounted_problem_triggers as $triggerid => $var) { 311 $problems_counted[$triggerid] = true; 312 $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); 313 } 314 unset($uncounted_problem_triggers); 315 break; 316 317 case SYSMAP_ELEMENT_TYPE_HOST: 318 $problems = $this->problems_per_severity_tpl; 319 320 if (($element = reset($selement['elements'])) !== false) { 321 if (array_key_exists($element['hostid'], $triggers_per_hosts)) { 322 $uncounted_problem_triggers = array_diff_key($triggers_per_hosts[$element['hostid']], 323 $problems_counted 324 ); 325 foreach ($uncounted_problem_triggers as $triggerid => $var) { 326 $problems_counted[$triggerid] = true; 327 $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); 328 } 329 unset($uncounted_problem_triggers); 330 } 331 } 332 break; 333 334 case SYSMAP_ELEMENT_TYPE_MAP: 335 $problems = $this->problems_per_severity_tpl; 336 337 if (($submap_element = reset($selement['elements'])) !== false) { 338 // Recursively find all submaps in any depth and put them into an array. 339 $maps_to_process[$submap_element['sysmapid']] = false; 340 341 while (array_filter($maps_to_process, function($item) {return !$item;})) { 342 foreach ($maps_to_process as $linked_map => $val) { 343 $maps_to_process[$linked_map] = true; 344 345 if (array_key_exists($linked_map, $submaps_relations)) { 346 foreach ($submaps_relations[$linked_map] as $submap) { 347 if (!array_key_exists($submap, $maps_to_process)) { 348 $maps_to_process[$submap] = false; 349 } 350 } 351 } 352 } 353 } 354 355 // Count problems in each of selected submap. 356 foreach ($maps_to_process as $sysmapid => $val) { 357 // Count problems in elements assigned to selements. 358 if (array_key_exists($sysmapid, $sysmaps)) { 359 foreach ($sysmaps[$sysmapid]['selements'] as $submap_selement) { 360 if ($submap_selement['permission'] >= PERM_READ) { 361 $problems_in_submap = $this->getElementProblems($submap_selement, 362 $problems_per_trigger, $sysmaps, $submaps_relations, 363 $sysmaps[$sysmapid]['severity_min'], $problems_counted, $triggers_per_hosts, 364 $triggers_per_host_groups 365 ); 366 367 if ($problems_in_submap !== null) { 368 $problems = self::sumArrayValues($problems, $problems_in_submap); 369 } 370 } 371 } 372 } 373 374 // Count problems in triggers assigned to linked. 375 if (array_key_exists($sysmapid, $sysmaps)) { 376 foreach ($sysmaps[$sysmapid]['links'] as $link) { 377 if ($link['permission'] >= PERM_READ) { 378 $uncounted_problem_triggers = array_diff_key( 379 array_flip(zbx_objectValues($link['linktriggers'], 'triggerid')), 380 $problems_counted 381 ); 382 foreach ($uncounted_problem_triggers as $triggerid => $var) { 383 $problems_counted[$triggerid] = true; 384 $problems = self::sumArrayValues($problems, $problems_per_trigger[$triggerid]); 385 } 386 unset($uncounted_problem_triggers); 387 } 388 } 389 } 390 } 391 } 392 break; 393 } 394 395 // Remove problems which are less important than $severity_min. 396 if ($problems !== null && $severity_min > 0) { 397 foreach ($problems as $sev => $probl) { 398 if ($severity_min > $sev) { 399 $problems[$sev] = 0; 400 } 401 } 402 } 403 404 return $problems; 405 } 406 407 protected function doAction() { 408 $fields = $this->getForm()->getFieldsData(); 409 $error = null; 410 411 // Get list of sysmapids. 412 $sysmapids = []; 413 $navtree_items = []; 414 foreach ($fields['navtree'] as $id => $navtree_item) { 415 $sysmapid = array_key_exists('sysmapid', $navtree_item) ? $navtree_item['sysmapid'] : 0; 416 if ($sysmapid != 0) { 417 $sysmapids[$sysmapid] = true; 418 } 419 420 $navtree_items[$id] = [ 421 'parent' => $navtree_item['parent'], 422 'sysmapid' => $sysmapid, 423 'child_sysmapids' => [] 424 ]; 425 } 426 427 // Propagate item mapids to all its parent items. 428 foreach ($navtree_items as $navtree_item) { 429 $parent = $navtree_item['parent']; 430 431 while (array_key_exists($parent, $navtree_items)) { 432 if ($navtree_item['sysmapid'] != 0) { 433 $navtree_items[$parent]['child_sysmapids'][$navtree_item['sysmapid']] = true; 434 } 435 $parent = $navtree_items[$parent]['parent']; 436 } 437 } 438 439 // Get severity levels and colors and select list of sysmapids to count problems per maps. 440 $this->problems_per_severity_tpl = []; 441 $config = select_config(); 442 $severity_config = []; 443 444 $maps_accessible = $sysmapids 445 ? API::Map()->get([ 446 'output' => [], 447 'sysmapids' => array_keys($sysmapids), 448 'preservekeys' => true 449 ]) 450 : []; 451 452 for ($severity = TRIGGER_SEVERITY_NOT_CLASSIFIED; $severity < TRIGGER_SEVERITY_COUNT; $severity++) { 453 $this->problems_per_severity_tpl[$severity] = 0; 454 $severity_config[$severity] = [ 455 'name' => getSeverityName($severity, $config), 456 'style_class' => getSeverityStatusStyle($severity) 457 ]; 458 } 459 460 $widgetid = $this->getInput('widgetid', 0); 461 $navtree_item_selected = 0; 462 $navtree_items_opened = []; 463 if ($widgetid) { 464 $navtree_items_opened = CProfile::findByIdxPattern('web.dashbrd.navtree-%.toggle', $widgetid); 465 // Keep only numerical value from idx key name. 466 foreach ($navtree_items_opened as &$item_opened) { 467 $item_opened = substr($item_opened, 20, -7); 468 } 469 unset($item_opened); 470 $navtree_item_selected = CProfile::get('web.dashbrd.navtree.item.selected', 0, $widgetid); 471 } 472 473 $this->setResponse(new CControllerResponseData([ 474 'name' => $this->getInput('name', $this->getDefaultHeader()), 475 'uniqueid' => $this->getInput('uniqueid'), 476 'navtree' => $fields['navtree'], 477 'navtree_item_selected' => $navtree_item_selected, 478 'navtree_items_opened' => $navtree_items_opened, 479 'problems' => $this->getNumberOfProblemsBySysmap($navtree_items), 480 'show_unavailable' => $fields['show_unavailable'], 481 'maps_accessible' => array_keys($maps_accessible), 482 'severity_config' => $severity_config, 483 'initial_load' => $this->getInput('initial_load', 0), 484 'error' => $error, 485 'user' => [ 486 'debug_mode' => $this->getDebugMode() 487 ] 488 ])); 489 } 490 491 /** 492 * Function is used to sum problems in 2 arrays. 493 * 494 * Example: 495 * $a1 = [1 => 0, 2 => 5, 3 => 10]; 496 * $a2 = [1 => 1, 2 => 2, 3 => 3]; 497 * self::sumArrayValues($a1, $a2); // returns [1 => 1, 2 => 7, 3 => 13] 498 * 499 * @param array $a1 Array containing severity as key and number of problems as value. 500 * @param array $a2 Array containing severity as key and number of problems as value. 501 * 502 * @return array Array containing problems in both arrays summed. 503 */ 504 protected static function sumArrayValues(array $a1, array $a2) { 505 foreach ($a1 as $key => &$value) { 506 $value += $a2[$key]; 507 } 508 unset($value); 509 510 return $a1; 511 } 512} 513