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 22/** 23 * Class for importing configuration data. 24 */ 25class CConfigurationImport { 26 27 /** 28 * @var CImportDataAdapter 29 */ 30 protected $adapter; 31 32 /** 33 * @var CImportReferencer 34 */ 35 protected $referencer; 36 37 /** 38 * @var CImportedObjectContainer 39 */ 40 protected $importedObjectContainer; 41 42 /** 43 * @var array 44 */ 45 protected $options; 46 47 /** 48 * @var array with data read from source string 49 */ 50 protected $data; 51 52 /** 53 * @var array cached data from the adapter 54 */ 55 protected $formattedData = []; 56 57 /** 58 * Constructor. 59 * Source string must be suitable for reader class, 60 * i.e. if string contains json then reader should be able to read json. 61 * 62 * @param array $options import options "createMissing", "updateExisting" and "deleteMissing" 63 * @param CImportReferencer $referencer class containing all importable objects 64 * @param CImportedObjectContainer $importedObjectContainer class containing processed host and template IDs 65 */ 66 public function __construct(array $options, CImportReferencer $referencer, 67 CImportedObjectContainer $importedObjectContainer) { 68 $default_options = [ 69 'groups' => ['updateExisting' => false, 'createMissing' => false], 70 'hosts' => ['updateExisting' => false, 'createMissing' => false], 71 'templates' => ['updateExisting' => false, 'createMissing' => false], 72 'templateDashboards' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false], 73 'templateLinkage' => ['createMissing' => false, 'deleteMissing' => false], 74 'items' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false], 75 'discoveryRules' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false], 76 'triggers' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false], 77 'graphs' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false], 78 'httptests' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false], 79 'maps' => ['updateExisting' => false, 'createMissing' => false], 80 'images' => ['updateExisting' => false, 'createMissing' => false], 81 'mediaTypes' => ['updateExisting' => false, 'createMissing' => false], 82 'valueMaps' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false] 83 ]; 84 85 foreach ($default_options as $entity => $rules) { 86 $options[$entity] = array_key_exists($entity, $options) 87 ? array_merge($rules, $options[$entity]) 88 : $rules; 89 } 90 91 $object_options = ( 92 $options['templateLinkage']['createMissing'] 93 || $options['templateLinkage']['deleteMissing'] 94 || $options['items']['updateExisting'] 95 || $options['items']['createMissing'] 96 || $options['items']['deleteMissing'] 97 || $options['discoveryRules']['updateExisting'] 98 || $options['discoveryRules']['createMissing'] 99 || $options['discoveryRules']['deleteMissing'] 100 || $options['triggers']['deleteMissing'] 101 || $options['graphs']['deleteMissing'] 102 || $options['httptests']['updateExisting'] 103 || $options['httptests']['createMissing'] 104 || $options['httptests']['deleteMissing'] 105 ); 106 107 $options['process_templates'] = ( 108 !$options['templates']['updateExisting'] 109 && ($object_options 110 || $options['templateDashboards']['updateExisting'] 111 || $options['templateDashboards']['createMissing'] 112 || $options['templateDashboards']['deleteMissing'] 113 ) 114 ); 115 $options['process_hosts'] = (!$options['hosts']['updateExisting'] && $object_options); 116 117 $this->options = $options; 118 $this->referencer = $referencer; 119 $this->importedObjectContainer = $importedObjectContainer; 120 } 121 122 /** 123 * Import configuration data. 124 * 125 * @param CImportDataAdapter $adapter an object to provide access to the imported data 126 * 127 * @return bool 128 * 129 * @throws Exception 130 */ 131 public function import(CImportDataAdapter $adapter): bool { 132 $this->adapter = $adapter; 133 134 // Parse all import for references to resolve them all together with less sql count. 135 $this->gatherReferences(); 136 137 $this->processGroups(); 138 $this->processTemplates(); 139 $this->processHosts(); 140 141 // Delete missing objects from processed hosts and templates. 142 $this->deleteMissingHttpTests(); 143 $this->deleteMissingTemplateDashboards(); 144 $this->deleteMissingDiscoveryRules(); 145 $this->deleteMissingTriggers(); 146 $this->deleteMissingGraphs(); 147 $this->deleteMissingItems(); 148 149 // Import objects. 150 $this->processHttpTests(); 151 $this->processItems(); 152 $this->processTriggers(); 153 $this->processDiscoveryRules(); 154 $this->processGraphs(); 155 $this->processImages(); 156 $this->processMaps(); 157 $this->processTemplateDashboards(); 158 $this->processMediaTypes(); 159 160 return true; 161 } 162 163 /** 164 * Parse all import data and collect references to objects. 165 * For host objects it collects host names, for items - host name and item key, etc. 166 * Collected references are added and resolved via the $this->referencer object. 167 * 168 * @see CImportReferencer 169 */ 170 protected function gatherReferences(): void { 171 $groups_refs = []; 172 $templates_refs = []; 173 $hosts_refs = []; 174 $items_refs = []; 175 $valuemaps_refs = []; 176 $triggers_refs = []; 177 $graphs_refs = []; 178 $iconmaps_refs = []; 179 $images_refs = []; 180 $maps_refs = []; 181 $template_dashboards_refs = []; 182 $template_macros_refs = []; 183 $host_macros_refs = []; 184 $host_prototype_macros_refs = []; 185 $proxy_refs = []; 186 $host_prototypes_refs = []; 187 $httptests_refs = []; 188 $httpsteps_refs = []; 189 190 foreach ($this->getFormattedGroups() as $group) { 191 $groups_refs[$group['name']] = ['uuid' => $group['uuid']]; 192 } 193 194 foreach ($this->getFormattedTemplates() as $template) { 195 $templates_refs[$template['host']] = ['uuid' => $template['uuid']]; 196 197 foreach ($template['groups'] as $group) { 198 $groups_refs += [$group['name'] => []]; 199 } 200 201 if (array_key_exists('macros', $template)) { 202 foreach ($template['macros'] as $macro) { 203 $template_macros_refs[$template['uuid']][] = $macro['macro']; 204 } 205 } 206 207 if ($template['templates']) { 208 foreach ($template['templates'] as $linked_template) { 209 $templates_refs += [$linked_template['name'] => []]; 210 } 211 } 212 } 213 214 foreach ($this->getFormattedHosts() as $host) { 215 $hosts_refs[$host['host']] = []; 216 217 foreach ($host['groups'] as $group) { 218 $groups_refs += [$group['name'] => []]; 219 } 220 221 if (array_key_exists('macros', $host)) { 222 foreach ($host['macros'] as $macro) { 223 $host_macros_refs[$host['host']][] = $macro['macro']; 224 } 225 } 226 227 if ($host['templates']) { 228 foreach ($host['templates'] as $linked_template) { 229 $templates_refs += [$linked_template['name'] => []]; 230 } 231 } 232 233 if ($host['proxy']) { 234 $proxy_refs[$host['proxy']['name']] = []; 235 } 236 } 237 238 foreach ($this->getFormattedItems() as $host => $items) { 239 foreach ($items as $item) { 240 $items_refs[$host][$item['key_']] = array_key_exists('uuid', $item) 241 ? ['uuid' => $item['uuid']] 242 : []; 243 244 if ($item['valuemap']) { 245 $valuemaps_refs[$host][$item['valuemap']['name']] = []; 246 } 247 } 248 } 249 250 foreach ($this->getFormattedDiscoveryRules() as $host => $discovery_rules) { 251 foreach ($discovery_rules as $discovery_rule) { 252 $items_refs[$host][$discovery_rule['key_']] = array_key_exists('uuid', $discovery_rule) 253 ? ['uuid' => $discovery_rule['uuid']] 254 : []; 255 256 foreach ($discovery_rule['item_prototypes'] as $item_prototype) { 257 $items_refs[$host][$item_prototype['key_']] = array_key_exists('uuid', $item_prototype) 258 ? ['uuid' => $item_prototype['uuid']] 259 : []; 260 261 if (!empty($item_prototype['valuemap'])) { 262 $valuemaps_refs[$host][$item_prototype['valuemap']['name']] = []; 263 } 264 } 265 266 foreach ($discovery_rule['trigger_prototypes'] as $trigger) { 267 $description = $trigger['description']; 268 $expression = $trigger['expression']; 269 $recovery_expression = $trigger['recovery_expression']; 270 271 $triggers_refs[$description][$expression][$recovery_expression] = array_key_exists('uuid', $trigger) 272 ? ['uuid' => $trigger['uuid']] 273 : []; 274 275 if (array_key_exists('dependencies', $trigger)) { 276 foreach ($trigger['dependencies'] as $dependency) { 277 $name = $dependency['name']; 278 $expression = $dependency['expression']; 279 $recovery_expression = $dependency['recovery_expression']; 280 281 if (!array_key_exists($name, $triggers_refs) 282 || !array_key_exists($expression, $triggers_refs[$name]) 283 || !array_key_exists($recovery_expression, $triggers_refs[$name][$expression])) { 284 $triggers_refs[$name][$expression] = []; 285 } 286 } 287 } 288 } 289 290 foreach ($discovery_rule['graph_prototypes'] as $graph) { 291 if ($graph['ymin_item_1']) { 292 $item_host = $graph['ymin_item_1']['host']; 293 $item_key = $graph['ymin_item_1']['key']; 294 295 if (!array_key_exists($item_host, $items_refs) 296 || !array_key_exists($item_key, $items_refs[$item_host])) { 297 $items_refs[$item_host][$item_key] = []; 298 } 299 } 300 301 if ($graph['ymax_item_1']) { 302 $item_host = $graph['ymax_item_1']['host']; 303 $item_key = $graph['ymax_item_1']['key']; 304 305 if (!array_key_exists($item_host, $items_refs) 306 || !array_key_exists($item_key, $items_refs[$item_host])) { 307 $items_refs[$item_host][$item_key] = []; 308 } 309 } 310 311 foreach ($graph['gitems'] as $gitem) { 312 $item_host = $gitem['item']['host']; 313 $item_key = $gitem['item']['key']; 314 315 if (!array_key_exists($item_host, $templates_refs)) { 316 $hosts_refs[$item_host] = []; 317 } 318 319 if (!array_key_exists($item_host, $items_refs) 320 || !array_key_exists($item_key, $items_refs[$item_host])) { 321 $items_refs[$item_host][$item_key] = []; 322 } 323 324 $graphs_refs[$item_host][$graph['name']] = array_key_exists('uuid', $graph) 325 ? ['uuid' => $graph['uuid']] 326 : []; 327 } 328 } 329 330 foreach ($discovery_rule['host_prototypes'] as $host_prototype) { 331 if (array_key_exists('uuid', $host_prototype)) { 332 $host_prototypes_refs['uuid'][$host][$discovery_rule['uuid']][] = $host_prototype['uuid']; 333 } 334 else { 335 $host_prototypes_refs['host'][$host][$discovery_rule['key_']][] = $host_prototype['host']; 336 } 337 338 foreach ($host_prototype['group_prototypes'] as $group_prototype) { 339 if (isset($group_prototype['group'])) { 340 $groups_refs += [$group_prototype['group']['name'] => []]; 341 } 342 } 343 344 if (array_key_exists('macros', $host_prototype)) { 345 foreach ($host_prototype['macros'] as $macro) { 346 if (array_key_exists('uuid', $host_prototype)) { 347 $host_prototype_macros_refs['uuid'][$host][$discovery_rule['key_']] 348 [$host_prototype['uuid']][] = $macro['macro']; 349 } 350 else { 351 $host_prototype_macros_refs['host'][$host][$discovery_rule['key_']] 352 [$host_prototype['host']][] = $macro['macro']; 353 } 354 } 355 } 356 357 foreach ($host_prototype['templates'] as $template) { 358 $templates_refs += [$template['name'] => []]; 359 } 360 } 361 362 if ($discovery_rule['overrides']) { 363 foreach ($discovery_rule['overrides'] as $override) { 364 foreach ($override['operations'] as $operation) { 365 if ($operation['operationobject'] == OPERATION_OBJECT_HOST_PROTOTYPE 366 && array_key_exists('optemplate', $operation)) { 367 foreach ($operation['optemplate'] as $template) { 368 $templates_refs += [$template['name'] => []]; 369 } 370 } 371 } 372 } 373 } 374 } 375 } 376 377 foreach ($this->getFormattedGraphs() as $graph) { 378 if ($graph['ymin_item_1']) { 379 $item_host = $graph['ymin_item_1']['host']; 380 $item_key = $graph['ymin_item_1']['key']; 381 382 if (!array_key_exists($item_host, $templates_refs)) { 383 $hosts_refs[$item_host] = []; 384 } 385 386 if (!array_key_exists($item_host, $items_refs) 387 || !array_key_exists($item_key, $items_refs[$item_host])) { 388 $items_refs[$item_host][$item_key] = []; 389 } 390 } 391 392 if ($graph['ymax_item_1']) { 393 $item_host = $graph['ymax_item_1']['host']; 394 $item_key = $graph['ymax_item_1']['key']; 395 396 if (!array_key_exists($item_host, $templates_refs)) { 397 $hosts_refs[$item_host] = []; 398 } 399 400 if (!array_key_exists($item_host, $items_refs) 401 || !array_key_exists($item_key, $items_refs[$item_host])) { 402 $items_refs[$item_host][$item_key] = []; 403 } 404 } 405 406 if (array_key_exists('gitems', $graph) && $graph['gitems']) { 407 foreach ($graph['gitems'] as $gitem) { 408 $item_host = $gitem['item']['host']; 409 $item_key = $gitem['item']['key']; 410 411 if (!array_key_exists($item_host, $templates_refs)) { 412 $hosts_refs[$item_host] = []; 413 } 414 415 if (!array_key_exists($item_host, $items_refs) 416 || !array_key_exists($item_key, $items_refs[$item_host])) { 417 $items_refs[$item_host][$item_key] = []; 418 } 419 420 $graphs_refs[$gitem['item']['host']][$graph['name']] = array_key_exists('uuid', $graph) 421 ? ['uuid' => $graph['uuid']] 422 : []; 423 } 424 } 425 } 426 427 foreach ($this->getFormattedTriggers() as $trigger) { 428 $triggers_refs[$trigger['description']][$trigger['expression']][$trigger['recovery_expression']] = 429 array_key_exists('uuid', $trigger) 430 ? ['uuid' => $trigger['uuid']] 431 : []; 432 433 if (array_key_exists('dependencies', $trigger)) { 434 foreach ($trigger['dependencies'] as $dependency) { 435 $name = $dependency['name']; 436 $expression = $dependency['expression']; 437 $recovery_expression = $dependency['recovery_expression']; 438 439 if (!array_key_exists($name, $triggers_refs) 440 || !array_key_exists($expression, $triggers_refs[$name]) 441 || !array_key_exists($recovery_expression, $triggers_refs[$name][$expression])) { 442 $triggers_refs[$name][$expression][$recovery_expression] = []; 443 } 444 } 445 } 446 } 447 448 foreach ($this->getFormattedMaps() as $map) { 449 $maps_refs[$map['name']] = []; 450 451 if ($map['iconmap'] && array_key_exists('name', $map['iconmap']) && $map['iconmap']['name'] !== '') { 452 $iconmaps_refs[$map['iconmap']['name']] = []; 453 } 454 455 if ($map['background'] && array_key_exists('name', $map['background']) 456 && $map['background']['name'] !== '') { 457 $images_refs[$map['background']['name']] = []; 458 } 459 460 if (array_key_exists('selements', $map)) { 461 foreach ($map['selements'] as $selement) { 462 switch ($selement['elementtype']) { 463 case SYSMAP_ELEMENT_TYPE_MAP: 464 $maps_refs[$selement['elements'][0]['name']] = []; 465 break; 466 467 case SYSMAP_ELEMENT_TYPE_HOST_GROUP: 468 $groups_refs += [$selement['elements'][0]['name'] => []]; 469 break; 470 471 case SYSMAP_ELEMENT_TYPE_HOST: 472 $hosts_refs += [$selement['elements'][0]['host'] => []]; 473 break; 474 475 case SYSMAP_ELEMENT_TYPE_TRIGGER: 476 foreach ($selement['elements'] as $element) { 477 $description = $element['description']; 478 $expression = $element['expression']; 479 $recovery_expression = $element['recovery_expression']; 480 481 if (!array_key_exists($description, $triggers_refs) 482 || !array_key_exists($expression, $triggers_refs[$description]) 483 || !array_key_exists($recovery_expression, 484 $triggers_refs[$description][$expression])) { 485 $triggers_refs[$description][$expression][$recovery_expression] = []; 486 } 487 } 488 break; 489 } 490 } 491 } 492 493 if (array_key_exists('links', $map)) { 494 foreach ($map['links'] as $link) { 495 if (array_key_exists('linktriggers', $link)) { 496 foreach ($link['linktriggers'] as $link_trigger) { 497 $description = $link_trigger['trigger']['description']; 498 $expression = $link_trigger['trigger']['expression']; 499 $recovery_expression = $link_trigger['trigger']['recovery_expression']; 500 501 if (!array_key_exists($description, $triggers_refs) 502 || !array_key_exists($expression, $triggers_refs[$description]) 503 || !array_key_exists($recovery_expression, 504 $triggers_refs[$description][$expression])) { 505 $triggers_refs[$description][$expression][$recovery_expression] = []; 506 } 507 508 } 509 } 510 } 511 } 512 } 513 514 foreach ($this->getFormattedTemplateDashboards() as $host => $dashboards) { 515 foreach ($dashboards as $dashboard) { 516 $template_dashboards_refs[$dashboard['uuid']] = []; 517 518 if (!$dashboard['pages']) { 519 continue; 520 } 521 522 foreach ($dashboard['pages'] as $dashboard_page) { 523 if (!$dashboard_page['widgets']) { 524 continue; 525 } 526 527 foreach ($dashboard_page['widgets'] as $widget) { 528 foreach ($widget['fields'] as $field) { 529 $value = $field['value']; 530 531 switch ($field['type']) { 532 case ZBX_WIDGET_FIELD_TYPE_ITEM: 533 case ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE: 534 $templates_refs += [$value['host'] => []]; 535 536 if (!array_key_exists($value['host'], $items_refs) 537 || !array_key_exists($value['key'], $items_refs[$value['host']])) { 538 $items_refs[$value['host']][$value['key']] = []; 539 } 540 break; 541 542 case ZBX_WIDGET_FIELD_TYPE_GRAPH: 543 case ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE: 544 $templates_refs += [$value['host'] => []]; 545 546 if (!array_key_exists($value['host'], $graphs_refs) 547 || !array_key_exists($value['name'], $graphs_refs[$value['host']])) { 548 $graphs_refs[$value['host']][$value['name']] = []; 549 } 550 break; 551 } 552 } 553 } 554 } 555 } 556 } 557 558 foreach ($this->getFormattedHttpTests() as $host => $httptests) { 559 foreach ($httptests as $httptest) { 560 $httptests_refs[$host][$httptest['name']] = array_key_exists('uuid', $httptest) 561 ? ['uuid' => $httptest['uuid']] 562 : []; 563 } 564 } 565 566 foreach ($this->getFormattedHttpSteps() as $host => $httptests) { 567 foreach ($httptests as $httptest_name => $httpsteps) { 568 foreach ($httpsteps as $httpstep) { 569 $httpsteps_refs[$host][$httptest_name][$httpstep['name']] = []; 570 } 571 } 572 } 573 574 foreach ($this->getFormattedImages() as $image) { 575 $images_refs[$image['name']] = []; 576 } 577 578 $this->referencer->addGroups($groups_refs); 579 $this->referencer->addTemplates($templates_refs); 580 $this->referencer->addHosts($hosts_refs); 581 $this->referencer->addItems($items_refs); 582 $this->referencer->addValuemaps($valuemaps_refs); 583 $this->referencer->addTriggers($triggers_refs); 584 $this->referencer->addGraphs($graphs_refs); 585 $this->referencer->addIconmaps($iconmaps_refs); 586 $this->referencer->addImages($images_refs); 587 $this->referencer->addMaps($maps_refs); 588 $this->referencer->addTemplateDashboards($template_dashboards_refs); 589 $this->referencer->addTemplateMacros($template_macros_refs); 590 $this->referencer->addHostMacros($host_macros_refs); 591 $this->referencer->addHostPrototypeMacros($host_prototype_macros_refs); 592 $this->referencer->addProxies($proxy_refs); 593 $this->referencer->addHostPrototypes($host_prototypes_refs); 594 $this->referencer->addHttpTests($httptests_refs); 595 $this->referencer->addHttpSteps($httpsteps_refs); 596 } 597 598 /** 599 * Import groups. 600 */ 601 protected function processGroups(): void { 602 if (!$this->options['groups']['createMissing'] && !$this->options['groups']['updateExisting']) { 603 return; 604 } 605 606 $groups_to_create = []; 607 $groups_to_update = []; 608 609 foreach ($this->getFormattedGroups() as $group) { 610 $groupid = $this->referencer->findGroupidByUuid($group['uuid']); 611 612 if ($groupid) { 613 $groups_to_update[] = $group + ['groupid' => $groupid]; 614 } 615 else { 616 $groups_to_create[] = $group; 617 } 618 } 619 620 if ($this->options['groups']['updateExisting'] && $groups_to_update) { 621 API::HostGroup()->update(array_map(function($group) { 622 unset($group['uuid']); 623 return $group; 624 }, $groups_to_update)); 625 626 foreach ($groups_to_update as $group) { 627 $this->referencer->setDbGroup($group['groupid'], $group); 628 } 629 } 630 631 if ($this->options['groups']['createMissing'] && $groups_to_create) { 632 $created_groups = API::HostGroup()->create($groups_to_create); 633 634 foreach ($created_groups['groupids'] as $index => $groupid) { 635 $this->referencer->setDbGroup($groupid, $groups_to_create[$index]); 636 } 637 } 638 } 639 640 /** 641 * Import templates. 642 * 643 * @throws Exception 644 */ 645 protected function processTemplates(): void { 646 if ($this->options['templates']['updateExisting'] || $this->options['templates']['createMissing'] 647 || $this->options['process_templates']) { 648 $templates = $this->getFormattedTemplates(); 649 650 if ($templates) { 651 $template_importer = new CTemplateImporter($this->options, $this->referencer, 652 $this->importedObjectContainer 653 ); 654 $template_importer->import($templates); 655 656 // Get list of imported template IDs and add them processed template ID list. 657 $templateids = $template_importer->getProcessedTemplateids(); 658 $this->importedObjectContainer->addTemplateIds($templateids); 659 } 660 } 661 } 662 663 /** 664 * Import hosts. 665 * 666 * @throws Exception 667 */ 668 protected function processHosts(): void { 669 if ($this->options['hosts']['updateExisting'] || $this->options['hosts']['createMissing'] 670 || $this->options['process_hosts']) { 671 $hosts = $this->getFormattedHosts(); 672 673 if ($hosts) { 674 $host_importer = new CHostImporter($this->options, $this->referencer, $this->importedObjectContainer); 675 $host_importer->import($hosts); 676 677 // Get list of imported host IDs and add them processed host ID list. 678 $hostids = $host_importer->getProcessedHostIds(); 679 $this->importedObjectContainer->addHostIds($hostids); 680 } 681 } 682 } 683 684 /** 685 * Import items. 686 * 687 * @throws Exception 688 */ 689 protected function processItems(): void { 690 if (!$this->options['items']['createMissing'] && !$this->options['items']['updateExisting']) { 691 return; 692 } 693 694 $master_item_key = 'master_item'; 695 $order_tree = $this->getItemsOrder($master_item_key); 696 697 $items_to_create = []; 698 $items_to_update = []; 699 700 foreach ($this->getFormattedItems() as $host => $items) { 701 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 702 703 if ($hostid === null 704 || (!$this->importedObjectContainer->isHostProcessed($hostid) 705 && !$this->importedObjectContainer->isTemplateProcessed($hostid))) { 706 continue; 707 } 708 709 foreach ($order_tree[$host] as $item_key => $level) { 710 $item = $items[$item_key]; 711 $item['hostid'] = $hostid; 712 713 if (array_key_exists('interface_ref', $item) && $item['interface_ref']) { 714 $interfaceid = $this->referencer->findInterfaceidByRef($hostid, $item['interface_ref']); 715 716 if ($interfaceid === null) { 717 throw new Exception(_s('Cannot find interface "%1$s" used for item "%2$s" on "%3$s".', 718 $item['interface_ref'], $item['name'], $host 719 )); 720 } 721 722 $item['interfaceid'] = $interfaceid; 723 } 724 725 if (array_key_exists('valuemap', $item) && $item['valuemap']) { 726 $valuemapid = $this->referencer->findValuemapidByName($hostid, $item['valuemap']['name']); 727 728 if ($valuemapid === null) { 729 throw new Exception(_s( 730 'Cannot find value map "%1$s" used for item "%2$s" on "%3$s".', 731 $item['valuemap']['name'], 732 $item['name'], 733 $host 734 )); 735 } 736 737 $item['valuemapid'] = $valuemapid; 738 unset($item['valuemap']); 739 } 740 741 if ($item['type'] == ITEM_TYPE_DEPENDENT) { 742 if (!array_key_exists('key', $item[$master_item_key])) { 743 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 744 _('cannot be empty') 745 )); 746 } 747 748 $master_itemid = $this->referencer->findItemidByKey($hostid, $item[$master_item_key]['key']); 749 750 if ($master_itemid !== null) { 751 $item['master_itemid'] = $master_itemid; 752 unset($item[$master_item_key]); 753 } 754 } 755 else { 756 unset($item[$master_item_key]); 757 } 758 759 if ($item['type'] == ITEM_TYPE_HTTPAGENT) { 760 $headers = []; 761 762 foreach ($item['headers'] as $header) { 763 $headers[$header['name']] = $header['value']; 764 } 765 766 $item['headers'] = $headers; 767 768 $query_fields = []; 769 770 foreach ($item['query_fields'] as $query_field) { 771 $query_fields[] = [$query_field['name'] => $query_field['value']]; 772 } 773 774 $item['query_fields'] = $query_fields; 775 } 776 777 foreach ($item['preprocessing'] as &$preprocessing_step) { 778 $preprocessing_step['params'] = implode("\n", $preprocessing_step['parameters']); 779 unset($preprocessing_step['parameters']); 780 } 781 unset($preprocessing_step); 782 783 $itemid = array_key_exists('uuid', $item) 784 ? $this->referencer->findItemidByUuid($item['uuid']) 785 : $this->referencer->findItemidByKey($hostid, $item['key_']); 786 787 if ($itemid !== null) { 788 $item['itemid'] = $itemid; 789 790 if (!array_key_exists($level, $items_to_update)) { 791 $items_to_update[$level] = []; 792 } 793 794 $items_to_update[$level][] = $item; 795 } 796 else { 797 if (!array_key_exists($level, $items_to_create)) { 798 $items_to_create[$level] = []; 799 } 800 801 $items_to_create[$level][] = $item; 802 } 803 } 804 } 805 806 if ($this->options['items']['updateExisting'] && $items_to_update) { 807 $this->updateItemsWithDependency($items_to_update, $master_item_key, API::Item()); 808 } 809 810 if ($this->options['items']['createMissing'] && $items_to_create) { 811 $this->createItemsWithDependency($items_to_create, $master_item_key, API::Item()); 812 } 813 814 // Refresh items because templated ones can be inherited to host and used in triggers, graphs, etc. 815 $this->referencer->refreshItems(); 816 } 817 818 /** 819 * Create CItem or CItemPrototype with dependency. 820 * 821 * @param array $items_by_level Associative array of entities where key is entity dependency 822 * level and value is array of entities for this level. 823 * @param string $master_item_key Master entity array key in xml parsed data. 824 * @param CItem|CItemPrototype $api_service Entity service which is capable to proceed with entity create. 825 * 826 * @throws Exception if entity master entity can not be resolved. 827 */ 828 protected function createItemsWithDependency(array $items_by_level, string $master_item_key, 829 CItemGeneral $api_service): void { 830 foreach ($items_by_level as $items_to_create) { 831 foreach ($items_to_create as &$item) { 832 if (array_key_exists($master_item_key, $item)) { 833 $item['master_itemid'] = $this->referencer->findItemidByKey($item['hostid'], 834 $item[$master_item_key]['key'] 835 ); 836 837 if ($item['master_itemid'] === null) { 838 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 839 'master_itemid', _s('value "%1$s" not found', $item[$master_item_key]['key']) 840 )); 841 } 842 unset($item[$master_item_key]); 843 } 844 } 845 unset($item); 846 847 $created_items = $api_service->create($items_to_create); 848 849 foreach ($items_to_create as $index => $item) { 850 $this->referencer->setDbItem($created_items['itemids'][$index], $item); 851 } 852 } 853 } 854 855 /** 856 * Update CItem or CItemPrototype with dependency. 857 * 858 * @param array $items_by_level Associative array of entities where key is entity dependency 859 * level and value is array of entities for this level. 860 * @param string $master_item_key Master entity array key in xml parsed data. 861 * @param CItem|CItemPrototype $api_service Entity service which is capable to proceed with entity update. 862 * 863 * @throws Exception if entity master entity can not be resolved. 864 */ 865 protected function updateItemsWithDependency(array $items_by_level, string $master_item_key, 866 CItemGeneral $api_service): void { 867 foreach ($items_by_level as $items_to_update) { 868 foreach ($items_to_update as &$item) { 869 if (array_key_exists($master_item_key, $item)) { 870 $item['master_itemid'] = $this->referencer->findItemidByKey($item['hostid'], 871 $item[$master_item_key]['key'] 872 ); 873 874 if ($item['master_itemid'] === null) { 875 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 876 'master_itemid', _s('value "%1$s" not found', $item[$master_item_key]['key']) 877 )); 878 } 879 unset($item[$master_item_key]); 880 } 881 } 882 unset($item); 883 884 $updated_items = $api_service->update(array_map(function($item) { 885 unset($item['uuid']); 886 return $item; 887 }, $items_to_update)); 888 889 foreach ($items_to_update as $index => $item) { 890 $this->referencer->setDbItem($updated_items['itemids'][$index], $item); 891 } 892 } 893 } 894 895 /** 896 * Import discovery rules. 897 * 898 * @throws Exception 899 */ 900 protected function processDiscoveryRules(): void { 901 if (!$this->options['discoveryRules']['createMissing'] && !$this->options['discoveryRules']['updateExisting']) { 902 return; 903 } 904 905 $master_item_key = 'master_item'; 906 $discovery_rules_by_hosts = $this->getFormattedDiscoveryRules(); 907 908 if (!$discovery_rules_by_hosts) { 909 return; 910 } 911 912 // Unset rules that are related to hosts we did not process. 913 foreach ($discovery_rules_by_hosts as $host => $discovery_rules) { 914 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 915 916 if ($hostid === null 917 || (!$this->importedObjectContainer->isHostProcessed($hostid) 918 && !$this->importedObjectContainer->isTemplateProcessed($hostid))) { 919 unset($discovery_rules_by_hosts[$host]); 920 } 921 } 922 923 if ($this->options['discoveryRules']['updateExisting']) { 924 $this->deleteMissingPrototypes($discovery_rules_by_hosts); 925 } 926 927 $discovery_rules_to_create = []; 928 $discovery_rules_to_update = []; 929 930 foreach ($discovery_rules_by_hosts as $host => $discovery_rules) { 931 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 932 933 foreach ($discovery_rules as $discovery_rule) { 934 $discovery_rule['hostid'] = $hostid; 935 936 $itemid = array_key_exists('uuid', $discovery_rule) 937 ? $this->referencer->findItemidByUuid($discovery_rule['uuid']) 938 : $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']); 939 940 unset($discovery_rule['item_prototypes'], $discovery_rule['trigger_prototypes'], 941 $discovery_rule['graph_prototypes'], $discovery_rule['host_prototypes'] 942 ); 943 944 if (array_key_exists('interface_ref', $discovery_rule) && $discovery_rule['interface_ref']) { 945 $interfaceid = $this->referencer->findInterfaceidByRef($hostid, $discovery_rule['interface_ref']); 946 947 if ($interfaceid === null) { 948 throw new Exception(_s('Cannot find interface "%1$s" used for discovery rule "%2$s" on "%3$s".', 949 $discovery_rule['interface_ref'], $discovery_rule['name'], $host 950 )); 951 } 952 953 $discovery_rule['interfaceid'] = $interfaceid; 954 } 955 956 if ($discovery_rule['type'] == ITEM_TYPE_HTTPAGENT) { 957 $headers = []; 958 959 foreach ($discovery_rule['headers'] as $header) { 960 $headers[$header['name']] = $header['value']; 961 } 962 963 $discovery_rule['headers'] = $headers; 964 965 $query_fields = []; 966 967 foreach ($discovery_rule['query_fields'] as $query_field) { 968 $query_fields[] = [$query_field['name'] => $query_field['value']]; 969 } 970 971 $discovery_rule['query_fields'] = $query_fields; 972 } 973 974 if ($discovery_rule['type'] == ITEM_TYPE_DEPENDENT) { 975 if (!array_key_exists('key', $discovery_rule[$master_item_key])) { 976 throw new Exception( _s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 977 _('cannot be empty') 978 )); 979 } 980 981 $discovery_rule['master_itemid'] = $this->referencer->findItemidByKey($hostid, 982 $discovery_rule[$master_item_key]['key'] 983 ); 984 } 985 986 unset($discovery_rule[$master_item_key]); 987 988 if ($discovery_rule['overrides']) { 989 foreach ($discovery_rule['overrides'] as &$override) { 990 foreach ($override['operations'] as &$operation) { 991 if ($operation['operationobject'] == OPERATION_OBJECT_HOST_PROTOTYPE 992 && array_key_exists('optemplate', $operation)) { 993 foreach ($operation['optemplate'] as &$template) { 994 $templateid = $this->referencer->findTemplateidByHost($template['name']); 995 996 if ($templateid === null) { 997 throw new Exception(_s( 998 'Cannot find template "%1$s" for override "%2$s" of discovery rule "%3$s" on "%4$s".', 999 $template['name'], 1000 $override['name'], 1001 $discovery_rule['name'], 1002 $host 1003 )); 1004 } 1005 1006 $template['templateid'] = $templateid; 1007 unset($template['name']); 1008 } 1009 unset($template); 1010 } 1011 } 1012 unset($operation); 1013 } 1014 unset($override); 1015 } 1016 1017 foreach ($discovery_rule['preprocessing'] as &$preprocessing_step) { 1018 $preprocessing_step['params'] = implode("\n", $preprocessing_step['parameters']); 1019 unset($preprocessing_step['parameters']); 1020 } 1021 unset($preprocessing_step); 1022 1023 if ($itemid !== null) { 1024 $discovery_rule['itemid'] = $itemid; 1025 unset($discovery_rule['uuid']); 1026 $discovery_rules_to_update[] = $discovery_rule; 1027 } 1028 else { 1029 /* 1030 * The array key "lld_macro_paths" must exist at this point. It is processed by chain conversion. 1031 * Unlike discoveryrule.update method, discoveryrule.create does not allow "lld_macro_paths" 1032 * to be empty. 1033 */ 1034 if (!$discovery_rule['lld_macro_paths']) { 1035 unset($discovery_rule['lld_macro_paths']); 1036 } 1037 $discovery_rules_to_create[] = $discovery_rule; 1038 } 1039 } 1040 } 1041 1042 $processed_discovery_rules = []; 1043 1044 if ($this->options['discoveryRules']['createMissing'] && $discovery_rules_to_create) { 1045 API::DiscoveryRule()->create($discovery_rules_to_create); 1046 1047 foreach ($discovery_rules_to_create as $discovery_rule) { 1048 $processed_discovery_rules[$discovery_rule['hostid']][$discovery_rule['key_']] = 1; 1049 } 1050 } 1051 1052 if ($this->options['discoveryRules']['updateExisting'] && $discovery_rules_to_update) { 1053 API::DiscoveryRule()->update($discovery_rules_to_update); 1054 1055 foreach ($discovery_rules_to_update as $discovery_rule) { 1056 $processed_discovery_rules[$discovery_rule['hostid']][$discovery_rule['key_']] = 1; 1057 } 1058 } 1059 1060 // Refresh discovery rules because templated ones can be inherited to host and used for prototypes. 1061 $this->referencer->refreshItems(); 1062 1063 $order_tree = $this->getDiscoveryRulesItemsOrder($master_item_key); 1064 1065 // process prototypes 1066 $item_prototypes_to_update = []; 1067 $item_prototypes_to_create = []; 1068 $host_prototypes_to_update = []; 1069 $host_prototypes_to_create = []; 1070 $ex_group_prototypes_where = []; 1071 1072 foreach ($discovery_rules_by_hosts as $host => $discovery_rules) { 1073 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 1074 1075 foreach ($discovery_rules as $discovery_rule) { 1076 // if rule was not processed we should not create/update any of its prototypes 1077 if (!array_key_exists($discovery_rule['key_'], $processed_discovery_rules[$hostid])) { 1078 continue; 1079 } 1080 1081 $itemid = $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']); 1082 1083 // prototypes 1084 $item_prototypes = $discovery_rule['item_prototypes'] ? $order_tree[$host][$discovery_rule['key_']] : []; 1085 1086 foreach ($item_prototypes as $index => $level) { 1087 $item_prototype = $discovery_rule['item_prototypes'][$index]; 1088 $item_prototype['hostid'] = $hostid; 1089 1090 if (array_key_exists('interface_ref', $item_prototype) && $item_prototype['interface_ref']) { 1091 $interfaceid = $this->referencer->findInterfaceidByRef($hostid, 1092 $item_prototype['interface_ref'] 1093 ); 1094 1095 if ($interfaceid !== null) { 1096 $item_prototype['interfaceid'] = $interfaceid; 1097 } 1098 else { 1099 throw new Exception(_s( 1100 'Cannot find interface "%1$s" used for item prototype "%2$s" of discovery rule "%3$s" on "%4$s".', 1101 $item_prototype['interface_ref'], 1102 $item_prototype['name'], 1103 $discovery_rule['name'], 1104 $host 1105 )); 1106 } 1107 } 1108 1109 if ($item_prototype['valuemap']) { 1110 $valuemapid = $this->referencer->findValuemapidByName($hostid, 1111 $item_prototype['valuemap']['name'] 1112 ); 1113 1114 if ($valuemapid === null) { 1115 throw new Exception(_s( 1116 'Cannot find value map "%1$s" used for item prototype "%2$s" of discovery rule "%3$s" on "%4$s".', 1117 $item_prototype['valuemap']['name'], 1118 $item_prototype['name'], 1119 $discovery_rule['name'], 1120 $host 1121 )); 1122 } 1123 1124 $item_prototype['valuemapid'] = $valuemapid; 1125 unset($item_prototype['valuemap']); 1126 } 1127 1128 if ($item_prototype['type'] == ITEM_TYPE_DEPENDENT) { 1129 if (!array_key_exists('key', $item_prototype[$master_item_key])) { 1130 throw new Exception( _s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 1131 _('cannot be empty') 1132 )); 1133 } 1134 1135 $master_item_prototypeid = $this->referencer->findItemidByKey($hostid, 1136 $item_prototype[$master_item_key]['key'] 1137 ); 1138 1139 if ($master_item_prototypeid !== null) { 1140 $item_prototype['master_itemid'] = $master_item_prototypeid; 1141 unset($item_prototype[$master_item_key]); 1142 } 1143 } 1144 else { 1145 unset($item_prototype[$master_item_key]); 1146 } 1147 1148 if ($item_prototype['type'] == ITEM_TYPE_HTTPAGENT) { 1149 $headers = []; 1150 1151 foreach ($item_prototype['headers'] as $header) { 1152 $headers[$header['name']] = $header['value']; 1153 } 1154 1155 $item_prototype['headers'] = $headers; 1156 1157 $query_fields = []; 1158 1159 foreach ($item_prototype['query_fields'] as $query_field) { 1160 $query_fields[] = [$query_field['name'] => $query_field['value']]; 1161 } 1162 1163 $item_prototype['query_fields'] = $query_fields; 1164 } 1165 1166 $item_prototypeid = array_key_exists('uuid', $item_prototype) 1167 ? $this->referencer->findItemidByUuid($item_prototype['uuid']) 1168 : $this->referencer->findItemidByKey($hostid, $item_prototype['key_']); 1169 1170 $item_prototype['rule'] = [ 1171 'hostid' => $hostid, 1172 'key' => $discovery_rule['key_'] 1173 ]; 1174 $item_prototype['ruleid'] = $itemid; 1175 1176 foreach ($item_prototype['preprocessing'] as &$preprocessing_step) { 1177 $preprocessing_step['params'] = implode("\n", $preprocessing_step['parameters']); 1178 unset($preprocessing_step['parameters']); 1179 } 1180 unset($preprocessing_step); 1181 1182 if ($item_prototypeid !== null) { 1183 if (!array_key_exists($level, $item_prototypes_to_update)) { 1184 $item_prototypes_to_update[$level] = []; 1185 } 1186 $item_prototype['itemid'] = $item_prototypeid; 1187 $item_prototypes_to_update[$level][] = $item_prototype; 1188 } 1189 else { 1190 if (!array_key_exists($level, $item_prototypes_to_create)) { 1191 $item_prototypes_to_create[$level] = []; 1192 } 1193 $item_prototypes_to_create[$level][] = $item_prototype; 1194 } 1195 } 1196 1197 foreach ($discovery_rule['host_prototypes'] as $host_prototype) { 1198 // Resolve group prototypes. 1199 $group_links = []; 1200 1201 foreach ($host_prototype['group_links'] as $group_link) { 1202 $groupid = $this->referencer->findGroupidByName($group_link['group']['name']); 1203 1204 if ($groupid === null) { 1205 throw new Exception(_s( 1206 'Cannot find host group "%1$s" for host prototype "%2$s" of discovery rule "%3$s" on "%4$s".', 1207 $group_link['group']['name'], 1208 $host_prototype['name'], 1209 $discovery_rule['name'], 1210 $host 1211 )); 1212 } 1213 1214 $group_links[] = ['groupid' => $groupid]; 1215 } 1216 1217 $host_prototype['groupLinks'] = $group_links; 1218 $host_prototype['groupPrototypes'] = $host_prototype['group_prototypes']; 1219 unset($host_prototype['group_links'], $host_prototype['group_prototypes']); 1220 1221 // Resolve templates. 1222 $templates = []; 1223 1224 foreach ($host_prototype['templates'] as $template) { 1225 $templateid = $this->referencer->findTemplateidByHost($template['name']); 1226 1227 if ($templateid === null) { 1228 throw new Exception(_s( 1229 'Cannot find template "%1$s" for host prototype "%2$s" of discovery rule "%3$s" on "%4$s".', 1230 $template['name'], 1231 $host_prototype['name'], 1232 $discovery_rule['name'], 1233 $host 1234 )); 1235 } 1236 1237 $templates[] = ['templateid' => $templateid]; 1238 } 1239 1240 $host_prototype['templates'] = $templates; 1241 1242 $host_prototypeid = array_key_exists('uuid', $host_prototype) 1243 ? $this->referencer->findHostPrototypeidByUuid($host_prototype['uuid']) 1244 : $this->referencer->findHostPrototypeidByHost($hostid, $itemid, 1245 $host_prototype['host'] 1246 ); 1247 1248 if ($host_prototypeid !== null) { 1249 if ($host_prototype['groupPrototypes']) { 1250 $ex_group_prototypes_where[] = '('.dbConditionInt('hostid', [$host_prototypeid]). 1251 ' AND '.dbConditionString('name', 1252 array_column($host_prototype['groupPrototypes'], 'name') 1253 ). 1254 ')'; 1255 } 1256 1257 if (array_key_exists('macros', $host_prototype)) { 1258 foreach ($host_prototype['macros'] as &$macro) { 1259 $hostmacroid = $this->referencer->findHostPrototypeMacroid($host_prototypeid, 1260 $macro['macro'] 1261 ); 1262 1263 if ($hostmacroid !== null) { 1264 $macro['hostmacroid'] = $hostmacroid; 1265 } 1266 } 1267 unset($macro); 1268 } 1269 1270 $host_prototype['hostid'] = $host_prototypeid; 1271 unset($host_prototype['uuid']); 1272 $host_prototypes_to_update[] = $host_prototype; 1273 } 1274 else { 1275 $host_prototype['ruleid'] = $itemid; 1276 $host_prototypes_to_create[] = $host_prototype; 1277 } 1278 } 1279 } 1280 } 1281 1282 // Attach existing host group prototype ids. 1283 if ($ex_group_prototypes_where) { 1284 $db_group_prototypes = DBFetchArray(DBselect( 1285 'SELECT group_prototypeid,name,hostid'. 1286 ' FROM group_prototype'. 1287 ' WHERE '.implode(' OR ', $ex_group_prototypes_where) 1288 )); 1289 1290 if ($db_group_prototypes) { 1291 $groups_hash = []; 1292 foreach ($db_group_prototypes as $group) { 1293 $groups_hash[$group['hostid']][$group['name']] = $group['group_prototypeid']; 1294 } 1295 1296 foreach ($host_prototypes_to_update as &$host_prototype) { 1297 if (!array_key_exists($host_prototype['hostid'], $groups_hash)) { 1298 continue; 1299 } 1300 1301 $hash = $groups_hash[$host_prototype['hostid']]; 1302 foreach ($host_prototype['groupPrototypes'] as &$group_prototype) { 1303 if (array_key_exists($group_prototype['name'], $hash)) { 1304 $group_prototype['group_prototypeid'] = $hash[$group_prototype['name']]; 1305 } 1306 } 1307 unset($group_prototype, $hash); 1308 } 1309 unset($host_prototype, $groups_hash); 1310 } 1311 } 1312 1313 if ($item_prototypes_to_update) { 1314 ksort($item_prototypes_to_update); 1315 $this->updateItemsWithDependency($item_prototypes_to_update, $master_item_key, API::ItemPrototype()); 1316 } 1317 1318 if ($item_prototypes_to_create) { 1319 ksort($item_prototypes_to_create); 1320 $this->createItemsWithDependency($item_prototypes_to_create, $master_item_key, API::ItemPrototype()); 1321 } 1322 1323 if ($host_prototypes_to_update) { 1324 API::HostPrototype()->update($host_prototypes_to_update); 1325 } 1326 1327 if ($host_prototypes_to_create) { 1328 API::HostPrototype()->create($host_prototypes_to_create); 1329 } 1330 1331 // Refresh prototypes because templated ones can be inherited to host and used in triggers prototypes or graph 1332 // prototypes. 1333 $this->referencer->refreshItems(); 1334 1335 // First we need to create item prototypes and only then trigger and graph prototypes. 1336 $triggers_to_create = []; 1337 $triggers_to_update = []; 1338 $graphs_to_create = []; 1339 $graphs_to_update = []; 1340 1341 // The list of triggers to process dependencies. 1342 $triggers = []; 1343 1344 foreach ($discovery_rules_by_hosts as $host => $discovery_rules) { 1345 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 1346 1347 foreach ($discovery_rules as $discovery_rule) { 1348 // If rule was not processed we should not create/update any of its prototypes. 1349 if (!array_key_exists($discovery_rule['key_'], $processed_discovery_rules[$hostid])) { 1350 continue; 1351 } 1352 1353 foreach ($discovery_rule['trigger_prototypes'] as $trigger) { 1354 $triggerid = array_key_exists('uuid', $trigger) 1355 ? $this->referencer->findTriggeridByUuid($trigger['uuid']) 1356 : $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'], 1357 $trigger['recovery_expression'] 1358 ); 1359 1360 $triggers[] = $trigger; 1361 unset($trigger['dependencies']); 1362 1363 if ($triggerid !== null) { 1364 $trigger['triggerid'] = $triggerid; 1365 $triggers_to_update[] = $trigger; 1366 } 1367 else { 1368 $triggers_to_create[] = $trigger; 1369 } 1370 } 1371 1372 foreach ($discovery_rule['graph_prototypes'] as $graph) { 1373 if ($graph['ymin_item_1']) { 1374 $hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymin_item_1']['host']); 1375 1376 $itemid = ($hostid !== null) 1377 ? $this->referencer->findItemidByKey($hostid, $graph['ymin_item_1']['key']) 1378 : null; 1379 1380 if ($itemid === null) { 1381 throw new Exception(_s( 1382 'Cannot find item "%1$s" on "%2$s" used as the Y axis MIN value for graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".', 1383 $graph['ymin_item_1']['key'], 1384 $graph['ymin_item_1']['host'], 1385 $graph['name'], 1386 $discovery_rule['name'], 1387 $host 1388 )); 1389 } 1390 1391 $graph['ymin_itemid'] = $itemid; 1392 } 1393 1394 if ($graph['ymax_item_1']) { 1395 $hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymax_item_1']['host']); 1396 1397 $itemid = ($hostid !== null) 1398 ? $this->referencer->findItemidByKey($hostid, $graph['ymax_item_1']['key']) 1399 : null; 1400 1401 if ($itemid === null) { 1402 throw new Exception(_s( 1403 'Cannot find item "%1$s" on "%2$s" used as the Y axis MAX value for graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".', 1404 $graph['ymax_item_1']['key'], 1405 $graph['ymax_item_1']['host'], 1406 $graph['name'], 1407 $discovery_rule['name'], 1408 $host 1409 )); 1410 } 1411 1412 $graph['ymax_itemid'] = $itemid; 1413 } 1414 1415 foreach ($graph['gitems'] as &$item) { 1416 $hostid = $this->referencer->findTemplateidOrHostidByHost($item['item']['host']); 1417 1418 $item['itemid'] = ($hostid !== null) 1419 ? $this->referencer->findItemidByKey($hostid, $item['item']['key']) 1420 : null; 1421 1422 if ($item['itemid'] === null) { 1423 throw new Exception(_s( 1424 'Cannot find item "%1$s" on "%2$s" used in graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".', 1425 $item['item']['key'], 1426 $item['item']['host'], 1427 $graph['name'], 1428 $discovery_rule['name'], 1429 $host 1430 )); 1431 } 1432 } 1433 unset($item); 1434 1435 $graphid = array_key_exists('uuid', $graph) 1436 ? $this->referencer->findGraphidByUuid($graph['uuid']) 1437 : $this->referencer->findGraphidByName($hostid, $graph['name']); 1438 1439 if ($graphid !== null) { 1440 $graph['graphid'] = $graphid; 1441 unset($graph['uuid']); 1442 $graphs_to_update[] = $graph; 1443 } 1444 else { 1445 $graphs_to_create[] = $graph; 1446 } 1447 } 1448 } 1449 } 1450 1451 if ($triggers_to_update) { 1452 $updated_triggers = API::TriggerPrototype()->update(array_map(function($trigger) { 1453 unset($trigger['uuid']); 1454 return $trigger; 1455 }, $triggers_to_update)); 1456 1457 foreach ($updated_triggers['triggerids'] as $index => $triggerid) { 1458 $trigger = $triggers_to_update[$index]; 1459 $this->referencer->setDbTrigger($triggerid, $trigger); 1460 } 1461 } 1462 1463 if ($triggers_to_create) { 1464 $created_triggers = API::TriggerPrototype()->create($triggers_to_create); 1465 1466 foreach ($created_triggers['triggerids'] as $index => $triggerid) { 1467 $trigger = $triggers_to_create[$index]; 1468 $this->referencer->setDbTrigger($triggerid, $trigger); 1469 } 1470 } 1471 1472 if ($graphs_to_update) { 1473 API::GraphPrototype()->update($graphs_to_update); 1474 $this->referencer->refreshGraphs(); 1475 } 1476 1477 if ($graphs_to_create) { 1478 API::GraphPrototype()->create($graphs_to_create); 1479 $this->referencer->refreshGraphs(); 1480 } 1481 1482 $this->processTriggerPrototypeDependencies($triggers); 1483 } 1484 1485 /** 1486 * Update trigger prototype dependencies. 1487 * 1488 * @param array $triggers 1489 * 1490 * @throws Exception 1491 */ 1492 protected function processTriggerPrototypeDependencies(array $triggers): void { 1493 $trigger_dependencies = []; 1494 1495 foreach ($triggers as $trigger) { 1496 if (!array_key_exists('dependencies', $trigger)) { 1497 continue; 1498 } 1499 1500 $dependencies = []; 1501 $triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'], 1502 $trigger['recovery_expression'] 1503 ); 1504 1505 foreach ($trigger['dependencies'] as $dependency) { 1506 $dependent_triggerid = $this->referencer->findTriggeridByName($dependency['name'], 1507 $dependency['expression'], $dependency['recovery_expression'] 1508 ); 1509 1510 if ($dependent_triggerid === null) { 1511 throw new Exception(_s('Trigger prototype "%1$s" depends on trigger "%2$s", which does not exist.', 1512 $trigger['description'], 1513 $dependency['name'] 1514 )); 1515 } 1516 1517 $dependencies[] = ['triggerid' => $dependent_triggerid]; 1518 } 1519 1520 $trigger_dependencies[] = [ 1521 'triggerid' => $triggerid, 1522 'dependencies' => $dependencies 1523 ]; 1524 } 1525 1526 if ($trigger_dependencies) { 1527 API::TriggerPrototype()->update($trigger_dependencies); 1528 } 1529 } 1530 1531 /** 1532 * Import web scenarios. 1533 * 1534 * @throws APIException 1535 */ 1536 protected function processHttpTests(): void { 1537 if (!$this->options['httptests']['createMissing'] && !$this->options['httptests']['updateExisting']) { 1538 return; 1539 } 1540 1541 $httptests_to_create = []; 1542 $httptests_to_update = []; 1543 1544 foreach ($this->getFormattedHttpTests() as $host => $httptests) { 1545 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 1546 1547 if ($hostid === null 1548 || (!$this->importedObjectContainer->isHostProcessed($hostid) 1549 && !$this->importedObjectContainer->isTemplateProcessed($hostid))) { 1550 continue; 1551 } 1552 1553 foreach ($httptests as $httptest) { 1554 $httptestid = array_key_exists('uuid', $httptest) 1555 ? $this->referencer->findHttpTestidByUuid($httptest['uuid']) 1556 : $this->referencer->findHttpTestidByName($hostid, $httptest['name']); 1557 1558 if ($httptestid !== null) { 1559 foreach ($httptest['steps'] as &$httpstep) { 1560 $httpstepid = $this->referencer->findHttpStepidByName($hostid, $httptestid, $httpstep['name']); 1561 1562 if ($httpstepid !== null) { 1563 $httpstep['httpstepid'] = $httpstepid; 1564 } 1565 } 1566 unset($httpstep); 1567 1568 $httptest['httptestid'] = $httptestid; 1569 unset($httptest['uuid']); 1570 1571 $httptests_to_update[] = $httptest; 1572 } 1573 else { 1574 $httptest['hostid'] = $hostid; 1575 $httptests_to_create[] = $httptest; 1576 } 1577 } 1578 } 1579 1580 if ($this->options['httptests']['updateExisting'] && $httptests_to_update) { 1581 API::HttpTest()->update($httptests_to_update); 1582 } 1583 1584 if ($this->options['httptests']['createMissing'] && $httptests_to_create) { 1585 API::HttpTest()->create($httptests_to_create); 1586 } 1587 1588 $this->referencer->refreshHttpTests(); 1589 } 1590 1591 /** 1592 * Import graphs. 1593 * 1594 * @throws Exception 1595 */ 1596 protected function processGraphs(): void { 1597 if (!$this->options['graphs']['createMissing'] && !$this->options['graphs']['updateExisting']) { 1598 return; 1599 } 1600 1601 $graphs_to_create = []; 1602 $graphs_to_update = []; 1603 1604 foreach ($this->getFormattedGraphs() as $graph) { 1605 if ($graph['ymin_item_1']) { 1606 $hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymin_item_1']['host']); 1607 $itemid = ($hostid !== null) 1608 ? $this->referencer->findItemidByKey($hostid, $graph['ymin_item_1']['key']) 1609 : null; 1610 1611 if ($itemid === null) { 1612 throw new Exception(_s( 1613 'Cannot find item "%1$s" on "%2$s" used as the Y axis MIN value for graph "%3$s".', 1614 $graph['ymin_item_1']['key'], 1615 $graph['ymin_item_1']['host'], 1616 $graph['name'] 1617 )); 1618 } 1619 1620 $graph['ymin_itemid'] = $itemid; 1621 } 1622 1623 if ($graph['ymax_item_1']) { 1624 $hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymax_item_1']['host']); 1625 $itemid = ($hostid !== null) 1626 ? $this->referencer->findItemidByKey($hostid, $graph['ymax_item_1']['key']) 1627 : null; 1628 1629 if ($itemid === null) { 1630 throw new Exception(_s( 1631 'Cannot find item "%1$s" on "%2$s" used as the Y axis MAX value for graph "%3$s".', 1632 $graph['ymax_item_1']['key'], 1633 $graph['ymax_item_1']['host'], 1634 $graph['name'] 1635 )); 1636 } 1637 1638 $graph['ymax_itemid'] = $itemid; 1639 } 1640 1641 $hostid = null; 1642 1643 foreach ($graph['gitems'] as &$item) { 1644 $hostid = $this->referencer->findTemplateidOrHostidByHost($item['item']['host']); 1645 $item['itemid'] = ($hostid !== null) 1646 ? $this->referencer->findItemidByKey($hostid, $item['item']['key']) 1647 : null; 1648 1649 if ($item['itemid'] === null) { 1650 throw new Exception(_s( 1651 'Cannot find item "%1$s" on "%2$s" used in graph "%3$s".', 1652 $item['item']['key'], 1653 $item['item']['host'], 1654 $graph['name'] 1655 )); 1656 } 1657 } 1658 unset($item); 1659 1660 if ($this->isTemplateGraph($graph)) { 1661 $graphid = $this->referencer->findGraphidByUuid($graph['uuid']); 1662 } 1663 else { 1664 unset($graph['uuid']); 1665 $graphid = $this->referencer->findGraphidByName($hostid, $graph['name']); 1666 } 1667 1668 if ($graphid !== null) { 1669 $graph['graphid'] = $graphid; 1670 unset($graph['uuid']); 1671 $graphs_to_update[] = $graph; 1672 } 1673 else { 1674 $graphs_to_create[] = $graph; 1675 } 1676 } 1677 1678 if ($this->options['graphs']['updateExisting'] && $graphs_to_update) { 1679 API::Graph()->update($graphs_to_update); 1680 } 1681 1682 if ($this->options['graphs']['createMissing'] && $graphs_to_create) { 1683 API::Graph()->create($graphs_to_create); 1684 } 1685 1686 $this->referencer->refreshGraphs(); 1687 } 1688 1689 private function isTemplateGraph(array $graph): bool { 1690 if ($graph['ymin_item_1'] && $this->referencer->findTemplateidByHost($graph['ymin_item_1']['host'])) { 1691 return true; 1692 } 1693 1694 if ($graph['ymax_item_1'] && $this->referencer->findTemplateidByHost($graph['ymax_item_1']['host'])) { 1695 return true; 1696 } 1697 1698 if (array_key_exists('gitems', $graph) && $graph['gitems']) { 1699 foreach ($graph['gitems'] as $gitem) { 1700 if ($this->referencer->findTemplateidByHost($gitem['item']['host'])) { 1701 return true; 1702 } 1703 } 1704 } 1705 1706 return false; 1707 } 1708 1709 /** 1710 * Import triggers. 1711 * 1712 * @throws Exception 1713 */ 1714 protected function processTriggers(): void { 1715 if (!$this->options['triggers']['createMissing'] && !$this->options['triggers']['updateExisting']) { 1716 return; 1717 } 1718 1719 $triggers_to_create = []; 1720 $triggers_to_update = []; 1721 1722 $triggers_to_process_dependencies = []; 1723 1724 foreach ($this->getFormattedTriggers() as $trigger) { 1725 $triggerid = null; 1726 1727 $is_template_trigger = $this->isTemplateTrigger($trigger); 1728 1729 if ($is_template_trigger && array_key_exists('uuid', $trigger)) { 1730 $triggerid = $this->referencer->findTriggeridByUuid($trigger['uuid']); 1731 } 1732 elseif (!$is_template_trigger) { 1733 unset($trigger['uuid']); 1734 $triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'], 1735 $trigger['recovery_expression'] 1736 ); 1737 } 1738 1739 if ($triggerid !== null) { 1740 if ($this->options['triggers']['updateExisting']) { 1741 $triggers_to_process_dependencies[] = $trigger; 1742 1743 $trigger['triggerid'] = $triggerid; 1744 unset($trigger['dependencies'], $trigger['uuid']); 1745 $triggers_to_update[] = $trigger; 1746 } 1747 } 1748 else { 1749 if ($this->options['triggers']['createMissing']) { 1750 $triggers_to_process_dependencies[] = $trigger; 1751 1752 unset($trigger['dependencies']); 1753 $triggers_to_create[] = $trigger; 1754 } 1755 } 1756 } 1757 1758 if ($triggers_to_update) { 1759 API::Trigger()->update($triggers_to_update); 1760 } 1761 1762 if ($triggers_to_create) { 1763 API::Trigger()->create($triggers_to_create); 1764 } 1765 1766 // Refresh triggers because template triggers can be inherited to host and used in maps. 1767 $this->referencer->refreshTriggers(); 1768 1769 $this->processTriggerDependencies($triggers_to_process_dependencies); 1770 } 1771 1772 private function isTemplateTrigger(array $trigger): bool { 1773 $expression_parser = new CExpressionParser(['usermacros' => true]); 1774 1775 if ($expression_parser->parse($trigger['expression']) != CParser::PARSE_SUCCESS) { 1776 return false; 1777 } 1778 1779 foreach ($expression_parser->getResult()->getHosts() as $host) { 1780 $host = $this->referencer->findTemplateidByHost($host); 1781 1782 if ($host !== null) { 1783 return true; 1784 } 1785 } 1786 1787 if ($trigger['recovery_expression'] === '' 1788 || $expression_parser->parse($trigger['recovery_expression']) != CParser::PARSE_SUCCESS) { 1789 return false; 1790 } 1791 1792 foreach ($expression_parser->getResult()->getHosts() as $host) { 1793 $host = $this->referencer->findTemplateidByHost($host); 1794 1795 if ($host !== null) { 1796 return true; 1797 } 1798 } 1799 1800 return false; 1801 } 1802 1803 /** 1804 * Update trigger dependencies 1805 * 1806 * @param array $triggers 1807 * 1808 * @throws Exception 1809 */ 1810 protected function processTriggerDependencies(array $triggers): void { 1811 $trigger_dependencies = []; 1812 1813 foreach ($triggers as $trigger) { 1814 if (!array_key_exists('dependencies', $trigger)) { 1815 continue; 1816 } 1817 1818 $triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'], 1819 $trigger['recovery_expression'] 1820 ); 1821 1822 $dependencies = []; 1823 1824 foreach ($trigger['dependencies'] as $dependency) { 1825 $dependent_triggerid = $this->referencer->findTriggeridByName($dependency['name'], 1826 $dependency['expression'], $dependency['recovery_expression'] 1827 ); 1828 1829 if ($dependent_triggerid === null) { 1830 throw new Exception(_s('Trigger "%1$s" depends on trigger "%2$s", which does not exist.', 1831 $trigger['description'], 1832 $dependency['name'] 1833 )); 1834 } 1835 1836 $dependencies[] = ['triggerid' => $dependent_triggerid]; 1837 } 1838 1839 $trigger_dependencies[] = [ 1840 'triggerid' => $triggerid, 1841 'dependencies' => $dependencies 1842 ]; 1843 } 1844 1845 if ($trigger_dependencies) { 1846 API::Trigger()->update($trigger_dependencies); 1847 } 1848 } 1849 1850 /** 1851 * Import images. 1852 * 1853 * @throws Exception 1854 */ 1855 protected function processImages(): void { 1856 if (!$this->options['images']['updateExisting'] && !$this->options['images']['createMissing']) { 1857 return; 1858 } 1859 1860 $images_to_import = $this->getFormattedImages(); 1861 1862 if (!$images_to_import) { 1863 return; 1864 } 1865 1866 $images_to_update = []; 1867 $images_to_create = []; 1868 1869 foreach ($images_to_import as $image) { 1870 $imageid = $this->referencer->findImageidByName($image['name']); 1871 1872 if ($imageid !== null) { 1873 $image['imageid'] = $imageid; 1874 unset($image['imagetype']); 1875 $images_to_update[] = $image; 1876 } 1877 else { 1878 $images_to_create[] = $image; 1879 } 1880 } 1881 1882 if ($this->options['images']['updateExisting'] && $images_to_update) { 1883 API::Image()->update($images_to_update); 1884 } 1885 1886 if ($this->options['images']['createMissing'] && $images_to_create) { 1887 $created_images = API::Image()->create($images_to_create); 1888 1889 foreach ($images_to_create as $index => $image) { 1890 $this->referencer->setDbImage($created_images['imageids'][$index], $image); 1891 } 1892 } 1893 } 1894 1895 /** 1896 * Import maps. 1897 * 1898 * @throws Exception 1899 */ 1900 protected function processMaps(): void { 1901 if ($this->options['maps']['updateExisting'] || $this->options['maps']['createMissing']) { 1902 $maps = $this->getFormattedMaps(); 1903 1904 if ($maps) { 1905 $map_importer = new CMapImporter($this->options, $this->referencer, $this->importedObjectContainer); 1906 $map_importer->import($maps); 1907 } 1908 } 1909 } 1910 1911 /** 1912 * Import template dashboards. 1913 */ 1914 protected function processTemplateDashboards(): void { 1915 if ($this->options['templateDashboards']['updateExisting'] 1916 || $this->options['templateDashboards']['createMissing']) { 1917 $dashboards = $this->getFormattedTemplateDashboards(); 1918 1919 if ($dashboards) { 1920 $dashboard_importer = new CTemplateDashboardImporter($this->options, $this->referencer, 1921 $this->importedObjectContainer 1922 ); 1923 1924 $dashboard_importer->import($dashboards); 1925 } 1926 } 1927 } 1928 1929 /** 1930 * Import media types. 1931 */ 1932 protected function processMediaTypes(): void { 1933 if (!$this->options['mediaTypes']['updateExisting'] && !$this->options['mediaTypes']['createMissing']) { 1934 return; 1935 } 1936 1937 $media_types_to_import = $this->getFormattedMediaTypes(); 1938 1939 if (!$media_types_to_import) { 1940 return; 1941 } 1942 1943 $media_types_to_import = zbx_toHash($media_types_to_import, 'name'); 1944 1945 $db_media_types = API::MediaType()->get([ 1946 'output' => ['mediatypeid', 'name'], 1947 'filter' => ['name' => array_keys($media_types_to_import)] 1948 ]); 1949 $db_media_types = zbx_toHash($db_media_types, 'name'); 1950 1951 $media_types_to_update = []; 1952 $media_types_to_create = []; 1953 1954 foreach ($media_types_to_import as $name => $media_type) { 1955 if (array_key_exists($name, $db_media_types)) { 1956 $media_type['mediatypeid'] = $db_media_types[$name]['mediatypeid']; 1957 $media_types_to_update[] = $media_type; 1958 } 1959 else { 1960 $media_types_to_create[] = $media_type; 1961 } 1962 } 1963 1964 if ($this->options['mediaTypes']['updateExisting'] && $media_types_to_update) { 1965 API::MediaType()->update($media_types_to_update); 1966 } 1967 1968 if ($this->options['mediaTypes']['createMissing'] && $media_types_to_create) { 1969 API::MediaType()->create($media_types_to_create); 1970 } 1971 } 1972 1973 /** 1974 * Deletes items from DB that are missing in import file. 1975 */ 1976 protected function deleteMissingItems(): void { 1977 if (!$this->options['items']['deleteMissing']) { 1978 return; 1979 } 1980 1981 $processed_hostids = array_merge( 1982 $this->importedObjectContainer->getHostids(), 1983 $this->importedObjectContainer->getTemplateids() 1984 ); 1985 1986 if (!$processed_hostids) { 1987 return; 1988 } 1989 1990 $itemids = []; 1991 1992 foreach ($this->getFormattedItems() as $host => $items) { 1993 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 1994 1995 if ($hostid === null) { 1996 continue; 1997 } 1998 1999 foreach ($items as $item) { 2000 $itemid = array_key_exists('uuid', $item) 2001 ? $this->referencer->findItemidByUuid($item['uuid']) 2002 : $this->referencer->findItemidByKey($hostid, $item['key_']); 2003 2004 if ($itemid) { 2005 $itemids[$itemid] = []; 2006 } 2007 } 2008 } 2009 2010 $db_itemids = API::Item()->get([ 2011 'output' => ['itemid'], 2012 'hostids' => $processed_hostids, 2013 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL], 2014 'inherited' => false, 2015 'preservekeys' => true, 2016 'nopermissions' => true 2017 ]); 2018 2019 $items_to_delete = array_diff_key($db_itemids, $itemids); 2020 2021 if ($items_to_delete) { 2022 API::Item()->delete(array_keys($items_to_delete)); 2023 } 2024 2025 $this->referencer->refreshItems(); 2026 } 2027 2028 /** 2029 * Deletes triggers from DB that are missing in import file. 2030 */ 2031 protected function deleteMissingTriggers(): void { 2032 if (!$this->options['triggers']['deleteMissing']) { 2033 return; 2034 } 2035 2036 $processed_hostids = array_merge( 2037 $this->importedObjectContainer->getHostids(), 2038 $this->importedObjectContainer->getTemplateids() 2039 ); 2040 2041 if (!$processed_hostids) { 2042 return; 2043 } 2044 2045 $triggerids = []; 2046 2047 foreach ($this->getFormattedTriggers() as $trigger) { 2048 $triggerid = null; 2049 2050 if (array_key_exists('uuid', $trigger)) { 2051 $triggerid = $this->referencer->findTriggeridByUuid($trigger['uuid']); 2052 } 2053 2054 // In import file host trigger can have UUID assigned after conversion, such should be searched by name. 2055 if ($triggerid === null) { 2056 $triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'], 2057 $trigger['recovery_expression'] 2058 ); 2059 2060 // Template triggers should only be searched by UUID. 2061 if ($triggerid !== null && array_key_exists('uuid', $trigger)) { 2062 $db_trigger = $this->referencer->findTriggerById($triggerid); 2063 2064 if ($db_trigger['uuid'] !== '' && $db_trigger['uuid'] !== $trigger['uuid']) { 2065 $triggerid = null; 2066 } 2067 } 2068 } 2069 2070 if ($triggerid !== null) { 2071 $triggerids[$triggerid] = []; 2072 } 2073 } 2074 2075 $db_triggerids = API::Trigger()->get([ 2076 'output' => [], 2077 'selectHosts' => ['hostid'], 2078 'hostids' => $processed_hostids, 2079 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL], 2080 'inherited' => false, 2081 'preservekeys' => true, 2082 'nopermissions' => true 2083 ]); 2084 2085 // Check that potentially deletable trigger belongs to same hosts that are in the import file. 2086 // If some triggers belong to more hosts than import file contains, don't delete them. 2087 $triggerids_to_delete = []; 2088 $processed_hostids = array_flip($processed_hostids); 2089 2090 foreach (array_diff_key($db_triggerids, $triggerids) as $triggerid => $trigger) { 2091 $trigger_hostids = array_flip(array_column($trigger['hosts'], 'hostid')); 2092 2093 if (!array_diff_key($trigger_hostids, $processed_hostids)) { 2094 $triggerids_to_delete[] = $triggerid; 2095 } 2096 } 2097 2098 if ($triggerids_to_delete) { 2099 API::Trigger()->delete($triggerids_to_delete); 2100 } 2101 2102 // refresh triggers because template triggers can be inherited to host and used in maps 2103 $this->referencer->refreshTriggers(); 2104 } 2105 2106 /** 2107 * Deletes graphs from DB that are missing in import file. 2108 */ 2109 protected function deleteMissingGraphs(): void { 2110 if (!$this->options['graphs']['deleteMissing']) { 2111 return; 2112 } 2113 2114 $processed_hostids = array_merge( 2115 $this->importedObjectContainer->getHostids(), 2116 $this->importedObjectContainer->getTemplateids() 2117 ); 2118 2119 if (!$processed_hostids) { 2120 return; 2121 } 2122 2123 $graphids = []; 2124 2125 foreach ($this->getFormattedGraphs() as $graph) { 2126 $graphid = null; 2127 2128 if (array_key_exists('uuid', $graph)) { 2129 $graphid = $this->referencer->findGraphidByUuid($graph['uuid']); 2130 } 2131 2132 if ($graphid !== null) { 2133 $graphids[$graphid] = []; 2134 } 2135 elseif (array_key_exists('gitems', $graph)) { 2136 // In import file host graph can have UUID assigned after conversion, such should be searched by name. 2137 foreach ($graph['gitems'] as $gitem) { 2138 $gitem_hostid = $this->referencer->findTemplateidOrHostidByHost($gitem['item']['host']); 2139 $graphid = $this->referencer->findGraphidByName($gitem_hostid, $graph['name']); 2140 2141 if ($graphid !== null) { 2142 $graphids[$graphid] = []; 2143 } 2144 } 2145 } 2146 } 2147 2148 $db_graphids = API::Graph()->get([ 2149 'output' => ['graphid'], 2150 'hostids' => $processed_hostids, 2151 'selectHosts' => ['hostid'], 2152 'preservekeys' => true, 2153 'nopermissions' => true, 2154 'inherited' => false, 2155 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL] 2156 ]); 2157 2158 // check that potentially deletable graph belongs to same hosts that are in XML 2159 // if some graphs belong to more hosts than current XML contains, don't delete them 2160 $graphids_to_delete = []; 2161 $processed_hostids = array_flip($processed_hostids); 2162 2163 foreach (array_diff_key($db_graphids, $graphids) as $graphid => $graph) { 2164 $graph_hostids = array_flip(array_column($graph['hosts'], 'hostid')); 2165 2166 if (!array_diff_key($graph_hostids, $processed_hostids)) { 2167 $graphids_to_delete[] = $graphid; 2168 } 2169 } 2170 2171 if ($graphids_to_delete) { 2172 API::Graph()->delete($graphids_to_delete); 2173 } 2174 2175 $this->referencer->refreshGraphs(); 2176 } 2177 2178 /** 2179 * Deletes prototypes from DB that are missing in import file. 2180 * 2181 * @param array $discovery_rules_by_hosts 2182 * 2183 * @throws APIException 2184 */ 2185 protected function deleteMissingPrototypes(array $discovery_rules_by_hosts): void { 2186 $discovery_ruleids = []; 2187 $host_prototypeids = []; 2188 $trigger_prototypeids = []; 2189 $graph_prototypeids = []; 2190 $item_prototypeids = []; 2191 2192 foreach ($discovery_rules_by_hosts as $host => $discovery_rules) { 2193 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 2194 2195 foreach ($discovery_rules as $discovery_rule) { 2196 $discoveryid = array_key_exists('uuid', $discovery_rule) 2197 ? $this->referencer->findItemidByUuid($discovery_rule['uuid']) 2198 : $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']); 2199 2200 if ($discoveryid === null) { 2201 continue; 2202 } 2203 2204 $discovery_ruleids[$discoveryid] = []; 2205 2206 foreach ($discovery_rule['host_prototypes'] as $host_prototype) { 2207 $host_prototypeid = array_key_exists('uuid', $host_prototype) 2208 ? $this->referencer->findHostPrototypeidByUuid($host_prototype['uuid']) 2209 : $this->referencer->findHostPrototypeidByHost($hostid, $discoveryid, $host_prototype['host']); 2210 2211 if ($host_prototypeid !== null) { 2212 $host_prototypeids[$host_prototypeid] = []; 2213 } 2214 } 2215 2216 foreach ($discovery_rule['trigger_prototypes'] as $trigger_prototype) { 2217 $trigger_prototypeid = array_key_exists('uuid', $trigger_prototype) 2218 ? $this->referencer->findTriggeridByUuid($trigger_prototype['uuid']) 2219 : $this->referencer->findTriggeridByName($trigger_prototype['description'], 2220 $trigger_prototype['expression'], $trigger_prototype['recovery_expression'] 2221 ); 2222 2223 if ($trigger_prototypeid !== null) { 2224 $trigger_prototypeids[$trigger_prototypeid] = []; 2225 } 2226 } 2227 2228 foreach ($discovery_rule['graph_prototypes'] as $graph_prototype) { 2229 $graph_prototypeid = array_key_exists('uuid', $graph_prototype) 2230 ? $this->referencer->findGraphidByUuid($graph_prototype['uuid']) 2231 : $this->referencer->findGraphidByName($hostid, $graph_prototype['name']); 2232 2233 if ($graph_prototypeid !== null) { 2234 $graph_prototypeids[$graph_prototypeid] = []; 2235 } 2236 } 2237 2238 foreach ($discovery_rule['item_prototypes'] as $item_prototype) { 2239 $item_prototypeid = array_key_exists('uuid', $item_prototype) 2240 ? $this->referencer->findItemidByUuid($item_prototype['uuid']) 2241 : $this->referencer->findItemidByKey($hostid, $item_prototype['key_']); 2242 2243 if ($item_prototypeid !== null) { 2244 $item_prototypeids[$item_prototypeid] = []; 2245 } 2246 } 2247 } 2248 } 2249 2250 $db_host_prototypes = API::HostPrototype()->get([ 2251 'output' => [], 2252 'discoveryids' => array_keys($discovery_ruleids), 2253 'preservekeys' => true, 2254 'nopermissions' => true, 2255 'inherited' => false 2256 ]); 2257 2258 $host_prototypes_to_delete = array_diff_key($db_host_prototypes, $host_prototypeids); 2259 2260 if ($host_prototypes_to_delete) { 2261 API::HostPrototype()->delete(array_keys($host_prototypes_to_delete)); 2262 } 2263 2264 $db_trigger_prototypes = API::TriggerPrototype()->get([ 2265 'output' => [], 2266 'discoveryids' => array_keys($discovery_ruleids), 2267 'preservekeys' => true, 2268 'nopermissions' => true, 2269 'inherited' => false 2270 ]); 2271 2272 $trigger_prototypes_to_delete = array_diff_key($db_trigger_prototypes, $trigger_prototypeids); 2273 2274 // Unlike triggers that belong to multiple hosts, trigger prototypes do not, so we just delete them. 2275 if ($trigger_prototypes_to_delete) { 2276 API::TriggerPrototype()->delete(array_keys($trigger_prototypes_to_delete)); 2277 2278 $this->referencer->refreshTriggers(); 2279 } 2280 2281 $db_graph_prototypes = API::GraphPrototype()->get([ 2282 'output' => [], 2283 'discoveryids' => array_keys($discovery_ruleids), 2284 'preservekeys' => true, 2285 'nopermissions' => true, 2286 'inherited' => false 2287 ]); 2288 2289 $graph_prototypes_to_delete = array_diff_key($db_graph_prototypes, $graph_prototypeids); 2290 2291 // Unlike graphs that belong to multiple hosts, graph prototypes do not, so we just delete them. 2292 if ($graph_prototypes_to_delete) { 2293 API::GraphPrototype()->delete(array_keys($graph_prototypes_to_delete)); 2294 2295 $this->referencer->refreshGraphs(); 2296 } 2297 2298 $db_item_prototypes = API::ItemPrototype()->get([ 2299 'output' => [], 2300 'discoveryids' => array_keys($discovery_ruleids), 2301 'preservekeys' => true, 2302 'nopermissions' => true, 2303 'inherited' => false 2304 ]); 2305 2306 $item_prototypes_to_delete = array_diff_key($db_item_prototypes, $item_prototypeids); 2307 2308 if ($item_prototypes_to_delete) { 2309 API::ItemPrototype()->delete(array_keys($item_prototypes_to_delete)); 2310 2311 $this->referencer->refreshItems(); 2312 } 2313 } 2314 2315 /** 2316 * Deletes web scenarios from DB that are missing in import file. 2317 */ 2318 protected function deleteMissingHttpTests(): void { 2319 if (!$this->options['httptests']['deleteMissing']) { 2320 return; 2321 } 2322 2323 $processed_hostids = array_merge( 2324 $this->importedObjectContainer->getHostids(), 2325 $this->importedObjectContainer->getTemplateids() 2326 ); 2327 2328 if (!$processed_hostids) { 2329 return; 2330 } 2331 2332 $httptestids = []; 2333 2334 foreach ($this->getFormattedHttpTests() as $host => $httptests) { 2335 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 2336 2337 if ($hostid === null) { 2338 continue; 2339 } 2340 2341 foreach ($httptests as $httptest) { 2342 $httptestid = array_key_exists('uuid', $httptest) 2343 ? $this->referencer->findHttpTestidByUuid($httptest['uuid']) 2344 : $this->referencer->findHttpTestidByName($hostid, $httptest['name']); 2345 2346 if ($httptestid !== null) { 2347 $httptestids[$httptestid] = []; 2348 } 2349 } 2350 } 2351 2352 $db_httptestids = API::HttpTest()->get([ 2353 'output' => [], 2354 'hostids' => $processed_hostids, 2355 'inherited' => false, 2356 'preservekeys' => true, 2357 'nopermissions' => true 2358 ]); 2359 2360 $httptestids_to_delete = array_diff_key($db_httptestids, $httptestids); 2361 2362 if ($httptestids_to_delete) { 2363 API::HttpTest()->delete(array_keys($httptestids_to_delete)); 2364 } 2365 2366 $this->referencer->refreshHttpTests(); 2367 } 2368 2369 /** 2370 * Deletes template dashboards from DB that are missing in import file. 2371 * 2372 * @throws APIException 2373 */ 2374 protected function deleteMissingTemplateDashboards(): void { 2375 if (!$this->options['templateDashboards']['deleteMissing']) { 2376 return; 2377 } 2378 2379 $dashboards = $this->getFormattedTemplateDashboards(); 2380 2381 if ($dashboards) { 2382 $dashboard_importer = new CTemplateDashboardImporter($this->options, $this->referencer, 2383 $this->importedObjectContainer 2384 ); 2385 2386 $dashboard_importer->delete($dashboards); 2387 } 2388 } 2389 2390 /** 2391 * Deletes discovery rules from DB that are missing in import file. 2392 */ 2393 protected function deleteMissingDiscoveryRules(): void { 2394 if (!$this->options['discoveryRules']['deleteMissing']) { 2395 return; 2396 } 2397 2398 $processed_hostids = array_merge( 2399 $this->importedObjectContainer->getHostids(), 2400 $this->importedObjectContainer->getTemplateids() 2401 ); 2402 2403 if (!$processed_hostids) { 2404 return; 2405 } 2406 2407 $discovery_ruleids = []; 2408 2409 foreach ($this->getFormattedDiscoveryRules() as $host => $discovery_rules) { 2410 $hostid = $this->referencer->findTemplateidOrHostidByHost($host); 2411 2412 if ($hostid === null) { 2413 continue; 2414 } 2415 2416 foreach ($discovery_rules as $discovery_rule) { 2417 $discovery_ruleid = array_key_exists('uuid', $discovery_rule) 2418 ? $this->referencer->findItemidByUuid($discovery_rule['uuid']) 2419 : $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']); 2420 2421 if ($discovery_ruleid !== null) { 2422 $discovery_ruleids[$discovery_ruleid] = []; 2423 } 2424 } 2425 } 2426 2427 $db_discovery_ruleids = API::DiscoveryRule()->get([ 2428 'output' => ['itemid'], 2429 'hostids' => $processed_hostids, 2430 'inherited' => false, 2431 'preservekeys' => true, 2432 'nopermissions' => true 2433 ]); 2434 2435 $discovery_ruleids_to_delete = array_diff_key($db_discovery_ruleids, $discovery_ruleids); 2436 2437 if ($discovery_ruleids_to_delete) { 2438 API::DiscoveryRule()->delete(array_keys($discovery_ruleids_to_delete)); 2439 } 2440 2441 $this->referencer->refreshItems(); 2442 } 2443 2444 /** 2445 * Get formatted groups. 2446 * 2447 * @return array 2448 */ 2449 protected function getFormattedGroups(): array { 2450 if (!array_key_exists('groups', $this->formattedData)) { 2451 $this->formattedData['groups'] = $this->adapter->getGroups(); 2452 } 2453 2454 return $this->formattedData['groups']; 2455 } 2456 2457 /** 2458 * Get formatted templates. 2459 * 2460 * @return array 2461 */ 2462 public function getFormattedTemplates(): array { 2463 if (!array_key_exists('templates', $this->formattedData)) { 2464 $this->formattedData['templates'] = $this->adapter->getTemplates(); 2465 } 2466 2467 return $this->formattedData['templates']; 2468 } 2469 2470 /** 2471 * Get formatted hosts. 2472 * 2473 * @return array 2474 */ 2475 public function getFormattedHosts(): array { 2476 if (!array_key_exists('hosts', $this->formattedData)) { 2477 $this->formattedData['hosts'] = $this->adapter->getHosts(); 2478 } 2479 2480 return $this->formattedData['hosts']; 2481 } 2482 2483 /** 2484 * Get formatted items. 2485 * 2486 * @return array 2487 */ 2488 protected function getFormattedItems(): array { 2489 if (!array_key_exists('items', $this->formattedData)) { 2490 $this->formattedData['items'] = $this->adapter->getItems(); 2491 } 2492 2493 return $this->formattedData['items']; 2494 } 2495 2496 /** 2497 * Get formatted discovery rules. 2498 * 2499 * @return array 2500 */ 2501 protected function getFormattedDiscoveryRules(): array { 2502 if (!array_key_exists('discoveryRules', $this->formattedData)) { 2503 $this->formattedData['discoveryRules'] = $this->adapter->getDiscoveryRules(); 2504 } 2505 2506 return $this->formattedData['discoveryRules']; 2507 } 2508 2509 /** 2510 * Get formatted web scenarios. 2511 * 2512 * @return array 2513 */ 2514 protected function getFormattedHttpTests(): array { 2515 if (!array_key_exists('httptests', $this->formattedData)) { 2516 $this->formattedData['httptests'] = $this->adapter->getHttpTests(); 2517 } 2518 2519 return $this->formattedData['httptests']; 2520 } 2521 2522 /** 2523 * Get formatted web scenario steps. 2524 * 2525 * @return array 2526 */ 2527 protected function getFormattedHttpSteps(): array { 2528 if (!array_key_exists('httpsteps', $this->formattedData)) { 2529 $this->formattedData['httpsteps'] = $this->adapter->getHttpSteps(); 2530 } 2531 2532 return $this->formattedData['httpsteps']; 2533 } 2534 2535 /** 2536 * Get formatted triggers. 2537 * 2538 * @return array 2539 */ 2540 protected function getFormattedTriggers(): array { 2541 if (!array_key_exists('triggers', $this->formattedData)) { 2542 $this->formattedData['triggers'] = $this->adapter->getTriggers(); 2543 } 2544 2545 return $this->formattedData['triggers']; 2546 } 2547 2548 /** 2549 * Get formatted graphs. 2550 * 2551 * @return array 2552 */ 2553 protected function getFormattedGraphs(): array { 2554 if (!array_key_exists('graphs', $this->formattedData)) { 2555 $this->formattedData['graphs'] = $this->adapter->getGraphs(); 2556 } 2557 2558 return $this->formattedData['graphs']; 2559 } 2560 2561 /** 2562 * Get formatted images. 2563 * 2564 * @return array 2565 */ 2566 protected function getFormattedImages(): array { 2567 if (!array_key_exists('images', $this->formattedData)) { 2568 $this->formattedData['images'] = $this->adapter->getImages(); 2569 } 2570 2571 return $this->formattedData['images']; 2572 } 2573 2574 /** 2575 * Get formatted maps. 2576 * 2577 * @return array 2578 */ 2579 protected function getFormattedMaps(): array { 2580 if (!array_key_exists('maps', $this->formattedData)) { 2581 $this->formattedData['maps'] = $this->adapter->getMaps(); 2582 } 2583 2584 return $this->formattedData['maps']; 2585 } 2586 2587 /** 2588 * Get formatted template dashboards. 2589 * 2590 * @return array 2591 */ 2592 protected function getFormattedTemplateDashboards(): array { 2593 if (!array_key_exists('templateDashboards', $this->formattedData)) { 2594 $this->formattedData['templateDashboards'] = $this->adapter->getTemplateDashboards(); 2595 } 2596 2597 return $this->formattedData['templateDashboards']; 2598 } 2599 2600 /** 2601 * Get formatted media types. 2602 * 2603 * @return array 2604 */ 2605 protected function getFormattedMediaTypes(): array { 2606 if (!array_key_exists('mediaTypes', $this->formattedData)) { 2607 $this->formattedData['mediaTypes'] = $this->adapter->getMediaTypes(); 2608 } 2609 2610 return $this->formattedData['mediaTypes']; 2611 } 2612 2613 /** 2614 * Get items keys order tree, to ensure that master item will be inserted or updated before any of it dependent 2615 * item. Returns associative array where key is item index and value is item dependency level. 2616 * 2617 * @param string $master_item_key String containing master key name used to identify item master. 2618 * 2619 * @return array 2620 * 2621 * @throws Exception 2622 */ 2623 protected function getItemsOrder(string $master_item_key): array { 2624 $entities = $this->getFormattedItems(); 2625 2626 return $this->getEntitiesOrder($entities, $master_item_key); 2627 } 2628 2629 /** 2630 * Get discovery rules items prototypes keys order tree, to ensure that master item will be inserted or updated 2631 * before any of it dependent item. Returns associative array where key is item prototype index and value is item 2632 * prototype dependency level. 2633 * 2634 * @param string $master_item_key String containing master key name used to identify item master. 2635 * 2636 * @return array 2637 * 2638 * @throws Exception 2639 */ 2640 protected function getDiscoveryRulesItemsOrder(string $master_item_key): array { 2641 $discovery_rules_by_hosts = $this->getFormattedDiscoveryRules(); 2642 $entities_order = []; 2643 2644 foreach ($discovery_rules_by_hosts as $host => $discovery_rules) { 2645 foreach ($discovery_rules as $discovery_rule) { 2646 if ($discovery_rule['item_prototypes']) { 2647 $item_prototypes = [$host => $discovery_rule['item_prototypes']]; 2648 $item_prototypes = $this->getEntitiesOrder($item_prototypes, $master_item_key, true); 2649 $entities_order[$host][$discovery_rule['key_']] = $item_prototypes[$host]; 2650 } 2651 } 2652 } 2653 2654 return $entities_order; 2655 } 2656 2657 /** 2658 * Generic method to get entities order tree, to ensure that master entity will be inserted or updated before any 2659 * of it dependent entities. 2660 * Returns associative array where key is entity index in source array grouped by host key and value is entity 2661 * dependency level. 2662 * 2663 * @param array $items_by_hosts Associative array of host key and host items. 2664 * @param string $master_item_key String containing master key name to identify item master. 2665 * @param bool $get_prototypes Option to get also master item prototypes not found in supplied input. 2666 * 2667 * @return array 2668 * 2669 * @throws Exception if data is invalid. 2670 */ 2671 protected function getEntitiesOrder(array $items_by_hosts, string $master_item_key, 2672 bool $get_prototypes = false): array { 2673 $parent_item_hostids = []; 2674 $parent_item_keys = []; 2675 $resolved_masters_cache = []; 2676 2677 $host_name_to_hostid = array_fill_keys(array_keys($items_by_hosts), null); 2678 2679 foreach ($host_name_to_hostid as $host_name => &$hostid) { 2680 $hostid = $this->referencer->findTemplateidOrHostidByHost($host_name); 2681 } 2682 unset($hostid); 2683 2684 foreach ($items_by_hosts as $host_name => $items) { 2685 if (!array_key_exists($host_name, $host_name_to_hostid)) { 2686 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'host', 2687 _s('value "%1$s" not found', $host_name) 2688 )); 2689 } 2690 2691 if (!array_key_exists($host_name, $resolved_masters_cache)) { 2692 $resolved_masters_cache[$host_name] = []; 2693 } 2694 2695 // Cache input array entities. 2696 foreach ($items as $item) { 2697 $resolved_masters_cache[$host_name][$item['key_']] = [ 2698 'type' => $item['type'], 2699 $master_item_key => $item[$master_item_key] 2700 ]; 2701 2702 if ($item['type'] == ITEM_TYPE_DEPENDENT && array_key_exists('key', $item[$master_item_key])) { 2703 $parent_item_hostids[$host_name_to_hostid[$host_name]] = true; 2704 $parent_item_keys[$item[$master_item_key]['key']] = true; 2705 } 2706 } 2707 } 2708 2709 // There are entities to resolve from database, resolve and cache them recursively. 2710 if ($parent_item_keys) { 2711 /* 2712 * For existing items, 'referencer' should be initialized before 'setDbItem' method will be used. 2713 * Registering reference when property 'db_items' is empty, will not allow first call of 2714 * 'findValueMapidByName' method update references to existing items. 2715 */ 2716 $this->referencer->initItemsReferences(); 2717 2718 $options = [ 2719 'output' => ['itemid', 'uuid', 'key_', 'type', 'hostid', 'master_itemid'], 2720 'hostids' => array_keys($parent_item_hostids), 2721 'filter' => ['key_' => array_keys($parent_item_keys)], 2722 'preservekeys' => true 2723 ]; 2724 2725 $db_items = API::Item()->get($options + ['webitems' => true]); 2726 2727 if ($get_prototypes) { 2728 $db_items += API::ItemPrototype()->get($options); 2729 } 2730 2731 $resolve_entity_keys = []; 2732 $itemid_to_item_key_by_hosts = []; 2733 2734 for ($level = 0; $level < ZBX_DEPENDENT_ITEM_MAX_LEVELS; $level++) { 2735 $missing_master_itemids = []; 2736 2737 foreach ($db_items as $itemid => $item) { 2738 $host_name = array_search($item['hostid'], $host_name_to_hostid); 2739 2740 $this->referencer->setDbItem($itemid, $item); 2741 2742 $item['key'] = $item['key_']; 2743 unset($item['key_']); 2744 2745 $itemid_to_item_key_by_hosts[$host_name][$itemid] = $item['key']; 2746 2747 $cache_entity = [ 2748 'type' => $item['type'] 2749 ]; 2750 2751 if ($item['type'] == ITEM_TYPE_DEPENDENT) { 2752 $master_itemid = $item['master_itemid']; 2753 2754 if (array_key_exists($master_itemid, $itemid_to_item_key_by_hosts[$host_name])) { 2755 $cache_entity[$master_item_key] = [ 2756 'key' => $itemid_to_item_key_by_hosts[$host_name][$master_itemid] 2757 ]; 2758 } 2759 else { 2760 $missing_master_itemids[] = $item['master_itemid']; 2761 $resolve_entity_keys[] = [ 2762 'host' => $host_name, 2763 'key' => $item['key'], 2764 'master_itemid' => $item['master_itemid'] 2765 ]; 2766 } 2767 } 2768 2769 $resolved_masters_cache[$host_name][$item['key']] = $cache_entity; 2770 } 2771 2772 if ($missing_master_itemids) { 2773 $options = [ 2774 'output' => ['uuid', 'key_', 'type', 'hostid', 'master_itemid'], 2775 'itemids' => $missing_master_itemids, 2776 'preservekeys' => true 2777 ]; 2778 $db_items = API::Item()->get($options + ['webitems' => true]); 2779 2780 if ($get_prototypes) { 2781 $db_items += API::ItemPrototype()->get($options); 2782 } 2783 } 2784 else { 2785 break; 2786 } 2787 } 2788 2789 if ($missing_master_itemids) { 2790 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2791 _('maximum number of dependency levels reached') 2792 )); 2793 } 2794 2795 foreach ($resolve_entity_keys as $item) { 2796 $master_itemid = $item['master_itemid']; 2797 2798 if (!array_key_exists($item['host'], $itemid_to_item_key_by_hosts) || 2799 !array_key_exists($master_itemid, $itemid_to_item_key_by_hosts[$item['host']])) { 2800 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2801 _s('value "%1$s" not found', $master_itemid) 2802 )); 2803 } 2804 2805 $master_key = $itemid_to_item_key_by_hosts[$item['host']][$master_itemid]; 2806 $resolved_masters_cache[$item['host']][$item['key']] += [ 2807 $master_item_key => ['key' => $master_key] 2808 ]; 2809 } 2810 2811 unset($resolve_entity_keys, $itemid_to_item_key_by_hosts); 2812 } 2813 2814 // Resolve every entity dependency level. 2815 $tree = []; 2816 2817 foreach ($items_by_hosts as $host_name => $items) { 2818 $hostid = $host_name_to_hostid[$host_name]; 2819 $host_items_tree = []; 2820 2821 foreach ($items as $index => $item) { 2822 $level = 0; 2823 $traversal_path = [$item['key_']]; 2824 2825 while ($item && $item['type'] == ITEM_TYPE_DEPENDENT) { 2826 if (!array_key_exists('key', $item[$master_item_key])) { 2827 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2828 _('cannot be empty') 2829 )); 2830 } 2831 2832 $master_key = $item[$master_item_key]['key']; 2833 2834 if (array_key_exists($host_name, $resolved_masters_cache) 2835 && array_key_exists($master_key, $resolved_masters_cache[$host_name])) { 2836 $item = $resolved_masters_cache[$host_name][$master_key]; 2837 2838 if (($item['type'] == ITEM_TYPE_DEPENDENT 2839 && $item[$master_item_key] 2840 && $master_key === $item[$master_item_key]['key']) 2841 || in_array($master_key, $traversal_path)) { 2842 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2843 _('circular item dependency is not allowed') 2844 )); 2845 } 2846 2847 $traversal_path[] = $master_key; 2848 $level++; 2849 } 2850 else { 2851 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2852 _s('value "%1$s" not found', $master_key) 2853 )); 2854 } 2855 2856 if ($level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) { 2857 throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2858 _('maximum number of dependency levels reached') 2859 )); 2860 } 2861 } 2862 2863 $host_items_tree[$index] = $level; 2864 } 2865 2866 $tree[$host_name] = $host_items_tree; 2867 } 2868 2869 // Order item indexes in descending order by nesting level. 2870 foreach ($tree as &$item_indexes) { 2871 asort($item_indexes); 2872 } 2873 unset($item_indexes); 2874 2875 return $tree; 2876 } 2877} 2878