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 containing methods for operations with hosts. 24 */ 25abstract class CHostGeneral extends CHostBase { 26 27 /** 28 * Checks if the current user has access to the given hosts and templates. Assumes the "hostid" field is valid. 29 * 30 * @param array $hostids an array of host or template IDs 31 * 32 * @throws APIException if the user doesn't have write permissions for the given hosts. 33 */ 34 protected function checkHostPermissions(array $hostids) { 35 if ($hostids) { 36 $hostids = array_unique($hostids); 37 38 $count = API::Host()->get([ 39 'countOutput' => true, 40 'hostids' => $hostids, 41 'editable' => true 42 ]); 43 44 if ($count == count($hostids)) { 45 return; 46 } 47 48 $count += API::Template()->get([ 49 'countOutput' => true, 50 'templateids' => $hostids, 51 'editable' => true 52 ]); 53 54 if ($count != count($hostids)) { 55 self::exception(ZBX_API_ERROR_PERMISSIONS, 56 _('No permissions to referred object or it does not exist!') 57 ); 58 } 59 } 60 } 61 62 /** 63 * Allows to: 64 * - add hosts to groups; 65 * - link templates to hosts; 66 * - add new macros to hosts. 67 * 68 * Supported $data parameters are: 69 * - hosts - an array of hosts to be updated 70 * - templates - an array of templates to be updated 71 * - groups - an array of host groups to add the host to 72 * - templates_link - an array of templates to link to the hosts 73 * - macros - an array of macros to create on the host 74 * 75 * @param array $data 76 * 77 * @return array 78 */ 79 public function massAdd(array $data) { 80 $hostIds = zbx_objectValues($data['hosts'], 'hostid'); 81 $templateIds = zbx_objectValues($data['templates'], 'templateid'); 82 83 $allHostIds = array_merge($hostIds, $templateIds); 84 85 // add groups 86 if (!empty($data['groups'])) { 87 API::HostGroup()->massAdd([ 88 'hosts' => $data['hosts'], 89 'templates' => $data['templates'], 90 'groups' => $data['groups'] 91 ]); 92 } 93 94 // link templates 95 if (!empty($data['templates_link'])) { 96 $this->checkHostPermissions($allHostIds); 97 98 $this->link(zbx_objectValues(zbx_toArray($data['templates_link']), 'templateid'), $allHostIds); 99 } 100 101 // create macros 102 if (!empty($data['macros'])) { 103 $data['macros'] = zbx_toArray($data['macros']); 104 105 $hostMacrosToAdd = []; 106 foreach ($data['macros'] as $hostMacro) { 107 foreach ($allHostIds as $hostid) { 108 $hostMacro['hostid'] = $hostid; 109 $hostMacrosToAdd[] = $hostMacro; 110 } 111 } 112 113 API::UserMacro()->create($hostMacrosToAdd); 114 } 115 116 $ids = ['hostids' => $hostIds, 'templateids' => $templateIds]; 117 118 return [$this->pkOption() => $ids[$this->pkOption()]]; 119 } 120 121 /** 122 * Allows to: 123 * - remove hosts from groups; 124 * - unlink and clear templates from hosts; 125 * - remove macros from hosts. 126 * 127 * Supported $data parameters are: 128 * - hostids - an array of host IDs to be updated 129 * - templateids - an array of template IDs to be updated 130 * - groupids - an array of host group IDs the hosts should be removed from 131 * - templateids_link - an array of template IDs to unlink from the hosts 132 * - templateids_clear - an array of template IDs to unlink and clear from the hosts 133 * - macros - an array of macros to delete from the hosts 134 * 135 * @param array $data 136 * 137 * @return array 138 */ 139 public function massRemove(array $data) { 140 $allHostIds = array_merge($data['hostids'], $data['templateids']); 141 142 $this->checkHostPermissions($allHostIds); 143 144 if (!empty($data['templateids_link'])) { 145 $this->unlink(zbx_toArray($data['templateids_link']), $allHostIds); 146 } 147 148 if (isset($data['templateids_clear'])) { 149 $this->unlink(zbx_toArray($data['templateids_clear']), $allHostIds, true); 150 } 151 152 if (array_key_exists('macros', $data)) { 153 if (!$data['macros']) { 154 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 155 } 156 157 $hostMacros = API::UserMacro()->get([ 158 'output' => ['hostmacroid'], 159 'hostids' => $allHostIds, 160 'filter' => [ 161 'macro' => $data['macros'] 162 ] 163 ]); 164 $hostMacroIds = zbx_objectValues($hostMacros, 'hostmacroid'); 165 if ($hostMacroIds) { 166 API::UserMacro()->delete($hostMacroIds); 167 } 168 } 169 170 if (isset($data['groupids'])) { 171 API::HostGroup()->massRemove($data); 172 } 173 174 return [$this->pkOption() => $data[$this->pkOption()]]; 175 } 176 177 protected function link(array $templateIds, array $targetIds) { 178 $hosts_linkage_inserts = parent::link($templateIds, $targetIds); 179 $templates_hostids = []; 180 $link_requests = []; 181 182 foreach ($hosts_linkage_inserts as $host_tpl_ids) { 183 $templates_hostids[$host_tpl_ids['templateid']][] = $host_tpl_ids['hostid']; 184 } 185 186 foreach ($templates_hostids as $templateid => $hostids) { 187 Manager::Application()->link($templateid, $hostids); 188 189 // Fist link web items, so that later regular items can use web item as their master item. 190 Manager::HttpTest()->link($templateid, $hostids); 191 } 192 193 while ($templates_hostids) { 194 $templateid = key($templates_hostids); 195 $link_request = [ 196 'hostids' => reset($templates_hostids), 197 'templateids' => [$templateid] 198 ]; 199 unset($templates_hostids[$templateid]); 200 201 foreach ($templates_hostids as $templateid => $hostids) { 202 if ($link_request['hostids'] === $hostids) { 203 $link_request['templateids'][] = $templateid; 204 unset($templates_hostids[$templateid]); 205 } 206 } 207 208 $link_requests[] = $link_request; 209 } 210 211 foreach ($link_requests as $link_request) { 212 API::Item()->syncTemplates($link_request); 213 API::DiscoveryRule()->syncTemplates($link_request); 214 API::ItemPrototype()->syncTemplates($link_request); 215 API::HostPrototype()->syncTemplates($link_request); 216 } 217 218 // we do linkage in two separate loops because for triggers you need all items already created on host 219 foreach ($link_requests as $link_request){ 220 API::Trigger()->syncTemplates($link_request); 221 API::TriggerPrototype()->syncTemplates($link_request); 222 API::GraphPrototype()->syncTemplates($link_request); 223 API::Graph()->syncTemplates($link_request); 224 } 225 226 foreach ($link_requests as $link_request){ 227 API::Trigger()->syncTemplateDependencies($link_request); 228 API::TriggerPrototype()->syncTemplateDependencies($link_request); 229 } 230 231 return $hosts_linkage_inserts; 232 } 233 234 /** 235 * Unlinks the templates from the given hosts. If $targetids is set to null, the templates will be unlinked from 236 * all hosts. 237 * 238 * @param array $templateids 239 * @param null|array $targetids the IDs of the hosts to unlink the templates from 240 * @param bool $clear delete all of the inherited objects from the hosts 241 */ 242 protected function unlink($templateids, $targetids = null, $clear = false) { 243 $flags = ($clear) 244 ? [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE] 245 : [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_RULE, ZBX_FLAG_DISCOVERY_PROTOTYPE]; 246 247 // check that all triggers on templates that we unlink, don't have items from another templates 248 $sql = 'SELECT DISTINCT t.description'. 249 ' FROM triggers t,functions f,items i'. 250 ' WHERE t.triggerid=f.triggerid'. 251 ' AND f.itemid=i.itemid'. 252 ' AND '.dbConditionInt('i.hostid', $templateids). 253 ' AND EXISTS ('. 254 'SELECT ff.triggerid'. 255 ' FROM functions ff,items ii'. 256 ' WHERE ff.itemid=ii.itemid'. 257 ' AND ff.triggerid=t.triggerid'. 258 ' AND '.dbConditionInt('ii.hostid', $templateids, true). 259 ')'. 260 ' AND t.flags='.ZBX_FLAG_DISCOVERY_NORMAL; 261 if ($dbTrigger = DBfetch(DBSelect($sql, 1))) { 262 self::exception(ZBX_API_ERROR_PARAMETERS, 263 _s('Cannot unlink trigger "%1$s", it has items from template that is left linked to host.', 264 $dbTrigger['description'] 265 ) 266 ); 267 } 268 269 $templ_triggerids = []; 270 271 $db_triggers = DBselect( 272 'SELECT DISTINCT f.triggerid'. 273 ' FROM functions f,items i'. 274 ' WHERE f.itemid=i.itemid'. 275 ' AND '.dbConditionInt('i.hostid', $templateids) 276 ); 277 278 while ($db_trigger = DBfetch($db_triggers)) { 279 $templ_triggerids[] = $db_trigger['triggerid']; 280 } 281 282 $triggerids = [ZBX_FLAG_DISCOVERY_NORMAL => [], ZBX_FLAG_DISCOVERY_PROTOTYPE => []]; 283 284 if ($templ_triggerids) { 285 $sql_distinct = ($targetids !== null) ? ' DISTINCT' : ''; 286 $sql_from = ($targetids !== null) ? ',functions f,items i' : ''; 287 $sql_where = ($targetids !== null) 288 ? ' AND t.triggerid=f.triggerid'. 289 ' AND f.itemid=i.itemid'. 290 ' AND '.dbConditionInt('i.hostid', $targetids) 291 : ''; 292 293 $db_triggers = DBSelect( 294 'SELECT'.$sql_distinct.' t.triggerid,t.flags'. 295 ' FROM triggers t'.$sql_from. 296 ' WHERE '.dbConditionInt('t.templateid', $templ_triggerids). 297 ' AND '.dbConditionInt('t.flags', $flags). 298 $sql_where 299 ); 300 301 while ($db_trigger = DBfetch($db_triggers)) { 302 $triggerids[$db_trigger['flags']][] = $db_trigger['triggerid']; 303 } 304 } 305 306 if ($triggerids[ZBX_FLAG_DISCOVERY_NORMAL]) { 307 if ($clear) { 308 CTriggerManager::delete($triggerids[ZBX_FLAG_DISCOVERY_NORMAL]); 309 } 310 else { 311 DB::update('triggers', [ 312 'values' => ['templateid' => 0], 313 'where' => ['triggerid' => $triggerids[ZBX_FLAG_DISCOVERY_NORMAL]] 314 ]); 315 } 316 } 317 318 if ($triggerids[ZBX_FLAG_DISCOVERY_PROTOTYPE]) { 319 if ($clear) { 320 CTriggerPrototypeManager::delete($triggerids[ZBX_FLAG_DISCOVERY_PROTOTYPE]); 321 } 322 else { 323 DB::update('triggers', [ 324 'values' => ['templateid' => 0], 325 'where' => ['triggerid' => $triggerids[ZBX_FLAG_DISCOVERY_PROTOTYPE]] 326 ]); 327 } 328 } 329 330 /* GRAPHS {{{ */ 331 $db_tpl_graphs = DBselect( 332 'SELECT DISTINCT g.graphid'. 333 ' FROM graphs g,graphs_items gi,items i'. 334 ' WHERE g.graphid=gi.graphid'. 335 ' AND gi.itemid=i.itemid'. 336 ' AND '.dbConditionInt('i.hostid', $templateids). 337 ' AND '.dbConditionInt('g.flags', $flags) 338 ); 339 340 $tpl_graphids = []; 341 342 while ($db_tpl_graph = DBfetch($db_tpl_graphs)) { 343 $tpl_graphids[] = $db_tpl_graph['graphid']; 344 } 345 346 if ($tpl_graphids) { 347 $sql = ($targetids !== null) 348 ? 'SELECT DISTINCT g.graphid,g.flags'. 349 ' FROM graphs g,graphs_items gi,items i'. 350 ' WHERE g.graphid=gi.graphid'. 351 ' AND gi.itemid=i.itemid'. 352 ' AND '.dbConditionInt('g.templateid', $tpl_graphids). 353 ' AND '.dbConditionInt('i.hostid', $targetids) 354 : 'SELECT g.graphid,g.flags'. 355 ' FROM graphs g'. 356 ' WHERE '.dbConditionInt('g.templateid', $tpl_graphids); 357 358 $db_graphs = DBSelect($sql); 359 360 $graphs = [ 361 ZBX_FLAG_DISCOVERY_NORMAL => [], 362 ZBX_FLAG_DISCOVERY_PROTOTYPE => [] 363 ]; 364 while ($db_graph = DBfetch($db_graphs)) { 365 $graphs[$db_graph['flags']][] = $db_graph['graphid']; 366 } 367 368 if ($graphs[ZBX_FLAG_DISCOVERY_PROTOTYPE]) { 369 if ($clear) { 370 CGraphPrototypeManager::delete($graphs[ZBX_FLAG_DISCOVERY_PROTOTYPE]); 371 } 372 else { 373 DB::update('graphs', [ 374 'values' => ['templateid' => 0], 375 'where' => ['graphid' => $graphs[ZBX_FLAG_DISCOVERY_PROTOTYPE]] 376 ]); 377 } 378 } 379 380 if ($graphs[ZBX_FLAG_DISCOVERY_NORMAL]) { 381 if ($clear) { 382 CGraphManager::delete($graphs[ZBX_FLAG_DISCOVERY_NORMAL]); 383 } 384 else { 385 DB::update('graphs', [ 386 'values' => ['templateid' => 0], 387 'where' => ['graphid' => $graphs[ZBX_FLAG_DISCOVERY_NORMAL]] 388 ]); 389 } 390 } 391 } 392 /* }}} GRAPHS */ 393 394 /* ITEMS, DISCOVERY RULES {{{ */ 395 $sqlFrom = ' items i1,items i2,hosts h'; 396 $sqlWhere = ' i2.itemid=i1.templateid'. 397 ' AND '.dbConditionInt('i2.hostid', $templateids). 398 ' AND '.dbConditionInt('i1.flags', $flags). 399 ' AND h.hostid=i1.hostid'; 400 401 if (!is_null($targetids)) { 402 $sqlWhere .= ' AND '.dbConditionInt('i1.hostid', $targetids); 403 } 404 $sql = 'SELECT DISTINCT i1.itemid,i1.flags,i1.name,i1.hostid,h.name as host'. 405 ' FROM '.$sqlFrom. 406 ' WHERE '.$sqlWhere; 407 $dbItems = DBSelect($sql); 408 $items = [ 409 ZBX_FLAG_DISCOVERY_NORMAL => [], 410 ZBX_FLAG_DISCOVERY_RULE => [], 411 ZBX_FLAG_DISCOVERY_PROTOTYPE => [] 412 ]; 413 while ($item = DBfetch($dbItems)) { 414 $items[$item['flags']][$item['itemid']] = [ 415 'name' => $item['name'], 416 'host' => $item['host'] 417 ]; 418 } 419 420 if (!empty($items[ZBX_FLAG_DISCOVERY_RULE])) { 421 if ($clear) { 422 CDiscoveryRuleManager::delete(array_keys($items[ZBX_FLAG_DISCOVERY_RULE])); 423 } 424 else{ 425 DB::update('items', [ 426 'values' => ['templateid' => 0], 427 'where' => ['itemid' => array_keys($items[ZBX_FLAG_DISCOVERY_RULE])] 428 ]); 429 430 foreach ($items[ZBX_FLAG_DISCOVERY_RULE] as $discoveryRule) { 431 info(_s('Unlinked: Discovery rule "%1$s" on "%2$s".', $discoveryRule['name'], $discoveryRule['host'])); 432 } 433 } 434 } 435 436 if (!empty($items[ZBX_FLAG_DISCOVERY_NORMAL])) { 437 if ($clear) { 438 CItemManager::delete(array_keys($items[ZBX_FLAG_DISCOVERY_NORMAL])); 439 } 440 else{ 441 DB::update('items', [ 442 'values' => ['templateid' => 0], 443 'where' => ['itemid' => array_keys($items[ZBX_FLAG_DISCOVERY_NORMAL])] 444 ]); 445 446 foreach ($items[ZBX_FLAG_DISCOVERY_NORMAL] as $item) { 447 info(_s('Unlinked: Item "%1$s" on "%2$s".', $item['name'], $item['host'])); 448 } 449 } 450 } 451 452 if (!empty($items[ZBX_FLAG_DISCOVERY_PROTOTYPE])) { 453 $item_prototypeids = array_keys($items[ZBX_FLAG_DISCOVERY_PROTOTYPE]); 454 455 if ($clear) { 456 // This will include deletion of linked application prototypes. 457 CItemPrototypeManager::delete($item_prototypeids); 458 } 459 else { 460 DB::update('items', [ 461 'values' => ['templateid' => 0], 462 'where' => ['itemid' => $item_prototypeids] 463 ]); 464 465 foreach ($items[ZBX_FLAG_DISCOVERY_PROTOTYPE] as $item) { 466 info(_s('Unlinked: Item prototype "%1$s" on "%2$s".', $item['name'], $item['host'])); 467 } 468 469 /* 470 * Convert templated application prototypes to normal application prototypes 471 * who are linked to these item prototypes. 472 */ 473 $application_prototypes = DBfetchArray(DBselect( 474 'SELECT ap.application_prototypeid'. 475 ' FROM application_prototype ap'. 476 ' WHERE EXISTS ('. 477 'SELECT NULL'. 478 ' FROM item_application_prototype iap'. 479 ' WHERE '.dbConditionInt('iap.itemid', $item_prototypeids). 480 ' AND iap.application_prototypeid=ap.application_prototypeid'. 481 ')' 482 )); 483 484 if ($application_prototypes) { 485 $application_prototypeids = zbx_objectValues($application_prototypes, 'application_prototypeid'); 486 487 DB::update('application_prototype', [ 488 'values' => ['templateid' => 0], 489 'where' => ['application_prototypeid' => $application_prototypeids] 490 ]); 491 } 492 } 493 } 494 /* }}} ITEMS, DISCOVERY RULES */ 495 496 // host prototypes 497 // we need only to unlink host prototypes. in case of unlink and clear they will be deleted together with LLD rules. 498 if (!$clear && isset($items[ZBX_FLAG_DISCOVERY_RULE])) { 499 $discoveryRuleIds = array_keys($items[ZBX_FLAG_DISCOVERY_RULE]); 500 501 $hostPrototypes = DBfetchArrayAssoc(DBSelect( 502 'SELECT DISTINCT h.hostid,h.host,h3.host AS parent_host'. 503 ' FROM hosts h'. 504 ' INNER JOIN host_discovery hd ON h.hostid=hd.hostid'. 505 ' INNER JOIN hosts h2 ON h.templateid=h2.hostid'. 506 ' INNER JOIN host_discovery hd2 ON h.hostid=hd.hostid'. 507 ' INNER JOIN items i ON hd.parent_itemid=i.itemid'. 508 ' INNER JOIN hosts h3 ON i.hostid=h3.hostid'. 509 ' WHERE '.dbConditionInt('hd.parent_itemid', $discoveryRuleIds) 510 ), 'hostid'); 511 if ($hostPrototypes) { 512 DB::update('hosts', [ 513 'values' => ['templateid' => 0], 514 'where' => ['hostid' => array_keys($hostPrototypes)] 515 ]); 516 DB::update('group_prototype', [ 517 'values' => ['templateid' => 0], 518 'where' => ['hostid' => array_keys($hostPrototypes)] 519 ]); 520 foreach ($hostPrototypes as $hostPrototype) { 521 info(_s('Unlinked: Host prototype "%1$s" on "%2$s".', $hostPrototype['host'], $hostPrototype['parent_host'])); 522 } 523 } 524 } 525 526 // http tests 527 $sqlWhere = ''; 528 if (!is_null($targetids)) { 529 $sqlWhere = ' AND '.dbConditionInt('ht1.hostid', $targetids); 530 } 531 $sql = 'SELECT DISTINCT ht1.httptestid,ht1.name,h.name as host'. 532 ' FROM httptest ht1'. 533 ' INNER JOIN httptest ht2 ON ht2.httptestid=ht1.templateid'. 534 ' INNER JOIN hosts h ON h.hostid=ht1.hostid'. 535 ' WHERE '.dbConditionInt('ht2.hostid', $templateids). 536 $sqlWhere; 537 $dbHttpTests = DBSelect($sql); 538 $httpTests = []; 539 while ($httpTest = DBfetch($dbHttpTests)) { 540 $httpTests[$httpTest['httptestid']] = [ 541 'name' => $httpTest['name'], 542 'host' => $httpTest['host'] 543 ]; 544 } 545 546 if (!empty($httpTests)) { 547 if ($clear) { 548 $result = API::HttpTest()->delete(array_keys($httpTests), true); 549 if (!$result) { 550 self::exception(ZBX_API_ERROR_INTERNAL, _('Cannot unlink and clear Web scenarios.')); 551 } 552 } 553 else { 554 DB::update('httptest', [ 555 'values' => ['templateid' => 0], 556 'where' => ['httptestid' => array_keys($httpTests)] 557 ]); 558 foreach ($httpTests as $httpTest) { 559 info(_s('Unlinked: Web scenario "%1$s" on "%2$s".', $httpTest['name'], $httpTest['host'])); 560 } 561 } 562 } 563 564 /* APPLICATIONS {{{ */ 565 $sql = 'SELECT at.application_templateid,at.applicationid,h.name,h.host,h.hostid'. 566 ' FROM applications a1,application_template at,applications a2,hosts h'. 567 ' WHERE a1.applicationid=at.applicationid'. 568 ' AND at.templateid=a2.applicationid'. 569 ' AND '.dbConditionInt('a2.hostid', $templateids). 570 ' AND a1.hostid=h.hostid'; 571 if ($targetids) { 572 $sql .= ' AND '.dbConditionInt('a1.hostid', $targetids); 573 } 574 $query = DBselect($sql); 575 $applicationTemplates = []; 576 while ($applicationTemplate = DBfetch($query)) { 577 $applicationTemplates[] = [ 578 'applicationid' => $applicationTemplate['applicationid'], 579 'application_templateid' => $applicationTemplate['application_templateid'], 580 'name' => $applicationTemplate['name'], 581 'hostid' => $applicationTemplate['hostid'], 582 'host' => $applicationTemplate['host'] 583 ]; 584 } 585 586 if ($applicationTemplates) { 587 // unlink applications from templates 588 DB::delete('application_template', [ 589 'application_templateid' => zbx_objectValues($applicationTemplates, 'application_templateid') 590 ]); 591 592 if ($clear) { 593 // Delete inherited applications that are no longer linked to any templates and items. 594 $applicationids = zbx_objectValues($applicationTemplates, 'applicationid'); 595 596 $applications = DBfetchArray(DBselect( 597 'SELECT a.applicationid'. 598 ' FROM applications a'. 599 ' LEFT JOIN application_template at ON a.applicationid=at.applicationid'. 600 ' WHERE '.dbConditionInt('a.applicationid', $applicationids). 601 ' AND at.applicationid IS NULL'. 602 ' AND a.applicationid NOT IN ('. 603 'SELECT ia.applicationid'. 604 ' FROM items_applications ia'. 605 ' WHERE '.dbConditionInt('ia.applicationid', $applicationids). 606 ')' 607 )); 608 if ($applications) { 609 $result = API::Application()->delete(zbx_objectValues($applications, 'applicationid'), true); 610 if (!$result) { 611 self::exception(ZBX_API_ERROR_INTERNAL, _('Cannot unlink and clear applications.')); 612 } 613 } 614 } 615 else { 616 foreach ($applicationTemplates as $application) { 617 info(_s('Unlinked: Application "%1$s" on "%2$s".', $application['name'], $application['host'])); 618 } 619 } 620 } 621 622 /* 623 * Process discovered applications when parent is a host, not template. 624 * If a discovered application has no longer linked items, remove them. 625 */ 626 if ($targetids) { 627 $discovered_applications = API::Application()->get([ 628 'output' => ['applicationid'], 629 'hostids' => $targetids, 630 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_CREATED], 631 'preservekeys' => true 632 ]); 633 634 if ($discovered_applications) { 635 $discovered_applications = API::Application()->get([ 636 'output' => ['applicationid'], 637 'selectItems' => ['itemid'], 638 'applicationids' => array_keys($discovered_applications), 639 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_CREATED] 640 ]); 641 642 $applications_to_delete = []; 643 644 foreach ($discovered_applications as $discovered_application) { 645 if (!$discovered_application['items']) { 646 $applications_to_delete[$discovered_application['applicationid']] = true; 647 } 648 } 649 650 if ($applications_to_delete) { 651 API::Application()->delete(array_keys($applications_to_delete), true); 652 } 653 } 654 } 655 /* }}} APPLICATIONS */ 656 657 parent::unlink($templateids, $targetids); 658 } 659 660 protected function addRelatedObjects(array $options, array $result) { 661 $result = parent::addRelatedObjects($options, $result); 662 663 $hostids = array_keys($result); 664 665 // adding groups 666 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 667 $relationMap = $this->createRelationMap($result, 'hostid', 'groupid', 'hosts_groups'); 668 $groups = API::HostGroup()->get([ 669 'output' => $options['selectGroups'], 670 'groupids' => $relationMap->getRelatedIds(), 671 'preservekeys' => true 672 ]); 673 $result = $relationMap->mapMany($result, $groups, 'groups'); 674 } 675 676 // adding templates 677 if ($options['selectParentTemplates'] !== null) { 678 if ($options['selectParentTemplates'] != API_OUTPUT_COUNT) { 679 $templates = []; 680 $relationMap = $this->createRelationMap($result, 'hostid', 'templateid', 'hosts_templates'); 681 $related_ids = $relationMap->getRelatedIds(); 682 683 if ($related_ids) { 684 $templates = API::Template()->get([ 685 'output' => $options['selectParentTemplates'], 686 'templateids' => $related_ids, 687 'nopermissions' => $options['nopermissions'], 688 'preservekeys' => true 689 ]); 690 if (!is_null($options['limitSelects'])) { 691 order_result($templates, 'host'); 692 } 693 } 694 695 $result = $relationMap->mapMany($result, $templates, 'parentTemplates', $options['limitSelects']); 696 } 697 else { 698 $templates = API::Template()->get([ 699 'hostids' => $hostids, 700 'countOutput' => true, 701 'groupCount' => true 702 ]); 703 $templates = zbx_toHash($templates, 'hostid'); 704 foreach ($result as $hostid => $host) { 705 $result[$hostid]['parentTemplates'] = array_key_exists($hostid, $templates) 706 ? $templates[$hostid]['rowscount'] 707 : '0'; 708 } 709 } 710 } 711 712 // adding items 713 if ($options['selectItems'] !== null) { 714 if ($options['selectItems'] != API_OUTPUT_COUNT) { 715 $items = API::Item()->get([ 716 'output' => $this->outputExtend($options['selectItems'], ['hostid', 'itemid']), 717 'hostids' => $hostids, 718 'nopermissions' => true, 719 'preservekeys' => true 720 ]); 721 722 if (!is_null($options['limitSelects'])) { 723 order_result($items, 'name'); 724 } 725 726 $relationMap = $this->createRelationMap($items, 'hostid', 'itemid'); 727 728 $items = $this->unsetExtraFields($items, ['hostid', 'itemid'], $options['selectItems']); 729 $result = $relationMap->mapMany($result, $items, 'items', $options['limitSelects']); 730 } 731 else { 732 $items = API::Item()->get([ 733 'hostids' => $hostids, 734 'nopermissions' => true, 735 'countOutput' => true, 736 'groupCount' => true 737 ]); 738 $items = zbx_toHash($items, 'hostid'); 739 foreach ($result as $hostid => $host) { 740 $result[$hostid]['items'] = array_key_exists($hostid, $items) ? $items[$hostid]['rowscount'] : '0'; 741 } 742 } 743 } 744 745 // adding discoveries 746 if ($options['selectDiscoveries'] !== null) { 747 if ($options['selectDiscoveries'] != API_OUTPUT_COUNT) { 748 $items = API::DiscoveryRule()->get([ 749 'output' => $this->outputExtend($options['selectDiscoveries'], ['hostid', 'itemid']), 750 'hostids' => $hostids, 751 'nopermissions' => true, 752 'preservekeys' => true 753 ]); 754 755 if (!is_null($options['limitSelects'])) { 756 order_result($items, 'name'); 757 } 758 759 $relationMap = $this->createRelationMap($items, 'hostid', 'itemid'); 760 761 $items = $this->unsetExtraFields($items, ['hostid', 'itemid'], $options['selectDiscoveries']); 762 $result = $relationMap->mapMany($result, $items, 'discoveries', $options['limitSelects']); 763 } 764 else { 765 $items = API::DiscoveryRule()->get([ 766 'hostids' => $hostids, 767 'nopermissions' => true, 768 'countOutput' => true, 769 'groupCount' => true 770 ]); 771 $items = zbx_toHash($items, 'hostid'); 772 foreach ($result as $hostid => $host) { 773 $result[$hostid]['discoveries'] = array_key_exists($hostid, $items) 774 ? $items[$hostid]['rowscount'] 775 : '0'; 776 } 777 } 778 } 779 780 // adding triggers 781 if ($options['selectTriggers'] !== null) { 782 if ($options['selectTriggers'] != API_OUTPUT_COUNT) { 783 $triggers = []; 784 $relationMap = new CRelationMap(); 785 // discovered items 786 $res = DBselect( 787 'SELECT i.hostid,f.triggerid'. 788 ' FROM items i,functions f'. 789 ' WHERE '.dbConditionInt('i.hostid', $hostids). 790 ' AND i.itemid=f.itemid' 791 ); 792 while ($relation = DBfetch($res)) { 793 $relationMap->addRelation($relation['hostid'], $relation['triggerid']); 794 } 795 796 $related_ids = $relationMap->getRelatedIds(); 797 798 if ($related_ids) { 799 $triggers = API::Trigger()->get([ 800 'output' => $options['selectTriggers'], 801 'triggerids' => $related_ids, 802 'preservekeys' => true 803 ]); 804 if (!is_null($options['limitSelects'])) { 805 order_result($triggers, 'description'); 806 } 807 } 808 809 $result = $relationMap->mapMany($result, $triggers, 'triggers', $options['limitSelects']); 810 } 811 else { 812 $triggers = API::Trigger()->get([ 813 'hostids' => $hostids, 814 'countOutput' => true, 815 'groupCount' => true 816 ]); 817 $triggers = zbx_toHash($triggers, 'hostid'); 818 819 foreach ($result as $hostid => $host) { 820 $result[$hostid]['triggers'] = array_key_exists($hostid, $triggers) 821 ? $triggers[$hostid]['rowscount'] 822 : '0'; 823 } 824 } 825 } 826 827 // adding graphs 828 if ($options['selectGraphs'] !== null) { 829 if ($options['selectGraphs'] != API_OUTPUT_COUNT) { 830 $graphs = []; 831 $relationMap = new CRelationMap(); 832 // discovered items 833 $res = DBselect( 834 'SELECT i.hostid,gi.graphid'. 835 ' FROM items i,graphs_items gi'. 836 ' WHERE '.dbConditionInt('i.hostid', $hostids). 837 ' AND i.itemid=gi.itemid' 838 ); 839 while ($relation = DBfetch($res)) { 840 $relationMap->addRelation($relation['hostid'], $relation['graphid']); 841 } 842 843 $related_ids = $relationMap->getRelatedIds(); 844 845 if ($related_ids) { 846 $graphs = API::Graph()->get([ 847 'output' => $options['selectGraphs'], 848 'graphids' => $related_ids, 849 'preservekeys' => true 850 ]); 851 if (!is_null($options['limitSelects'])) { 852 order_result($graphs, 'name'); 853 } 854 } 855 856 $result = $relationMap->mapMany($result, $graphs, 'graphs', $options['limitSelects']); 857 } 858 else { 859 $graphs = API::Graph()->get([ 860 'hostids' => $hostids, 861 'countOutput' => true, 862 'groupCount' => true 863 ]); 864 $graphs = zbx_toHash($graphs, 'hostid'); 865 foreach ($result as $hostid => $host) { 866 $result[$hostid]['graphs'] = array_key_exists($hostid, $graphs) 867 ? $graphs[$hostid]['rowscount'] 868 : '0'; 869 } 870 } 871 } 872 873 // adding http tests 874 if ($options['selectHttpTests'] !== null) { 875 if ($options['selectHttpTests'] != API_OUTPUT_COUNT) { 876 $httpTests = API::HttpTest()->get([ 877 'output' => $this->outputExtend($options['selectHttpTests'], ['hostid', 'httptestid']), 878 'hostids' => $hostids, 879 'nopermissions' => true, 880 'preservekeys' => true 881 ]); 882 883 if (!is_null($options['limitSelects'])) { 884 order_result($httpTests, 'name'); 885 } 886 887 $relationMap = $this->createRelationMap($httpTests, 'hostid', 'httptestid'); 888 889 $httpTests = $this->unsetExtraFields($httpTests, ['hostid', 'httptestid'], $options['selectHttpTests']); 890 $result = $relationMap->mapMany($result, $httpTests, 'httpTests', $options['limitSelects']); 891 } 892 else { 893 $httpTests = API::HttpTest()->get([ 894 'hostids' => $hostids, 895 'nopermissions' => true, 896 'countOutput' => true, 897 'groupCount' => true 898 ]); 899 $httpTests = zbx_toHash($httpTests, 'hostid'); 900 foreach ($result as $hostid => $host) { 901 $result[$hostid]['httpTests'] = array_key_exists($hostid, $httpTests) 902 ? $httpTests[$hostid]['rowscount'] 903 : '0'; 904 } 905 } 906 } 907 908 // adding applications 909 if ($options['selectApplications'] !== null) { 910 if ($options['selectApplications'] != API_OUTPUT_COUNT) { 911 $applications = API::Application()->get([ 912 'output' => $this->outputExtend($options['selectApplications'], ['hostid', 'applicationid']), 913 'hostids' => $hostids, 914 'nopermissions' => true, 915 'preservekeys' => true 916 ]); 917 918 if (!is_null($options['limitSelects'])) { 919 order_result($applications, 'name'); 920 } 921 922 $relationMap = $this->createRelationMap($applications, 'hostid', 'applicationid'); 923 924 $applications = $this->unsetExtraFields($applications, ['hostid', 'applicationid'], 925 $options['selectApplications'] 926 ); 927 $result = $relationMap->mapMany($result, $applications, 'applications', $options['limitSelects']); 928 } 929 else { 930 $applications = API::Application()->get([ 931 'output' => $options['selectApplications'], 932 'hostids' => $hostids, 933 'nopermissions' => true, 934 'countOutput' => true, 935 'groupCount' => true 936 ]); 937 938 $applications = zbx_toHash($applications, 'hostid'); 939 foreach ($result as $hostid => $host) { 940 $result[$hostid]['applications'] = array_key_exists($hostid, $applications) 941 ? $applications[$hostid]['rowscount'] 942 : '0'; 943 } 944 } 945 } 946 947 // adding macros 948 if ($options['selectMacros'] !== null && $options['selectMacros'] != API_OUTPUT_COUNT) { 949 $macros = API::UserMacro()->get([ 950 'output' => $this->outputExtend($options['selectMacros'], ['hostid', 'hostmacroid']), 951 'hostids' => $hostids, 952 'preservekeys' => true 953 ]); 954 955 $relationMap = $this->createRelationMap($macros, 'hostid', 'hostmacroid'); 956 957 $macros = $this->unsetExtraFields($macros, ['hostid', 'hostmacroid'], $options['selectMacros']); 958 $result = $relationMap->mapMany($result, $macros, 'macros', $options['limitSelects']); 959 } 960 961 // adding tags 962 if ($options['selectTags'] !== null && $options['selectTags'] != API_OUTPUT_COUNT) { 963 if ($options['selectTags'] === API_OUTPUT_EXTEND) { 964 $options['selectTags'] = ['tag', 'value']; 965 } 966 967 $tags_options = [ 968 'output' => $this->outputExtend($options['selectTags'], ['hostid']), 969 'filter' => ['hostid' => $hostids] 970 ]; 971 972 foreach ($result as &$host) { 973 $host['tags'] = []; 974 } 975 unset($host); 976 977 $tags = DBselect(DB::makeSql('host_tag', $tags_options)); 978 979 while ($tag = DBfetch($tags)) { 980 $hostid = $tag['hostid']; 981 unset($tag['hosttagid'], $tag['hostid']); 982 $result[$hostid]['tags'][] = $tag; 983 } 984 } 985 986 return $result; 987 } 988 989 /** 990 * Compares input tags with tags stored in the database and performs tag deleting and inserting. 991 * 992 * @param array $hosts 993 * @param int $hosts[]['hostid'] 994 * @param int $hosts[]['templateid'] 995 * @param array $hosts[]['tags'] 996 * @param string $hosts[]['tags'][]['tag'] 997 * @param string $hosts[]['tags'][]['value'] 998 * @param string $id_field 999 */ 1000 protected function updateTags(array $hosts, $id_field) { 1001 $hostids = []; 1002 foreach ($hosts as $index => $host) { 1003 if (!array_key_exists('tags', $host)) { 1004 unset($host[$index]); 1005 continue; 1006 } 1007 $hostids[] = $host[$id_field]; 1008 } 1009 1010 if (!$hostids) { 1011 return; 1012 } 1013 1014 $options = [ 1015 'output' => ['hosttagid', 'hostid', 'tag', 'value'], 1016 'filter' => ['hostid' => $hostids] 1017 ]; 1018 1019 $db_tags = DBselect(DB::makeSql('host_tag', $options)); 1020 $db_hosts = []; 1021 $del_hosttagids = []; 1022 1023 while ($db_tag = DBfetch($db_tags)) { 1024 $db_hosts[$db_tag['hostid']]['tags'][] = $db_tag; 1025 $del_hosttagids[$db_tag['hosttagid']] = true; 1026 } 1027 1028 $ins_tags = []; 1029 foreach ($hosts as $host) { 1030 foreach ($host['tags'] as $tag) { 1031 $tag += ['value' => '']; 1032 1033 if (array_key_exists($host[$id_field], $db_hosts)) { 1034 foreach ($db_hosts[$host[$id_field]]['tags'] as $db_tag) { 1035 if ($tag['tag'] === $db_tag['tag'] && $tag['value'] === $db_tag['value']) { 1036 unset($del_hosttagids[$db_tag['hosttagid']]); 1037 $tag = null; 1038 break; 1039 } 1040 } 1041 } 1042 1043 if ($tag !== null) { 1044 $ins_tags[] = ['hostid' => $host[$id_field]] + $tag; 1045 } 1046 } 1047 } 1048 1049 if ($del_hosttagids) { 1050 DB::delete('host_tag', ['hosttagid' => array_keys($del_hosttagids)]); 1051 } 1052 1053 if ($ins_tags) { 1054 DB::insert('host_tag', $ins_tags); 1055 } 1056 } 1057 1058 /** 1059 * Validates tags. 1060 * 1061 * @param array $host 1062 * @param int $host['evaltype'] 1063 * @param array $host['tags'] 1064 * @param string $host['tags'][]['tag'] 1065 * @param string $host['tags'][]['value'] 1066 * 1067 * @throws APIException if the input is invalid. 1068 */ 1069 protected function validateTags(array $host) { 1070 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 1071 'evaltype' => ['type' => API_INT32, 'in' => implode(',', [TAG_EVAL_TYPE_AND_OR, TAG_EVAL_TYPE_OR])], 1072 'tags' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['tag', 'value']], 'fields' => [ 1073 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('host_tag', 'tag')], 1074 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('host_tag', 'value'), 'default' => DB::getDefault('host_tag', 'value')] 1075 ]] 1076 ]]; 1077 1078 // Keep values only for fields with defined validation rules. 1079 $host = array_intersect_key($host, $api_input_rules['fields']); 1080 1081 if (!CApiInputValidator::validate($api_input_rules, $host, '/', $error)) { 1082 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1083 } 1084 } 1085} 1086