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 */ 25class CHost extends CHostGeneral { 26 27 protected $sortColumns = ['hostid', 'host', 'name', 'status']; 28 29 /** 30 * Get host data. 31 * 32 * @param array $options 33 * @param array $options['groupids'] Select hosts by group IDs. 34 * @param array $options['hostids'] Select hosts by host IDs. 35 * @param array $options['templateids'] Select hosts by template IDs. 36 * @param array $options['interfaceids'] Select hosts by interface IDs. 37 * @param array $options['itemids'] Select hosts by item IDs. 38 * @param array $options['triggerids'] Select hosts by trigger IDs. 39 * @param array $options['maintenanceids'] Select hosts by maintenance IDs. 40 * @param array $options['graphids'] Select hosts by graph IDs. 41 * @param array $options['dserviceids'] Select hosts by discovery service IDs. 42 * @param array $options['httptestids'] Select hosts by web scenario IDs. 43 * @param bool $options['monitored_hosts'] Return only monitored hosts. 44 * @param bool $options['templated_hosts'] Include templates in result. 45 * @param bool $options['proxy_hosts'] Include proxies in result. 46 * @param bool $options['with_items'] Select hosts only with items. 47 * @param bool $options['with_item_prototypes'] Select hosts only with item prototypes. 48 * @param bool $options['with_simple_graph_items'] Select hosts only with items suitable for graphs. 49 * @param bool $options['with_simple_graph_item_prototypes'] Select hosts only with item prototypes suitable for graphs. 50 * @param bool $options['with_monitored_items'] Select hosts only with monitored items. 51 * @param bool $options['with_triggers'] Select hosts only with triggers. 52 * @param bool $options['with_monitored_triggers'] Select hosts only with monitored triggers. 53 * @param bool $options['with_httptests'] Select hosts only with http tests. 54 * @param bool $options['with_monitored_httptests'] Select hosts only with monitored http tests. 55 * @param bool $options['with_graphs'] Select hosts only with graphs. 56 * @param bool $options['with_graph_prototypes'] Select hosts only with graph prototypes. 57 * @param bool $options['withProblemsSuppressed'] Select hosts that have suppressed problems. (null - all, true - only suppressed, false - unsuppressed) 58 * @param bool $options['editable'] Select hosts only with read-write permission. Ignored for Super admins. 59 * @param bool $options['nopermissions'] Select hosts by ignoring all permissions. Only available inside API calls. 60 * @param bool $options['evaltype'] Operator for tag filter 0 - AND/OR; 2 - OR. 61 * @param bool $options['tags'] Select hosts by given tags. 62 * @param bool $options['severities'] Select hosts that have only problems with given severities. 63 * @param bool $options['inheritedTags'] Select hosts that have given tags also in their linked templates. 64 * @param string|array $options['selectGroups'] Return a "groups" property with host groups data that the host belongs to. 65 * @param string|array $options['selectParentTemplates'] Return a "parentTemplates" property with templates that the host is linked to. 66 * @param string|array $options['selectItems'] Return an "items" property with host items. 67 * @param string|array $options['selectDiscoveries'] Return a "discoveries" property with host low-level discovery rules. 68 * @param string|array $options['selectTriggers'] Return a "triggers" property with host triggers. 69 * @param string|array $options['selectGraphs'] Return a "graphs" property with host graphs. 70 * @param string|array $options['selectMacros'] Return a "macros" property with host macros. 71 * @param string|array $options['selectDashboards'] Return a "dashboards" property with host dashboards. 72 * @param string|array $options['selectInterfaces'] Return an "interfaces" property with host interfaces. 73 * @param string|array $options['selectInventory'] Return an "inventory" property with host inventory data. 74 * @param string|array $options['selectHttpTests'] Return an "httpTests" property with host web scenarios. 75 * @param string|array $options['selectDiscoveryRule'] Return a "discoveryRule" property with the low-level discovery rule that created the host (from host prototype in VMware monitoring). 76 * @param string|array $options['selectHostDiscovery'] Return a "hostDiscovery" property with host discovery object data. 77 * @param string|array $options['selectTags'] Return a "tags" property with host tags. 78 * @param string|array $options['selectInheritedTags'] Return an "inheritedTags" property with tags that are on templates which are linked to host. 79 * @param bool $options['countOutput'] Return host count as output. 80 * @param bool $options['groupCount'] Group the host count. 81 * @param bool $options['preservekeys'] Return host IDs as array keys. 82 * @param string $options['sortfield'] Field to sort by. 83 * @param string $options['sortorder'] Sort order. 84 * @param int $options['limit'] Limit selection. 85 * @param int $options['limitSelects'] Limits the number of records returned by subselects. 86 * 87 * @return array|boolean Host data as array or false if error 88 */ 89 public function get($options = []) { 90 $result = []; 91 92 $sqlParts = [ 93 'select' => ['hosts' => 'h.hostid'], 94 'from' => ['hosts' => 'hosts h'], 95 'where' => ['flags' => 'h.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'], 96 'group' => [], 97 'order' => [], 98 'limit' => null 99 ]; 100 101 $defOptions = [ 102 'groupids' => null, 103 'hostids' => null, 104 'proxyids' => null, 105 'templateids' => null, 106 'interfaceids' => null, 107 'itemids' => null, 108 'triggerids' => null, 109 'maintenanceids' => null, 110 'graphids' => null, 111 'dserviceids' => null, 112 'httptestids' => null, 113 'monitored_hosts' => null, 114 'templated_hosts' => null, 115 'proxy_hosts' => null, 116 'with_items' => null, 117 'with_item_prototypes' => null, 118 'with_simple_graph_items' => null, 119 'with_simple_graph_item_prototypes' => null, 120 'with_monitored_items' => null, 121 'with_triggers' => null, 122 'with_monitored_triggers' => null, 123 'with_httptests' => null, 124 'with_monitored_httptests' => null, 125 'with_graphs' => null, 126 'with_graph_prototypes' => null, 127 'withProblemsSuppressed' => null, 128 'editable' => false, 129 'nopermissions' => null, 130 // filter 131 'evaltype' => TAG_EVAL_TYPE_AND_OR, 132 'tags' => null, 133 'severities' => null, 134 'inheritedTags' => false, 135 'filter' => null, 136 'search' => null, 137 'searchInventory' => null, 138 'searchByAny' => null, 139 'startSearch' => false, 140 'excludeSearch' => false, 141 'searchWildcardsEnabled' => false, 142 // output 143 'output' => API_OUTPUT_EXTEND, 144 'selectGroups' => null, 145 'selectParentTemplates' => null, 146 'selectItems' => null, 147 'selectDiscoveries' => null, 148 'selectTriggers' => null, 149 'selectGraphs' => null, 150 'selectMacros' => null, 151 'selectDashboards' => null, 152 'selectInterfaces' => null, 153 'selectInventory' => null, 154 'selectHttpTests' => null, 155 'selectDiscoveryRule' => null, 156 'selectHostDiscovery' => null, 157 'selectTags' => null, 158 'selectInheritedTags' => null, 159 'selectValueMaps' => null, 160 'countOutput' => false, 161 'groupCount' => false, 162 'preservekeys' => false, 163 'sortfield' => '', 164 'sortorder' => '', 165 'limit' => null, 166 'limitSelects' => null 167 ]; 168 $options = zbx_array_merge($defOptions, $options); 169 170 $this->validateGet($options); 171 172 // editable + PERMISSION CHECK 173 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 174 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 175 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 176 177 $sqlParts['where'][] = 'EXISTS ('. 178 'SELECT NULL'. 179 ' FROM hosts_groups hgg'. 180 ' JOIN rights r'. 181 ' ON r.id=hgg.groupid'. 182 ' AND '.dbConditionInt('r.groupid', $userGroups). 183 ' WHERE h.hostid=hgg.hostid'. 184 ' GROUP BY hgg.hostid'. 185 ' HAVING MIN(r.permission)>'.PERM_DENY. 186 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 187 ')'; 188 } 189 190 // hostids 191 if (!is_null($options['hostids'])) { 192 zbx_value2array($options['hostids']); 193 $sqlParts['where']['hostid'] = dbConditionInt('h.hostid', $options['hostids']); 194 } 195 196 // groupids 197 if (!is_null($options['groupids'])) { 198 zbx_value2array($options['groupids']); 199 200 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 201 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 202 $sqlParts['where']['hgh'] = 'hg.hostid=h.hostid'; 203 204 if ($options['groupCount']) { 205 $sqlParts['group']['groupid'] = 'hg.groupid'; 206 } 207 } 208 209 // proxyids 210 if (!is_null($options['proxyids'])) { 211 zbx_value2array($options['proxyids']); 212 213 $sqlParts['where'][] = dbConditionId('h.proxy_hostid', $options['proxyids']); 214 } 215 216 // templateids 217 if (!is_null($options['templateids'])) { 218 zbx_value2array($options['templateids']); 219 220 $sqlParts['from']['hosts_templates'] = 'hosts_templates ht'; 221 $sqlParts['where'][] = dbConditionInt('ht.templateid', $options['templateids']); 222 $sqlParts['where']['hht'] = 'h.hostid=ht.hostid'; 223 224 if ($options['groupCount']) { 225 $sqlParts['group']['templateid'] = 'ht.templateid'; 226 } 227 } 228 229 // interfaceids 230 if (!is_null($options['interfaceids'])) { 231 zbx_value2array($options['interfaceids']); 232 233 $sqlParts['left_join']['interface'] = ['alias' => 'hi', 'table' => 'interface', 'using' => 'hostid']; 234 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 235 236 $sqlParts['where'][] = dbConditionInt('hi.interfaceid', $options['interfaceids']); 237 } 238 239 // itemids 240 if (!is_null($options['itemids'])) { 241 zbx_value2array($options['itemids']); 242 243 $sqlParts['from']['items'] = 'items i'; 244 $sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']); 245 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 246 } 247 248 // triggerids 249 if (!is_null($options['triggerids'])) { 250 zbx_value2array($options['triggerids']); 251 252 $sqlParts['from']['functions'] = 'functions f'; 253 $sqlParts['from']['items'] = 'items i'; 254 $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); 255 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 256 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 257 } 258 259 // httptestids 260 if (!is_null($options['httptestids'])) { 261 zbx_value2array($options['httptestids']); 262 263 $sqlParts['from']['httptest'] = 'httptest ht'; 264 $sqlParts['where'][] = dbConditionInt('ht.httptestid', $options['httptestids']); 265 $sqlParts['where']['aht'] = 'ht.hostid=h.hostid'; 266 } 267 268 // graphids 269 if (!is_null($options['graphids'])) { 270 zbx_value2array($options['graphids']); 271 272 $sqlParts['from']['graphs_items'] = 'graphs_items gi'; 273 $sqlParts['from']['items'] = 'items i'; 274 $sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']); 275 $sqlParts['where']['igi'] = 'i.itemid=gi.itemid'; 276 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 277 } 278 279 // dserviceids 280 if (!is_null($options['dserviceids'])) { 281 zbx_value2array($options['dserviceids']); 282 283 $sqlParts['from']['dservices'] = 'dservices ds'; 284 $sqlParts['from']['interface'] = 'interface i'; 285 $sqlParts['where'][] = dbConditionInt('ds.dserviceid', $options['dserviceids']); 286 $sqlParts['where']['dsh'] = 'ds.ip=i.ip'; 287 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 288 289 if ($options['groupCount']) { 290 $sqlParts['group']['dserviceid'] = 'ds.dserviceid'; 291 } 292 } 293 294 // maintenanceids 295 if (!is_null($options['maintenanceids'])) { 296 zbx_value2array($options['maintenanceids']); 297 298 $sqlParts['from']['maintenances_hosts'] = 'maintenances_hosts mh'; 299 $sqlParts['where'][] = dbConditionInt('mh.maintenanceid', $options['maintenanceids']); 300 $sqlParts['where']['hmh'] = 'h.hostid=mh.hostid'; 301 302 if ($options['groupCount']) { 303 $sqlParts['group']['maintenanceid'] = 'mh.maintenanceid'; 304 } 305 } 306 307 // monitored_hosts, templated_hosts 308 if (!is_null($options['monitored_hosts'])) { 309 $sqlParts['where']['status'] = 'h.status='.HOST_STATUS_MONITORED; 310 } 311 elseif (!is_null($options['templated_hosts'])) { 312 $sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED.','.HOST_STATUS_TEMPLATE.')'; 313 } 314 elseif (!is_null($options['proxy_hosts'])) { 315 $sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_PROXY_ACTIVE.','.HOST_STATUS_PROXY_PASSIVE.')'; 316 } 317 else { 318 $sqlParts['where']['status'] = 'h.status IN ('.HOST_STATUS_MONITORED.','.HOST_STATUS_NOT_MONITORED.')'; 319 } 320 321 // with_items, with_simple_graph_items, with_monitored_items 322 if ($options['with_items'] !== null 323 || $options['with_simple_graph_items'] !== null 324 || $options['with_monitored_items'] !== null) { 325 326 if ($options['with_items'] !== null) { 327 $where_and = 328 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]); 329 } 330 elseif ($options['with_monitored_items'] !== null) { 331 $where_and = 332 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]). 333 ' AND '.dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]); 334 } 335 elseif ($options['with_simple_graph_items'] !== null) { 336 $where_and = 337 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]). 338 ' AND '.dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]). 339 ' AND '.dbConditionInt('i.value_type', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]); 340 } 341 342 $sqlParts['where'][] = 'EXISTS ('. 343 'SELECT NULL'. 344 ' FROM items i'. 345 ' WHERE h.hostid=i.hostid'. 346 $where_and. 347 ')'; 348 } 349 350 // with_item_prototypes, with_simple_graph_item_prototypes 351 if ($options['with_item_prototypes'] !== null || $options['with_simple_graph_item_prototypes'] !== null) { 352 if ($options['with_item_prototypes'] !== null) { 353 $where_and = 354 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]); 355 } 356 elseif ($options['with_simple_graph_item_prototypes'] !== null) { 357 $where_and = 358 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]). 359 ' AND '.dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]). 360 ' AND '.dbConditionInt('i.value_type', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]); 361 } 362 363 $sqlParts['where'][] = 'EXISTS ('. 364 'SELECT NULL'. 365 ' FROM items i'. 366 ' WHERE h.hostid=i.hostid'. 367 $where_and. 368 ')'; 369 } 370 371 // with_triggers, with_monitored_triggers 372 if (!is_null($options['with_triggers'])) { 373 $sqlParts['where'][] = 'EXISTS ('. 374 'SELECT NULL'. 375 ' FROM items i,functions f,triggers t'. 376 ' WHERE h.hostid=i.hostid'. 377 ' AND i.itemid=f.itemid'. 378 ' AND f.triggerid=t.triggerid'. 379 ' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 380 ')'; 381 } 382 elseif (!is_null($options['with_monitored_triggers'])) { 383 $sqlParts['where'][] = 'EXISTS ('. 384 'SELECT NULL'. 385 ' FROM items i,functions f,triggers t'. 386 ' WHERE h.hostid=i.hostid'. 387 ' AND i.itemid=f.itemid'. 388 ' AND f.triggerid=t.triggerid'. 389 ' AND i.status='.ITEM_STATUS_ACTIVE. 390 ' AND t.status='.TRIGGER_STATUS_ENABLED. 391 ' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 392 ')'; 393 } 394 395 // with_httptests, with_monitored_httptests 396 if (!empty($options['with_httptests'])) { 397 $sqlParts['where'][] = 'EXISTS (SELECT NULL FROM httptest ht WHERE ht.hostid=h.hostid)'; 398 } 399 elseif (!empty($options['with_monitored_httptests'])) { 400 $sqlParts['where'][] = 'EXISTS ('. 401 'SELECT NULL'. 402 ' FROM httptest ht'. 403 ' WHERE h.hostid=ht.hostid'. 404 ' AND ht.status='.HTTPTEST_STATUS_ACTIVE. 405 ')'; 406 } 407 408 // with_graphs 409 if ($options['with_graphs'] !== null) { 410 $sqlParts['where'][] = 'EXISTS ('. 411 'SELECT NULL'. 412 ' FROM items i,graphs_items gi,graphs g'. 413 ' WHERE i.hostid=h.hostid'. 414 ' AND i.itemid=gi.itemid '. 415 ' AND gi.graphid=g.graphid'. 416 ' AND '.dbConditionInt('g.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]). 417 ')'; 418 } 419 420 // with_graph_prototypes 421 if ($options['with_graph_prototypes'] !== null) { 422 $sqlParts['where'][] = 'EXISTS ('. 423 'SELECT NULL'. 424 ' FROM items i,graphs_items gi,graphs g'. 425 ' WHERE i.hostid=h.hostid'. 426 ' AND i.itemid=gi.itemid '. 427 ' AND gi.graphid=g.graphid'. 428 ' AND '.dbConditionInt('g.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]). 429 ')'; 430 } 431 432 // search 433 if (is_array($options['search'])) { 434 zbx_db_search('hosts h', $options, $sqlParts); 435 436 if (zbx_db_search('interface hi', $options, $sqlParts)) { 437 $sqlParts['left_join']['interface'] = ['alias' => 'hi', 'table' => 'interface', 'using' => 'hostid']; 438 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 439 } 440 } 441 442 // search inventory 443 if ($options['searchInventory'] !== null) { 444 $sqlParts['from']['host_inventory'] = 'host_inventory hii'; 445 $sqlParts['where']['hii'] = 'h.hostid=hii.hostid'; 446 447 zbx_db_search('host_inventory hii', 448 [ 449 'search' => $options['searchInventory'], 450 'startSearch' => $options['startSearch'], 451 'excludeSearch' => $options['excludeSearch'], 452 'searchWildcardsEnabled' => $options['searchWildcardsEnabled'], 453 'searchByAny' => $options['searchByAny'] 454 ], 455 $sqlParts 456 ); 457 } 458 459 // filter 460 if (is_array($options['filter'])) { 461 $this->dbFilter('hosts h', $options, $sqlParts); 462 463 if ($this->dbFilter('interface hi', $options, $sqlParts)) { 464 $sqlParts['left_join']['interface'] = ['alias' => 'hi', 'table' => 'interface', 'using' => 'hostid']; 465 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 466 } 467 } 468 469 // tags 470 if ($options['tags'] !== null && $options['tags']) { 471 if ($options['inheritedTags']) { 472 $sqlParts['left_join'][] = ['alias' => 'ht2', 'table' => 'hosts_templates', 'using' => 'hostid']; 473 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 474 $sqlParts['where'][] = CApiTagHelper::addInheritedHostTagsWhereCondition($options['tags'], 475 $options['evaltype'] 476 ); 477 } 478 else { 479 $sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 'h', 480 'host_tag', 'hostid' 481 ); 482 } 483 } 484 485 // limit 486 if (!zbx_ctype_digit($options['limit']) || !$options['limit']) { 487 $options['limit'] = null; 488 } 489 490 /* 491 * Cleaning the output from write-only properties. 492 */ 493 $write_only_keys = ['tls_psk_identity', 'tls_psk']; 494 495 if ($options['output'] === API_OUTPUT_EXTEND) { 496 $all_keys = array_keys(DB::getSchema($this->tableName())['fields']); 497 $all_keys[] = 'inventory_mode'; 498 $options['output'] = array_diff($all_keys, $write_only_keys); 499 } 500 /* 501 * For internal calls of API method, is possible to get the write-only fields if they were specified in output. 502 * Specify write-only fields in output only if they will not appear in debug mode. 503 */ 504 elseif (is_array($options['output']) && APP::getMode() === APP::EXEC_MODE_API) { 505 $options['output'] = array_diff($options['output'], $write_only_keys); 506 } 507 508 $sqlParts = $this->applyQueryFilterOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 509 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 510 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 511 512 // Return count or grouped counts via direct SQL count. 513 if ($options['countOutput'] && !$this->requiresPostSqlFiltering($options)) { 514 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $options['limit']); 515 while ($host = DBfetch($res)) { 516 if ($options['groupCount']) { 517 $result[] = $host; 518 } 519 else { 520 $result = $host['rowscount']; 521 } 522 } 523 524 return $result; 525 } 526 527 $result = zbx_toHash($this->customFetch(self::createSelectQueryFromParts($sqlParts), $options), 'hostid'); 528 529 // Return count for post SQL filtered result sets. 530 if ($options['countOutput']) { 531 return (string) count($result); 532 } 533 534 // Hosts share table with host prototypes. Therefore remove host unrelated fields. 535 if ($this->outputIsRequested('discover', $options['output'])) { 536 foreach ($result as &$row) { 537 unset($row['discover']); 538 } 539 540 unset($row); 541 } 542 543 if ($result) { 544 $result = $this->addRelatedObjects($options, $result); 545 } 546 547 // removing keys (hash -> array) 548 if (!$options['preservekeys']) { 549 $result = zbx_cleanHashes($result); 550 } 551 552 return $result; 553 } 554 555 protected function applyQueryFilterOptions($tableName, $tableAlias, array $options, array $sqlParts) { 556 if ($options['filter'] && array_key_exists('inventory_mode', $options['filter'])) { 557 if ($options['filter']['inventory_mode'] !== null) { 558 $inventory_mode_query = (array) $options['filter']['inventory_mode']; 559 560 $inventory_mode_where = []; 561 $null_position = array_search(HOST_INVENTORY_DISABLED, $inventory_mode_query); 562 563 if ($null_position !== false) { 564 unset($inventory_mode_query[$null_position]); 565 $inventory_mode_where[] = 'hinv.inventory_mode IS NULL'; 566 } 567 568 if ($null_position === false || $inventory_mode_query) { 569 $inventory_mode_where[] = dbConditionInt('hinv.inventory_mode', $inventory_mode_query); 570 } 571 572 $sqlParts['where'][] = (count($inventory_mode_where) > 1) 573 ? '('.implode(' OR ', $inventory_mode_where).')' 574 : $inventory_mode_where[0]; 575 } 576 } 577 578 return $sqlParts; 579 } 580 581 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 582 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 583 584 if (!$options['countOutput'] && $this->outputIsRequested('inventory_mode', $options['output'])) { 585 $sqlParts['select']['inventory_mode'] = 586 dbConditionCoalesce('hinv.inventory_mode', HOST_INVENTORY_DISABLED, 'inventory_mode'); 587 } 588 589 if ((!$options['countOutput'] && $this->outputIsRequested('inventory_mode', $options['output'])) 590 || ($options['filter'] && array_key_exists('inventory_mode', $options['filter']))) { 591 $sqlParts['left_join'][] = ['alias' => 'hinv', 'table' => 'host_inventory', 'using' => 'hostid']; 592 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 593 } 594 595 return $sqlParts; 596 } 597 598 /** 599 * Add host. 600 * 601 * @param array $hosts An array with hosts data. 602 * @param string $hosts[]['host'] Host technical name. 603 * @param string $hosts[]['name'] Host visible name (optional). 604 * @param array $hosts[]['groups'] An array of host group objects with IDs that host will be 605 * added to. 606 * @param int $hosts[]['status'] Status of the host (optional). 607 * @param array $hosts[]['interfaces'] An array of host interfaces data. 608 * @param int $hosts[]['interfaces']['type'] Interface type. 609 * @param int $hosts[]['interfaces']['main'] Is this the default interface to use. 610 * @param string $hosts[]['interfaces']['ip'] Interface IP (optional). 611 * @param int $hosts[]['interfaces']['port'] Interface port (optional). 612 * @param int $hosts[]['interfaces']['useip'] Interface should use IP (optional). 613 * @param string $hosts[]['interfaces']['dns'] Interface should use DNS (optional). 614 * @param int $hosts[]['interfaces']['details'] Interface additional fields (optional). 615 * @param int $hosts[]['proxy_hostid'] ID of the proxy that is used to monitor the host (optional). 616 * @param int $hosts[]['ipmi_authtype'] IPMI authentication type (optional). 617 * @param int $hosts[]['ipmi_privilege'] IPMI privilege (optional). 618 * @param string $hosts[]['ipmi_username'] IPMI username (optional). 619 * @param string $hosts[]['ipmi_password'] IPMI password (optional). 620 * @param array $hosts[]['tags'] An array of tags (optional). 621 * @param string $hosts[]['tags'][]['tag'] Tag name. 622 * @param string $hosts[]['tags'][]['value'] Tag value. 623 * @param array $hosts[]['inventory'] An array of host inventory data (optional). 624 * @param array $hosts[]['macros'] An array of host macros (optional). 625 * @param string $hosts[]['macros'][]['macro'] Host macro (required if "macros" is set). 626 * @param array $hosts[]['templates'] An array of template objects with IDs that will be linked 627 * to host (optional). 628 * @param string $hosts[]['templates'][]['templateid'] Template ID (required if "templates" is set). 629 * @param string $hosts[]['tls_connect'] Connections to host (optional). 630 * @param string $hosts[]['tls_accept'] Connections from host (optional). 631 * @param string $hosts[]['tls_psk_identity'] PSK identity (required if "PSK" type is set). 632 * @param string $hosts[]['tls_psk'] PSK (required if "PSK" type is set). 633 * @param string $hosts[]['tls_issuer'] Certificate issuer (optional). 634 * @param string $hosts[]['tls_subject'] Certificate subject (optional). 635 * 636 * @return array 637 */ 638 public function create($hosts) { 639 $this->validateCreate($hosts); 640 641 $hosts_groups = []; 642 $hosts_tags = []; 643 $hosts_interfaces = []; 644 $hosts_inventory = []; 645 $templates_hostids = []; 646 647 $hostids = DB::insert('hosts', $hosts); 648 649 foreach ($hosts as $index => &$host) { 650 $host['hostid'] = $hostids[$index]; 651 652 foreach ($host['groups'] as $group) { 653 $hosts_groups[] = [ 654 'hostid' => $host['hostid'], 655 'groupid' => $group['groupid'] 656 ]; 657 } 658 659 if (array_key_exists('tags', $host)) { 660 foreach (zbx_toArray($host['tags']) as $tag) { 661 $hosts_tags[] = ['hostid' => $host['hostid']] + $tag; 662 } 663 } 664 665 if (array_key_exists('interfaces', $host)) { 666 foreach (zbx_toArray($host['interfaces']) as $interface) { 667 $hosts_interfaces[] = ['hostid' => $host['hostid']] + $interface; 668 } 669 } 670 671 if (array_key_exists('templates', $host)) { 672 foreach (zbx_toArray($host['templates']) as $template) { 673 $templates_hostids[$template['templateid']][] = $host['hostid']; 674 } 675 } 676 677 $host_inventory = []; 678 if (array_key_exists('inventory', $host) && $host['inventory']) { 679 $host_inventory = $host['inventory']; 680 $host_inventory['inventory_mode'] = HOST_INVENTORY_MANUAL; 681 } 682 683 if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] != HOST_INVENTORY_DISABLED) { 684 $host_inventory['inventory_mode'] = $host['inventory_mode']; 685 } 686 687 if (array_key_exists('inventory_mode', $host_inventory)) { 688 $hosts_inventory[] = ['hostid' => $host['hostid']] + $host_inventory; 689 } 690 } 691 unset($host); 692 693 DB::insertBatch('hosts_groups', $hosts_groups); 694 695 if ($hosts_tags) { 696 DB::insert('host_tag', $hosts_tags); 697 } 698 699 if ($hosts_interfaces) { 700 API::HostInterface()->create($hosts_interfaces); 701 } 702 703 $this->createHostMacros($hosts); 704 705 while ($templates_hostids) { 706 $templateid = key($templates_hostids); 707 $link_hostids = reset($templates_hostids); 708 $link_templateids = [$templateid]; 709 unset($templates_hostids[$templateid]); 710 711 foreach ($templates_hostids as $templateid => $hostids) { 712 if ($link_hostids === $hostids) { 713 $link_templateids[] = $templateid; 714 unset($templates_hostids[$templateid]); 715 } 716 } 717 718 $this->link($link_templateids, $link_hostids); 719 } 720 721 if ($hosts_inventory) { 722 DB::insert('host_inventory', $hosts_inventory, false); 723 } 724 725 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_HOST, $hosts); 726 727 return ['hostids' => array_column($hosts, 'hostid')]; 728 } 729 730 /** 731 * Update host. 732 * 733 * @param array $hosts An array with hosts data. 734 * @param string $hosts[]['hostid'] Host ID. 735 * @param string $hosts[]['host'] Host technical name (optional). 736 * @param string $hosts[]['name'] Host visible name (optional). 737 * @param array $hosts[]['groups'] An array of host group objects with IDs that host will be replaced to. 738 * @param int $hosts[]['status'] Status of the host (optional). 739 * @param array $hosts[]['interfaces'] An array of host interfaces data to be replaced. 740 * @param int $hosts[]['interfaces']['type'] Interface type. 741 * @param int $hosts[]['interfaces']['main'] Is this the default interface to use. 742 * @param string $hosts[]['interfaces']['ip'] Interface IP (optional). 743 * @param int $hosts[]['interfaces']['port'] Interface port (optional). 744 * @param int $hosts[]['interfaces']['useip'] Interface should use IP (optional). 745 * @param string $hosts[]['interfaces']['dns'] Interface should use DNS (optional). 746 * @param int $hosts[]['interfaces']['details'] Interface additional fields (optional). 747 * @param int $hosts[]['proxy_hostid'] ID of the proxy that is used to monitor the host (optional). 748 * @param int $hosts[]['ipmi_authtype'] IPMI authentication type (optional). 749 * @param int $hosts[]['ipmi_privilege'] IPMI privilege (optional). 750 * @param string $hosts[]['ipmi_username'] IPMI username (optional). 751 * @param string $hosts[]['ipmi_password'] IPMI password (optional). 752 * @param array $hosts[]['tags'] An array of tags (optional). 753 * @param string $hosts[]['tags'][]['tag'] Tag name. 754 * @param string $hosts[]['tags'][]['value'] Tag value. 755 * @param array $hosts[]['inventory'] An array of host inventory data (optional). 756 * @param array $hosts[]['macros'] An array of host macros (optional). 757 * @param string $hosts[]['macros'][]['macro'] Host macro (required if "macros" is set). 758 * @param array $hosts[]['templates'] An array of template objects with IDs that will be linked to host (optional). 759 * @param string $hosts[]['templates'][]['templateid'] Template ID (required if "templates" is set). 760 * @param array $hosts[]['templates_clear'] Templates to unlink and clear from the host (optional). 761 * @param string $hosts[]['templates_clear'][]['templateid'] Template ID (required if "templates" is set). 762 * @param string $hosts[]['tls_connect'] Connections to host (optional). 763 * @param string $hosts[]['tls_accept'] Connections from host (optional). 764 * @param string $hosts[]['tls_psk_identity'] PSK identity (required if "PSK" type is set). 765 * @param string $hosts[]['tls_psk'] PSK (required if "PSK" type is set). 766 * @param string $hosts[]['tls_issuer'] Certificate issuer (optional). 767 * @param string $hosts[]['tls_subject'] Certificate subject (optional). 768 * 769 * @return array 770 */ 771 public function update($hosts) { 772 $hosts = $this->validateUpdate($hosts, $db_hosts); 773 774 $inventories = []; 775 foreach ($hosts as &$host) { 776 // If visible name is not given or empty it should be set to host name. 777 if (array_key_exists('host', $host) && (!array_key_exists('name', $host) || !trim($host['name']))) { 778 $host['name'] = $host['host']; 779 } 780 781 // Fetch fields required to update host inventory. 782 if (array_key_exists('inventory', $host)) { 783 $inventory = $host['inventory']; 784 $inventory['hostid'] = $host['hostid']; 785 786 $inventories[] = $inventory; 787 } 788 } 789 unset($host); 790 791 $inventories = $this->extendObjects('host_inventory', $inventories, ['inventory_mode']); 792 $inventories = zbx_toHash($inventories, 'hostid'); 793 794 $this->updateHostMacros($hosts, $db_hosts); 795 796 foreach ($hosts as &$host) { 797 unset($host['macros']); 798 } 799 unset($host); 800 801 $hosts = $this->extendObjectsByKey($hosts, $db_hosts, 'hostid', ['tls_connect', 'tls_accept', 'tls_issuer', 802 'tls_subject', 'tls_psk_identity', 'tls_psk' 803 ]); 804 805 foreach ($hosts as $host) { 806 // Extend host inventory with the required data. 807 if (array_key_exists('inventory', $host) && $host['inventory']) { 808 // If inventory mode is HOST_INVENTORY_DISABLED, database record is not created. 809 if (array_key_exists('inventory_mode', $inventories[$host['hostid']]) 810 && ($inventories[$host['hostid']]['inventory_mode'] == HOST_INVENTORY_MANUAL 811 || $inventories[$host['hostid']]['inventory_mode'] == HOST_INVENTORY_AUTOMATIC)) { 812 $host['inventory'] = $inventories[$host['hostid']]; 813 } 814 } 815 816 $data = $host; 817 $data['hosts'] = ['hostid' => $host['hostid']]; 818 $result = $this->massUpdate($data); 819 820 if (!$result) { 821 self::exception(ZBX_API_ERROR_INTERNAL, _('Host update failed.')); 822 } 823 } 824 825 $this->updateTags(array_column($hosts, 'tags', 'hostid')); 826 827 return ['hostids' => array_column($hosts, 'hostid')]; 828 } 829 830 /** 831 * Additionally allows to create new interfaces on hosts. 832 * 833 * Checks write permissions for hosts. 834 * 835 * Additional supported $data parameters are: 836 * - interfaces - an array of interfaces to create on the hosts 837 * - templates - an array of templates to link to the hosts, overrides the CHostGeneral::massAdd() 838 * 'templates' parameter 839 * 840 * @param array $data 841 * 842 * @return array 843 */ 844 public function massAdd(array $data) { 845 $hosts = isset($data['hosts']) ? zbx_toArray($data['hosts']) : []; 846 $hostIds = zbx_objectValues($hosts, 'hostid'); 847 848 $this->checkPermissions($hostIds, _('You do not have permission to perform this operation.')); 849 850 // add new interfaces 851 if (!empty($data['interfaces'])) { 852 API::HostInterface()->massAdd([ 853 'hosts' => $data['hosts'], 854 'interfaces' => zbx_toArray($data['interfaces']) 855 ]); 856 } 857 858 // rename the "templates" parameter to the common "templates_link" 859 if (isset($data['templates'])) { 860 $data['templates_link'] = $data['templates']; 861 unset($data['templates']); 862 } 863 864 $data['templates'] = []; 865 866 return parent::massAdd($data); 867 } 868 869 /** 870 * Mass update hosts. 871 * 872 * @param array $hosts multidimensional array with Hosts data 873 * @param array $hosts['hosts'] Array of Host objects to update 874 * @param string $hosts['fields']['host'] Host name. 875 * @param array $hosts['fields']['groupids'] HostGroup IDs add Host to. 876 * @param int $hosts['fields']['port'] Port. OPTIONAL 877 * @param int $hosts['fields']['status'] Host Status. OPTIONAL 878 * @param int $hosts['fields']['useip'] Use IP. OPTIONAL 879 * @param string $hosts['fields']['dns'] DNS. OPTIONAL 880 * @param string $hosts['fields']['ip'] IP. OPTIONAL 881 * @param int $hosts['fields']['details'] Details. OPTIONAL 882 * @param int $hosts['fields']['proxy_hostid'] Proxy Host ID. OPTIONAL 883 * @param int $hosts['fields']['ipmi_authtype'] IPMI authentication type. OPTIONAL 884 * @param int $hosts['fields']['ipmi_privilege'] IPMI privilege. OPTIONAL 885 * @param string $hosts['fields']['ipmi_username'] IPMI username. OPTIONAL 886 * @param string $hosts['fields']['ipmi_password'] IPMI password. OPTIONAL 887 * 888 * @return boolean 889 */ 890 public function massUpdate($data) { 891 if (!array_key_exists('hosts', $data) || !is_array($data['hosts'])) { 892 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'hosts')); 893 } 894 895 $hosts = zbx_toArray($data['hosts']); 896 $inputHostIds = zbx_objectValues($hosts, 'hostid'); 897 $hostids = array_unique($inputHostIds); 898 899 sort($hostids); 900 901 $db_hosts = $this->get([ 902 'output' => ['hostid', 'proxy_hostid', 'host', 'status', 'ipmi_authtype', 'ipmi_privilege', 'ipmi_username', 903 'ipmi_password', 'name', 'description', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject', 904 'tls_psk_identity', 'tls_psk', 'inventory_mode' 905 ], 906 'hostids' => $hostids, 907 'editable' => true, 908 'preservekeys' => true 909 ]); 910 911 foreach ($hosts as $host) { 912 if (!array_key_exists($host['hostid'], $db_hosts)) { 913 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 914 } 915 } 916 917 // Check inventory mode value. 918 if (array_key_exists('inventory_mode', $data)) { 919 $valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC]; 920 $inventory_mode = new CLimitedSetValidator([ 921 'values' => $valid_inventory_modes, 922 'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode', 923 _s('value must be one of %1$s', implode(', ', $valid_inventory_modes))) 924 ]); 925 $this->checkValidator($data['inventory_mode'], $inventory_mode); 926 } 927 928 // Check connection fields only for massupdate action. 929 if (array_key_exists('tls_connect', $data) || array_key_exists('tls_accept', $data) 930 || array_key_exists('tls_psk_identity', $data) || array_key_exists('tls_psk', $data) 931 || array_key_exists('tls_issuer', $data) || array_key_exists('tls_subject', $data)) { 932 if (!array_key_exists('tls_connect', $data) || !array_key_exists('tls_accept', $data)) { 933 self::exception(ZBX_API_ERROR_PERMISSIONS, _( 934 'Cannot update host encryption settings. Connection settings for both directions should be specified.' 935 )); 936 } 937 938 // Clean PSK fields. 939 if ($data['tls_connect'] != HOST_ENCRYPTION_PSK && !($data['tls_accept'] & HOST_ENCRYPTION_PSK)) { 940 $data['tls_psk_identity'] = ''; 941 $data['tls_psk'] = ''; 942 } 943 944 // Clean certificate fields. 945 if ($data['tls_connect'] != HOST_ENCRYPTION_CERTIFICATE 946 && !($data['tls_accept'] & HOST_ENCRYPTION_CERTIFICATE)) { 947 $data['tls_issuer'] = ''; 948 $data['tls_subject'] = ''; 949 } 950 } 951 952 $this->validateEncryption([$data]); 953 954 if (array_key_exists('groups', $data) && !$data['groups'] && $db_hosts) { 955 $host = reset($db_hosts); 956 957 self::exception(ZBX_API_ERROR_PARAMETERS, 958 _s('Host "%1$s" cannot be without host group.', $host['host']) 959 ); 960 } 961 962 // Property 'auto_compress' is not supported for hosts. 963 if (array_key_exists('auto_compress', $data)) { 964 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 965 } 966 967 /* 968 * Update hosts properties 969 */ 970 if (isset($data['name'])) { 971 if (count($hosts) > 1) { 972 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update visible host name.')); 973 } 974 } 975 976 if (array_key_exists('host', $data)) { 977 $host_name_parser = new CHostNameParser(); 978 979 if ($host_name_parser->parse($data['host']) != CParser::PARSE_SUCCESS) { 980 self::exception(ZBX_API_ERROR_PARAMETERS, 981 _s('Incorrect characters used for host name "%1$s".', $data['host']) 982 ); 983 } 984 985 if (count($hosts) > 1) { 986 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update host name.')); 987 } 988 989 $curHost = reset($hosts); 990 991 $sameHostnameHost = $this->get([ 992 'output' => ['hostid'], 993 'filter' => ['host' => $data['host']], 994 'nopermissions' => true, 995 'limit' => 1 996 ]); 997 $sameHostnameHost = reset($sameHostnameHost); 998 if ($sameHostnameHost && (bccomp($sameHostnameHost['hostid'], $curHost['hostid']) != 0)) { 999 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host "%1$s" already exists.', $data['host'])); 1000 } 1001 1002 // can't add host with the same name as existing template 1003 $sameHostnameTemplate = API::Template()->get([ 1004 'output' => ['templateid'], 1005 'filter' => ['host' => $data['host']], 1006 'nopermissions' => true, 1007 'limit' => 1 1008 ]); 1009 if ($sameHostnameTemplate) { 1010 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Template "%1$s" already exists.', $data['host'])); 1011 } 1012 } 1013 1014 if (isset($data['groups'])) { 1015 $updateGroups = $data['groups']; 1016 } 1017 1018 if (isset($data['interfaces'])) { 1019 $updateInterfaces = $data['interfaces']; 1020 } 1021 1022 if (array_key_exists('templates_clear', $data)) { 1023 $updateTemplatesClear = zbx_toArray($data['templates_clear']); 1024 } 1025 1026 if (isset($data['templates'])) { 1027 $updateTemplates = $data['templates']; 1028 } 1029 1030 if (isset($data['macros'])) { 1031 $updateMacros = $data['macros']; 1032 } 1033 1034 // second check is necessary, because import incorrectly inputs unset 'inventory' as empty string rather than null 1035 if (isset($data['inventory']) && $data['inventory']) { 1036 if (isset($data['inventory_mode']) && $data['inventory_mode'] == HOST_INVENTORY_DISABLED) { 1037 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.')); 1038 } 1039 1040 $updateInventory = $data['inventory']; 1041 $updateInventory['inventory_mode'] = null; 1042 } 1043 1044 if (isset($data['inventory_mode'])) { 1045 if (!isset($updateInventory)) { 1046 $updateInventory = []; 1047 } 1048 $updateInventory['inventory_mode'] = $data['inventory_mode']; 1049 } 1050 1051 unset($data['hosts'], $data['groups'], $data['interfaces'], $data['templates_clear'], $data['templates'], 1052 $data['macros'], $data['inventory'], $data['inventory_mode']); 1053 1054 if (!zbx_empty($data)) { 1055 DB::update('hosts', [ 1056 'values' => $data, 1057 'where' => ['hostid' => $hostids] 1058 ]); 1059 } 1060 1061 /* 1062 * Update template linkage 1063 */ 1064 if (isset($updateTemplatesClear)) { 1065 $templateIdsClear = zbx_objectValues($updateTemplatesClear, 'templateid'); 1066 1067 if ($updateTemplatesClear) { 1068 $this->massRemove(['hostids' => $hostids, 'templateids_clear' => $templateIdsClear]); 1069 } 1070 } 1071 else { 1072 $templateIdsClear = []; 1073 } 1074 1075 // unlink templates 1076 if (isset($updateTemplates)) { 1077 $hostTemplates = API::Template()->get([ 1078 'hostids' => $hostids, 1079 'output' => ['templateid'], 1080 'preservekeys' => true 1081 ]); 1082 1083 $hostTemplateids = array_keys($hostTemplates); 1084 $newTemplateids = zbx_objectValues($updateTemplates, 'templateid'); 1085 1086 $templatesToDel = array_diff($hostTemplateids, $newTemplateids); 1087 $templatesToDel = array_diff($templatesToDel, $templateIdsClear); 1088 1089 if ($templatesToDel) { 1090 $result = $this->massRemove([ 1091 'hostids' => $hostids, 1092 'templateids' => $templatesToDel 1093 ]); 1094 if (!$result) { 1095 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot unlink template')); 1096 } 1097 } 1098 } 1099 1100 /* 1101 * update interfaces 1102 */ 1103 if (isset($updateInterfaces)) { 1104 foreach($hostids as $hostid) { 1105 API::HostInterface()->replaceHostInterfaces([ 1106 'hostid' => $hostid, 1107 'interfaces' => $updateInterfaces 1108 ]); 1109 } 1110 } 1111 1112 // link new templates 1113 if (isset($updateTemplates)) { 1114 $result = $this->massAdd([ 1115 'hosts' => $hosts, 1116 'templates' => $updateTemplates 1117 ]); 1118 1119 if (!$result) { 1120 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot link template')); 1121 } 1122 } 1123 1124 // macros 1125 if (isset($updateMacros)) { 1126 DB::delete('hostmacro', ['hostid' => $hostids]); 1127 1128 $this->massAdd([ 1129 'hosts' => $hosts, 1130 'macros' => $updateMacros 1131 ]); 1132 } 1133 1134 /* 1135 * Inventory 1136 */ 1137 if (isset($updateInventory)) { 1138 // disabling inventory 1139 if ($updateInventory['inventory_mode'] == HOST_INVENTORY_DISABLED) { 1140 $sql = 'DELETE FROM host_inventory WHERE '.dbConditionInt('hostid', $hostids); 1141 if (!DBexecute($sql)) { 1142 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete inventory.')); 1143 } 1144 } 1145 // changing inventory mode or setting inventory fields 1146 else { 1147 $existingInventoriesDb = DBfetchArrayAssoc(DBselect( 1148 'SELECT hostid,inventory_mode'. 1149 ' FROM host_inventory'. 1150 ' WHERE '.dbConditionInt('hostid', $hostids) 1151 ), 'hostid'); 1152 1153 // check existing host inventory data 1154 $automaticHostIds = []; 1155 if ($updateInventory['inventory_mode'] === null) { 1156 foreach ($hostids as $hostid) { 1157 // if inventory is disabled for one of the updated hosts, throw an exception 1158 if (!isset($existingInventoriesDb[$hostid])) { 1159 $host = get_host_by_hostid($hostid); 1160 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1161 'Inventory disabled for host "%1$s".', $host['host'] 1162 )); 1163 } 1164 // if inventory mode is set to automatic, save its ID for later usage 1165 elseif ($existingInventoriesDb[$hostid]['inventory_mode'] == HOST_INVENTORY_AUTOMATIC) { 1166 $automaticHostIds[] = $hostid; 1167 } 1168 } 1169 } 1170 1171 $inventoriesToSave = []; 1172 foreach ($hostids as $hostid) { 1173 $hostInventory = $updateInventory; 1174 $hostInventory['hostid'] = $hostid; 1175 1176 // if no 'inventory_mode' has been passed, set inventory 'inventory_mode' from DB 1177 if ($updateInventory['inventory_mode'] === null) { 1178 $hostInventory['inventory_mode'] = $existingInventoriesDb[$hostid]['inventory_mode']; 1179 } 1180 1181 $inventoriesToSave[$hostid] = $hostInventory; 1182 } 1183 1184 // when updating automatic inventory, ignore fields that have items linked to them 1185 if ($updateInventory['inventory_mode'] == HOST_INVENTORY_AUTOMATIC 1186 || ($updateInventory['inventory_mode'] === null && $automaticHostIds)) { 1187 1188 $itemsToInventories = API::item()->get([ 1189 'output' => ['inventory_link', 'hostid'], 1190 'hostids' => $automaticHostIds ? $automaticHostIds : $hostids, 1191 'nopermissions' => true 1192 ]); 1193 1194 $inventoryFields = getHostInventories(); 1195 foreach ($itemsToInventories as $hinv) { 1196 // 0 means 'no link' 1197 if ($hinv['inventory_link'] != 0) { 1198 $inventoryName = $inventoryFields[$hinv['inventory_link']]['db_field']; 1199 unset($inventoriesToSave[$hinv['hostid']][$inventoryName]); 1200 } 1201 } 1202 } 1203 1204 // save inventory data 1205 foreach ($inventoriesToSave as $inventory) { 1206 $hostid = $inventory['hostid']; 1207 if (isset($existingInventoriesDb[$hostid])) { 1208 DB::update('host_inventory', [ 1209 'values' => $inventory, 1210 'where' => ['hostid' => $hostid] 1211 ]); 1212 } 1213 else { 1214 DB::insert('host_inventory', [$inventory], false); 1215 } 1216 } 1217 } 1218 } 1219 1220 /* 1221 * Update host and host group linkage. This procedure should be done the last because user can unlink 1222 * him self from a group with write permissions leaving only read premissions. Thus other procedures, like 1223 * host-template linkage, inventory update, macros update, must be done before this. 1224 */ 1225 if (isset($updateGroups)) { 1226 $updateGroups = zbx_toArray($updateGroups); 1227 1228 $hostGroups = API::HostGroup()->get([ 1229 'output' => ['groupid'], 1230 'hostids' => $hostids 1231 ]); 1232 $hostGroupIds = zbx_objectValues($hostGroups, 'groupid'); 1233 $newGroupIds = zbx_objectValues($updateGroups, 'groupid'); 1234 1235 $groupsToAdd = array_diff($newGroupIds, $hostGroupIds); 1236 if ($groupsToAdd) { 1237 $this->massAdd([ 1238 'hosts' => $hosts, 1239 'groups' => zbx_toObject($groupsToAdd, 'groupid') 1240 ]); 1241 } 1242 1243 $groupIdsToDelete = array_diff($hostGroupIds, $newGroupIds); 1244 if ($groupIdsToDelete) { 1245 $this->massRemove([ 1246 'hostids' => $hostids, 1247 'groupids' => $groupIdsToDelete 1248 ]); 1249 } 1250 } 1251 1252 $new_hosts = []; 1253 foreach ($db_hosts as $hostid => $db_host) { 1254 $new_host = $data + $db_host; 1255 if ($new_host['status'] != $db_host['status']) { 1256 info(_s('Updated status of host "%1$s".', $new_host['host'])); 1257 } 1258 1259 $new_hosts[] = $new_host; 1260 } 1261 1262 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_HOST, $new_hosts, $db_hosts); 1263 1264 return ['hostids' => $inputHostIds]; 1265 } 1266 1267 /** 1268 * Additionally allows to remove interfaces from hosts. 1269 * 1270 * Checks write permissions for hosts. 1271 * 1272 * Additional supported $data parameters are: 1273 * - interfaces - an array of interfaces to delete from the hosts 1274 * 1275 * @throws APIException if the input is invalid. 1276 * 1277 * @param array $data 1278 * 1279 * @return array 1280 */ 1281 public function massRemove(array $data) { 1282 if (!array_key_exists('hostids', $data) || $data['hostids'] === null) { 1283 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1284 } 1285 1286 $data['hostids'] = zbx_toArray($data['hostids']); 1287 1288 $this->checkPermissions($data['hostids'], _('No permissions to referred object or it does not exist!')); 1289 1290 if (isset($data['interfaces'])) { 1291 $options = [ 1292 'hostids' => $data['hostids'], 1293 'interfaces' => zbx_toArray($data['interfaces']) 1294 ]; 1295 API::HostInterface()->massRemove($options); 1296 } 1297 1298 // rename the "templates" parameter to the common "templates_link" 1299 if (isset($data['templateids'])) { 1300 $data['templateids_link'] = $data['templateids']; 1301 unset($data['templateids']); 1302 } 1303 1304 $data['templateids'] = []; 1305 1306 return parent::massRemove($data); 1307 } 1308 1309 /** 1310 * Validates the input parameters for the delete() method. 1311 * 1312 * @throws APIException if the input is invalid 1313 * 1314 * @param array $hostIds 1315 * @param bool $nopermissions 1316 */ 1317 protected function validateDelete(array $hostIds, $nopermissions = false) { 1318 if (!$hostIds) { 1319 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 1320 } 1321 1322 if (!$nopermissions) { 1323 $this->checkPermissions($hostIds, _('No permissions to referred object or it does not exist!')); 1324 } 1325 1326 $this->validateDeleteCheckMaintenances($hostIds); 1327 } 1328 1329 /** 1330 * Validates if hosts may be deleted, due to maintenance constrain. 1331 * 1332 * @throws APIException if a constrain failed 1333 * 1334 * @param array $hostids 1335 */ 1336 protected function validateDeleteCheckMaintenances(array $hostids) { 1337 $maintenance = DBfetch(DBselect( 1338 'SELECT m.name'. 1339 ' FROM maintenances m'. 1340 ' WHERE NOT EXISTS ('. 1341 'SELECT NULL'. 1342 ' FROM maintenances_hosts mh'. 1343 ' WHERE m.maintenanceid=mh.maintenanceid'. 1344 ' AND '.dbConditionInt('mh.hostid', $hostids, true). 1345 ')'. 1346 ' AND NOT EXISTS ('. 1347 'SELECT NULL'. 1348 ' FROM maintenances_groups mg'. 1349 ' WHERE m.maintenanceid=mg.maintenanceid'. 1350 ')' 1351 )); 1352 1353 if ($maintenance) { 1354 self::exception(ZBX_API_ERROR_PARAMETERS, _n( 1355 'Cannot delete host because maintenance "%1$s" must contain at least one host or host group.', 1356 'Cannot delete selected hosts because maintenance "%1$s" must contain at least one host or host group.', 1357 $maintenance['name'], 1358 count($hostids) 1359 )); 1360 } 1361 } 1362 1363 /** 1364 * Delete Host. 1365 * 1366 * @param array $hostIds 1367 * @param bool $nopermissions 1368 * 1369 * @return array 1370 */ 1371 public function delete(array $hostIds, $nopermissions = false) { 1372 $this->validateDelete($hostIds, $nopermissions); 1373 1374 // delete the discovery rules first 1375 $del_rules = API::DiscoveryRule()->get([ 1376 'output' => [], 1377 'hostids' => $hostIds, 1378 'nopermissions' => true, 1379 'preservekeys' => true 1380 ]); 1381 if ($del_rules) { 1382 CDiscoveryRuleManager::delete(array_keys($del_rules)); 1383 } 1384 1385 // delete the items 1386 $del_items = API::Item()->get([ 1387 'output' => [], 1388 'templateids' => $hostIds, 1389 'nopermissions' => true, 1390 'preservekeys' => true 1391 ]); 1392 if ($del_items) { 1393 CItemManager::delete(array_keys($del_items)); 1394 } 1395 1396 // delete web tests 1397 $delHttptests = []; 1398 $dbHttptests = get_httptests_by_hostid($hostIds); 1399 while ($dbHttptest = DBfetch($dbHttptests)) { 1400 $delHttptests[$dbHttptest['httptestid']] = $dbHttptest['httptestid']; 1401 } 1402 if (!empty($delHttptests)) { 1403 API::HttpTest()->delete($delHttptests, true); 1404 } 1405 1406 // delete host from maps 1407 if (!empty($hostIds)) { 1408 DB::delete('sysmaps_elements', [ 1409 'elementtype' => SYSMAP_ELEMENT_TYPE_HOST, 1410 'elementid' => $hostIds 1411 ]); 1412 } 1413 1414 // disable actions 1415 // actions from conditions 1416 $actionids = []; 1417 $sql = 'SELECT DISTINCT actionid'. 1418 ' FROM conditions'. 1419 ' WHERE conditiontype='.CONDITION_TYPE_HOST. 1420 ' AND '.dbConditionString('value', $hostIds); 1421 $dbActions = DBselect($sql); 1422 while ($dbAction = DBfetch($dbActions)) { 1423 $actionids[$dbAction['actionid']] = $dbAction['actionid']; 1424 } 1425 1426 // actions from operations 1427 $sql = 'SELECT DISTINCT o.actionid'. 1428 ' FROM operations o, opcommand_hst oh'. 1429 ' WHERE o.operationid=oh.operationid'. 1430 ' AND '.dbConditionInt('oh.hostid', $hostIds); 1431 $dbActions = DBselect($sql); 1432 while ($dbAction = DBfetch($dbActions)) { 1433 $actionids[$dbAction['actionid']] = $dbAction['actionid']; 1434 } 1435 1436 if (!empty($actionids)) { 1437 $update = []; 1438 $update[] = [ 1439 'values' => ['status' => ACTION_STATUS_DISABLED], 1440 'where' => ['actionid' => $actionids] 1441 ]; 1442 DB::update('actions', $update); 1443 } 1444 1445 // delete action conditions 1446 DB::delete('conditions', [ 1447 'conditiontype' => CONDITION_TYPE_HOST, 1448 'value' => $hostIds 1449 ]); 1450 1451 // delete action operation commands 1452 $operationids = []; 1453 $sql = 'SELECT DISTINCT oh.operationid'. 1454 ' FROM opcommand_hst oh'. 1455 ' WHERE '.dbConditionInt('oh.hostid', $hostIds); 1456 $dbOperations = DBselect($sql); 1457 while ($dbOperation = DBfetch($dbOperations)) { 1458 $operationids[$dbOperation['operationid']] = $dbOperation['operationid']; 1459 } 1460 1461 DB::delete('opcommand_hst', [ 1462 'hostid' => $hostIds 1463 ]); 1464 1465 // delete empty operations 1466 $delOperationids = []; 1467 $sql = 'SELECT DISTINCT o.operationid'. 1468 ' FROM operations o'. 1469 ' WHERE '.dbConditionInt('o.operationid', $operationids). 1470 ' AND NOT EXISTS(SELECT oh.opcommand_hstid FROM opcommand_hst oh WHERE oh.operationid=o.operationid)'; 1471 $dbOperations = DBselect($sql); 1472 while ($dbOperation = DBfetch($dbOperations)) { 1473 $delOperationids[$dbOperation['operationid']] = $dbOperation['operationid']; 1474 } 1475 1476 DB::delete('operations', [ 1477 'operationid' => $delOperationids 1478 ]); 1479 1480 $db_hosts = API::Host()->get([ 1481 'output' => ['hostid', 'name'], 1482 'hostids' => $hostIds, 1483 'nopermissions' => true 1484 ]); 1485 1486 // delete host inventory 1487 DB::delete('host_inventory', ['hostid' => $hostIds]); 1488 1489 // delete host 1490 DB::delete('hosts', ['hostid' => $hostIds]); 1491 1492 // TODO: remove info from API 1493 foreach ($db_hosts as $db_host) { 1494 info(_s('Deleted: Host "%1$s".', $db_host['name'])); 1495 } 1496 1497 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_HOST, $db_hosts); 1498 1499 return ['hostids' => $hostIds]; 1500 } 1501 1502 /** 1503 * Retrieves and adds additional requested data to the result set. 1504 * 1505 * @param array $options 1506 * @param array $result 1507 * 1508 * @return array 1509 */ 1510 protected function addRelatedObjects(array $options, array $result) { 1511 $result = parent::addRelatedObjects($options, $result); 1512 1513 $hostids = array_keys($result); 1514 1515 // adding inventory 1516 if ($options['selectInventory'] !== null) { 1517 $inventory = API::getApiService()->select('host_inventory', [ 1518 'output' => $options['selectInventory'], 1519 'filter' => ['hostid' => $hostids], 1520 'preservekeys' => true 1521 ]); 1522 1523 $inventory = $this->unsetExtraFields($inventory, ['hostid', 'inventory_mode'], []); 1524 $relation_map = $this->createRelationMap($result, 'hostid', 'hostid'); 1525 $result = $relation_map->mapOne($result, $inventory, 'inventory'); 1526 } 1527 1528 // adding hostinterfaces 1529 if ($options['selectInterfaces'] !== null) { 1530 if ($options['selectInterfaces'] != API_OUTPUT_COUNT) { 1531 $interfaces = API::HostInterface()->get([ 1532 'output' => $this->outputExtend($options['selectInterfaces'], ['hostid', 'interfaceid']), 1533 'hostids' => $hostids, 1534 'nopermissions' => true, 1535 'preservekeys' => true 1536 ]); 1537 1538 // we need to order interfaces for proper linkage and viewing 1539 order_result($interfaces, 'interfaceid', ZBX_SORT_UP); 1540 1541 $relationMap = $this->createRelationMap($interfaces, 'hostid', 'interfaceid'); 1542 1543 $interfaces = $this->unsetExtraFields($interfaces, ['hostid', 'interfaceid'], 1544 $options['selectInterfaces'] 1545 ); 1546 $result = $relationMap->mapMany($result, $interfaces, 'interfaces', $options['limitSelects']); 1547 } 1548 else { 1549 $interfaces = API::HostInterface()->get([ 1550 'hostids' => $hostids, 1551 'nopermissions' => true, 1552 'countOutput' => true, 1553 'groupCount' => true 1554 ]); 1555 1556 $interfaces = zbx_toHash($interfaces, 'hostid'); 1557 foreach ($result as $hostid => $host) { 1558 $result[$hostid]['interfaces'] = array_key_exists($hostid, $interfaces) 1559 ? $interfaces[$hostid]['rowscount'] 1560 : '0'; 1561 } 1562 } 1563 } 1564 1565 // Adding dashboards. 1566 if ($options['selectDashboards'] !== null) { 1567 [$hosts_templates, $templateids] = CApiHostHelper::getParentTemplates($hostids); 1568 1569 if ($options['selectDashboards'] != API_OUTPUT_COUNT) { 1570 $dashboards = API::TemplateDashboard()->get([ 1571 'output' => $this->outputExtend($options['selectDashboards'], ['templateid']), 1572 'templateids' => $templateids 1573 ]); 1574 1575 if (!is_null($options['limitSelects'])) { 1576 order_result($dashboards, 'name'); 1577 } 1578 1579 foreach ($result as &$host) { 1580 foreach ($hosts_templates[$host['hostid']] as $templateid) { 1581 foreach ($dashboards as $dashboard) { 1582 if ($dashboard['templateid'] == $templateid) { 1583 $host['dashboards'][] = $dashboard; 1584 } 1585 } 1586 } 1587 } 1588 unset($host); 1589 } 1590 else { 1591 $dashboards = API::TemplateDashboard()->get([ 1592 'templateids' => $templateids, 1593 'countOutput' => true, 1594 'groupCount' => true 1595 ]); 1596 1597 foreach ($result as $hostid => $host) { 1598 $result[$hostid]['dashboards'] = 0; 1599 1600 foreach ($dashboards as $dashboard) { 1601 if (in_array($dashboard['templateid'], $hosts_templates[$hostid])) { 1602 $result[$hostid]['dashboards'] += $dashboard['rowscount']; 1603 } 1604 } 1605 1606 $result[$hostid]['dashboards'] = (string) $result[$hostid]['dashboards']; 1607 } 1608 } 1609 } 1610 1611 // adding discovery rule 1612 if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) { 1613 // discovered items 1614 $discoveryRules = DBFetchArray(DBselect( 1615 'SELECT hd.hostid,hd2.parent_itemid'. 1616 ' FROM host_discovery hd,host_discovery hd2'. 1617 ' WHERE '.dbConditionInt('hd.hostid', $hostids). 1618 ' AND hd.parent_hostid=hd2.hostid' 1619 )); 1620 $relationMap = $this->createRelationMap($discoveryRules, 'hostid', 'parent_itemid'); 1621 1622 $discoveryRules = API::DiscoveryRule()->get([ 1623 'output' => $options['selectDiscoveryRule'], 1624 'itemids' => $relationMap->getRelatedIds(), 1625 'preservekeys' => true 1626 ]); 1627 $result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule'); 1628 } 1629 1630 // adding host discovery 1631 if ($options['selectHostDiscovery'] !== null) { 1632 $hostDiscoveries = API::getApiService()->select('host_discovery', [ 1633 'output' => $this->outputExtend($options['selectHostDiscovery'], ['hostid']), 1634 'filter' => ['hostid' => $hostids], 1635 'preservekeys' => true 1636 ]); 1637 $relationMap = $this->createRelationMap($hostDiscoveries, 'hostid', 'hostid'); 1638 1639 $hostDiscoveries = $this->unsetExtraFields($hostDiscoveries, ['hostid'], 1640 $options['selectHostDiscovery'] 1641 ); 1642 $result = $relationMap->mapOne($result, $hostDiscoveries, 'hostDiscovery'); 1643 } 1644 1645 if ($options['selectInheritedTags'] !== null && $options['selectInheritedTags'] != API_OUTPUT_COUNT) { 1646 $hosts_templates = []; 1647 [$hosts_templates, $templateids] = CApiHostHelper::getParentTemplates($hostids); 1648 1649 $templates = API::Template()->get([ 1650 'output' => [], 1651 'selectTags' => ['tag', 'value'], 1652 'templateids' => $templateids, 1653 'preservekeys' => true, 1654 'nopermissions' => true 1655 ]); 1656 1657 // Set "inheritedTags" for each host. 1658 foreach ($result as &$host) { 1659 $tags = []; 1660 1661 // Get IDs and template tag values from previously stored variables. 1662 foreach ($hosts_templates[$host['hostid']] as $templateid) { 1663 foreach ($templates[$templateid]['tags'] as $tag) { 1664 foreach ($tags as $_tag) { 1665 // Skip tags with same name and value. 1666 if ($_tag['tag'] === $tag['tag'] && $_tag['value'] === $tag['value']) { 1667 continue 2; 1668 } 1669 } 1670 $tags[] = $tag; 1671 } 1672 } 1673 1674 $host['inheritedTags'] = $this->unsetExtraFields($tags, ['tag', 'value'], 1675 $options['selectInheritedTags'] 1676 ); 1677 } 1678 } 1679 1680 return $result; 1681 } 1682 1683 /** 1684 * Validates the input parameters for the get() method. 1685 * 1686 * @param array $options 1687 * 1688 * @throws APIException if the input is invalid 1689 */ 1690 protected function validateGet(array $options) { 1691 // Validate input parameters. 1692 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 1693 'inheritedTags' => ['type' => API_BOOLEAN, 'default' => false], 1694 'selectInheritedTags' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 1695 'severities' => [ 1696 'type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE | API_NOT_EMPTY, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1)), 'uniq' => true 1697 ], 1698 'withProblemsSuppressed' => ['type' => API_BOOLEAN, 'flags' => API_ALLOW_NULL], 1699 'selectValueMaps' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => 'valuemapid,name,mappings'] 1700 ]]; 1701 $options_filter = array_intersect_key($options, $api_input_rules['fields']); 1702 if (!CApiInputValidator::validate($api_input_rules, $options_filter, '/', $error)) { 1703 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1704 } 1705 } 1706 1707 /** 1708 * Checks if all of the given hosts are available for writing. 1709 * 1710 * @throws APIException if a host is not writable or does not exist 1711 * 1712 * @param array $hostids 1713 * @param string $error 1714 */ 1715 protected function checkPermissions(array $hostids, $error) { 1716 if ($hostids) { 1717 $hostids = array_unique($hostids); 1718 1719 $count = $this->get([ 1720 'countOutput' => true, 1721 'hostids' => $hostids, 1722 'editable' => true 1723 ]); 1724 1725 if ($count != count($hostids)) { 1726 self::exception(ZBX_API_ERROR_PERMISSIONS, $error); 1727 } 1728 } 1729 } 1730 1731 /** 1732 * Validate connections from/to host and PSK fields. 1733 * 1734 * @param array $hosts 1735 * @param string $hosts[]['hostid'] (optional if $db_hosts is null) 1736 * @param int $hosts[]['tls_connect'] (optionsl) 1737 * @param int $hosts[]['tls_accept'] (optional) 1738 * @param string $hosts[]['tls_psk_identity'] (optional) 1739 * @param string $hosts[]['tls_psk'] (optional) 1740 * @param string $hosts[]['tls_issuer'] (optional) 1741 * @param string $hosts[]['tls_subject'] (optional) 1742 * @param array $db_hosts (optional) 1743 * @param int $hosts[<hostid>]['tls_connect'] 1744 * @param int $hosts[<hostid>]['tls_accept'] 1745 * @param string $hosts[<hostid>]['tls_psk_identity'] 1746 * @param string $hosts[<hostid>]['tls_psk'] 1747 * @param string $hosts[<hostid>]['tls_issuer'] 1748 * @param string $hosts[<hostid>]['tls_subject'] 1749 * 1750 * @throws APIException if incorrect encryption options. 1751 */ 1752 protected function validateEncryption(array $hosts, array $db_hosts = null) { 1753 $available_connect_types = [HOST_ENCRYPTION_NONE, HOST_ENCRYPTION_PSK, HOST_ENCRYPTION_CERTIFICATE]; 1754 $min_accept_type = HOST_ENCRYPTION_NONE; 1755 $max_accept_type = HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE; 1756 1757 foreach ($hosts as $host) { 1758 foreach (['tls_connect', 'tls_accept'] as $field_name) { 1759 $$field_name = array_key_exists($field_name, $host) 1760 ? $host[$field_name] 1761 : ($db_hosts !== null ? $db_hosts[$host['hostid']][$field_name] : HOST_ENCRYPTION_NONE); 1762 } 1763 1764 if (!in_array($tls_connect, $available_connect_types)) { 1765 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_connect', 1766 _s('unexpected value "%1$s"', $tls_connect) 1767 )); 1768 } 1769 1770 if ($tls_accept < $min_accept_type || $tls_accept > $max_accept_type) { 1771 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_accept', 1772 _s('unexpected value "%1$s"', $tls_accept) 1773 )); 1774 } 1775 1776 foreach (['tls_psk_identity', 'tls_psk', 'tls_issuer', 'tls_subject'] as $field_name) { 1777 $$field_name = array_key_exists($field_name, $host) 1778 ? $host[$field_name] 1779 : ($db_hosts !== null ? $db_hosts[$host['hostid']][$field_name] : ''); 1780 } 1781 1782 // PSK validation. 1783 if ($tls_connect == HOST_ENCRYPTION_PSK || ($tls_accept & HOST_ENCRYPTION_PSK)) { 1784 if ($tls_psk_identity === '') { 1785 self::exception(ZBX_API_ERROR_PARAMETERS, 1786 _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('cannot be empty')) 1787 ); 1788 } 1789 1790 if ($tls_psk === '') { 1791 self::exception(ZBX_API_ERROR_PARAMETERS, 1792 _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('cannot be empty')) 1793 ); 1794 } 1795 1796 if (!preg_match('/^([0-9a-f]{2})+$/i', $tls_psk)) { 1797 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', 1798 _('an even number of hexadecimal characters is expected') 1799 )); 1800 } 1801 1802 if (strlen($tls_psk) < PSK_MIN_LEN) { 1803 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', 1804 _s('minimum length is %1$s characters', PSK_MIN_LEN) 1805 )); 1806 } 1807 } 1808 else { 1809 if ($tls_psk_identity !== '') { 1810 self::exception(ZBX_API_ERROR_PARAMETERS, 1811 _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('should be empty')) 1812 ); 1813 } 1814 1815 if ($tls_psk !== '') { 1816 self::exception(ZBX_API_ERROR_PARAMETERS, 1817 _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('should be empty')) 1818 ); 1819 } 1820 } 1821 1822 // Certificate validation. 1823 if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE && !($tls_accept & HOST_ENCRYPTION_CERTIFICATE)) { 1824 if ($tls_issuer !== '') { 1825 self::exception(ZBX_API_ERROR_PARAMETERS, 1826 _s('Incorrect value for field "%1$s": %2$s.', 'tls_issuer', _('should be empty')) 1827 ); 1828 } 1829 1830 if ($tls_subject !== '') { 1831 self::exception(ZBX_API_ERROR_PARAMETERS, 1832 _s('Incorrect value for field "%1$s": %2$s.', 'tls_subject', _('should be empty')) 1833 ); 1834 } 1835 } 1836 } 1837 } 1838 1839 /** 1840 * Validates the input parameters for the create() method. 1841 * 1842 * @param array $hosts hosts data array 1843 * 1844 * @throws APIException if the input is invalid. 1845 */ 1846 protected function validateCreate(array &$hosts) { 1847 $hosts = zbx_toArray($hosts); 1848 1849 if (!$hosts) { 1850 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 1851 } 1852 1853 $macro_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 1854 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hostmacro', 'macro')], 1855 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT], 1856 'value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ 1857 ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')], 1858 ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'length' => DB::getFieldLength('hostmacro', 'value')] 1859 ]], 1860 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] 1861 ]]; 1862 1863 $host_name_parser = new CHostNameParser(); 1864 1865 $host_db_fields = ['host' => null]; 1866 1867 $groupids = []; 1868 1869 foreach ($hosts as $index => &$host) { 1870 // Validate mandatory fields. 1871 if (!check_db_fields($host_db_fields, $host)) { 1872 self::exception(ZBX_API_ERROR_PARAMETERS, 1873 _s('Wrong fields for host "%1$s".', array_key_exists('host', $host) ? $host['host'] : '') 1874 ); 1875 } 1876 1877 // Property 'auto_compress' is not supported for hosts. 1878 if (array_key_exists('auto_compress', $host)) { 1879 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 1880 } 1881 1882 // Validate "host" field. 1883 if ($host_name_parser->parse($host['host']) != CParser::PARSE_SUCCESS) { 1884 self::exception(ZBX_API_ERROR_PARAMETERS, 1885 _s('Incorrect characters used for host name "%1$s".', $host['host']) 1886 ); 1887 } 1888 1889 // If visible name is not given or empty it should be set to host name. Required for duplicate checks. 1890 if (!array_key_exists('name', $host) || trim($host['name']) === '') { 1891 $host['name'] = $host['host']; 1892 } 1893 1894 // Validate "groups" field. 1895 if (!array_key_exists('groups', $host) || !is_array($host['groups']) || !$host['groups']) { 1896 self::exception(ZBX_API_ERROR_PARAMETERS, 1897 _s('Host "%1$s" cannot be without host group.', $host['host']) 1898 ); 1899 } 1900 1901 $host['groups'] = zbx_toArray($host['groups']); 1902 1903 foreach ($host['groups'] as $group) { 1904 if (!is_array($group) || (is_array($group) && !array_key_exists('groupid', $group))) { 1905 self::exception(ZBX_API_ERROR_PARAMETERS, 1906 _s('Incorrect value for field "%1$s": %2$s.', 'groups', 1907 _s('the parameter "%1$s" is missing', 'groupid') 1908 ) 1909 ); 1910 } 1911 1912 $groupids[$group['groupid']] = true; 1913 } 1914 1915 // Validate tags. 1916 if (array_key_exists('tags', $host)) { 1917 $this->validateTags($host); 1918 } 1919 1920 if (array_key_exists('macros', $host)) { 1921 if (!CApiInputValidator::validate($macro_rules, $host['macros'], '/'.($index + 1).'/macros', $error)) { 1922 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1923 } 1924 } 1925 } 1926 unset($host); 1927 1928 // Check for duplicate "host" and "name" fields. 1929 $duplicate = CArrayHelper::findDuplicate($hosts, 'host'); 1930 if ($duplicate) { 1931 self::exception(ZBX_API_ERROR_PARAMETERS, 1932 _s('Duplicate host. Host with the same host name "%1$s" already exists in data.', $duplicate['host']) 1933 ); 1934 } 1935 1936 $duplicate = CArrayHelper::findDuplicate($hosts, 'name'); 1937 if ($duplicate) { 1938 self::exception(ZBX_API_ERROR_PARAMETERS, 1939 _s('Duplicate host. Host with the same visible name "%1$s" already exists in data.', $duplicate['name']) 1940 ); 1941 } 1942 1943 // Validate permissions to host groups. 1944 $db_groups = $groupids 1945 ? API::HostGroup()->get([ 1946 'output' => ['groupid'], 1947 'groupids' => array_keys($groupids), 1948 'editable' => true, 1949 'preservekeys' => true 1950 ]) 1951 : []; 1952 1953 foreach ($hosts as $host) { 1954 foreach ($host['groups'] as $group) { 1955 if (!array_key_exists($group['groupid'], $db_groups)) { 1956 self::exception(ZBX_API_ERROR_PERMISSIONS, 1957 _('No permissions to referred object or it does not exist!') 1958 ); 1959 } 1960 } 1961 } 1962 1963 $inventory_fields = zbx_objectValues(getHostInventories(), 'db_field'); 1964 1965 $valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC]; 1966 $inventory_mode = new CLimitedSetValidator([ 1967 'values' => $valid_inventory_modes, 1968 'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode', 1969 _s('value must be one of %1$s', implode(', ', $valid_inventory_modes))) 1970 ]); 1971 1972 $status_validator = new CLimitedSetValidator([ 1973 'values' => [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED], 1974 'messageInvalid' => _('Incorrect status for host "%1$s".') 1975 ]); 1976 1977 $host_names = []; 1978 1979 foreach ($hosts as $host) { 1980 if (array_key_exists('interfaces', $host) && $host['interfaces'] !== null 1981 && !is_array($host['interfaces'])) { 1982 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1983 } 1984 1985 if (array_key_exists('status', $host)) { 1986 $status_validator->setObjectName($host['host']); 1987 $this->checkValidator($host['status'], $status_validator); 1988 } 1989 1990 if (array_key_exists('inventory_mode', $host)) { 1991 $inventory_mode->setObjectName($host['host']); 1992 $this->checkValidator($host['inventory_mode'], $inventory_mode); 1993 } 1994 1995 if (array_key_exists('inventory', $host) && $host['inventory']) { 1996 if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] == HOST_INVENTORY_DISABLED) { 1997 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.')); 1998 } 1999 2000 $fields = array_keys($host['inventory']); 2001 foreach ($fields as $field) { 2002 if (!in_array($field, $inventory_fields)) { 2003 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect inventory field "%1$s".', $field)); 2004 } 2005 } 2006 } 2007 2008 // Collect technical and visible names to check if they exist in hosts and templates. 2009 $host_names['host'][$host['host']] = true; 2010 $host_names['name'][$host['name']] = true; 2011 } 2012 2013 $filter = [ 2014 'host' => array_keys($host_names['host']), 2015 'name' => array_keys($host_names['name']) 2016 ]; 2017 2018 $hosts_exists = $this->get([ 2019 'output' => ['host', 'name'], 2020 'filter' => $filter, 2021 'searchByAny' => true, 2022 'nopermissions' => true 2023 ]); 2024 2025 foreach ($hosts_exists as $host_exists) { 2026 if (array_key_exists($host_exists['host'], $host_names['host'])) { 2027 self::exception(ZBX_API_ERROR_PARAMETERS, 2028 _s('Host with the same name "%1$s" already exists.', $host_exists['host']) 2029 ); 2030 } 2031 2032 if (array_key_exists($host_exists['name'], $host_names['name'])) { 2033 self::exception(ZBX_API_ERROR_PARAMETERS, 2034 _s('Host with the same visible name "%1$s" already exists.', $host_exists['name']) 2035 ); 2036 } 2037 } 2038 2039 $templates_exists = API::Template()->get([ 2040 'output' => ['host', 'name'], 2041 'filter' => $filter, 2042 'searchByAny' => true, 2043 'nopermissions' => true 2044 ]); 2045 2046 foreach ($templates_exists as $template_exists) { 2047 if (array_key_exists($template_exists['host'], $host_names['host'])) { 2048 self::exception(ZBX_API_ERROR_PARAMETERS, 2049 _s('Template with the same name "%1$s" already exists.', $template_exists['host']) 2050 ); 2051 } 2052 2053 if (array_key_exists($template_exists['name'], $host_names['name'])) { 2054 self::exception(ZBX_API_ERROR_PARAMETERS, 2055 _s('Template with the same visible name "%1$s" already exists.', $template_exists['name']) 2056 ); 2057 } 2058 } 2059 2060 $this->validateEncryption($hosts); 2061 } 2062 2063 /** 2064 * Validates the input parameters for the update() method. 2065 * 2066 * @param array $hosts hosts data array 2067 * @param array $db_hosts db hosts data array 2068 * 2069 * @throws APIException if the input is invalid. 2070 */ 2071 protected function validateUpdate(array &$hosts, array &$db_hosts = null) { 2072 $hosts = zbx_toArray($hosts); 2073 2074 if (!$hosts) { 2075 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 2076 } 2077 2078 $macro_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['hostmacroid']], 'fields' => [ 2079 'hostmacroid' => ['type' => API_ID], 2080 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('hostmacro', 'macro')], 2081 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT])], 2082 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')], 2083 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] 2084 ]]; 2085 2086 $db_hosts = $this->get([ 2087 'output' => ['hostid', 'host', 'flags', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject'], 2088 'hostids' => array_column($hosts, 'hostid'), 2089 'editable' => true, 2090 'preservekeys' => true 2091 ]); 2092 2093 // Load existing values of PSK fields of hosts independently from APP mode. 2094 $hosts_psk_fields = DB::select($this->tableName(), [ 2095 'output' => ['tls_psk_identity', 'tls_psk'], 2096 'hostids' => array_keys($db_hosts), 2097 'preservekeys' => true 2098 ]); 2099 2100 foreach ($hosts_psk_fields as $hostid => $psk_fields) { 2101 $db_hosts[$hostid] += $psk_fields; 2102 } 2103 2104 $host_db_fields = ['hostid' => null]; 2105 2106 foreach ($hosts as $index => &$host) { 2107 // Validate mandatory fields. 2108 if (!check_db_fields($host_db_fields, $host)) { 2109 self::exception(ZBX_API_ERROR_PARAMETERS, 2110 _s('Wrong fields for host "%1$s".', array_key_exists('host', $host) ? $host['host'] : '') 2111 ); 2112 } 2113 2114 // Property 'auto_compress' is not supported for hosts. 2115 if (array_key_exists('auto_compress', $host)) { 2116 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 2117 } 2118 2119 // Validate host permissions. 2120 if (!array_key_exists($host['hostid'], $db_hosts)) { 2121 self::exception(ZBX_API_ERROR_PARAMETERS, _( 2122 'No permissions to referred object or it does not exist!' 2123 )); 2124 } 2125 2126 // Validate "groups" field. 2127 if (array_key_exists('groups', $host)) { 2128 if (!is_array($host['groups']) || !$host['groups']) { 2129 self::exception(ZBX_API_ERROR_PARAMETERS, 2130 _s('Host "%1$s" cannot be without host group.', $db_hosts[$host['hostid']]['host']) 2131 ); 2132 } 2133 2134 $host['groups'] = zbx_toArray($host['groups']); 2135 2136 foreach ($host['groups'] as $group) { 2137 if (!is_array($group) || (is_array($group) && !array_key_exists('groupid', $group))) { 2138 self::exception(ZBX_API_ERROR_PARAMETERS, 2139 _s('Incorrect value for field "%1$s": %2$s.', 'groups', 2140 _s('the parameter "%1$s" is missing', 'groupid') 2141 ) 2142 ); 2143 } 2144 } 2145 } 2146 // Permissions to host groups is validated in massUpdate(). 2147 2148 if (array_key_exists('macros', $host)) { 2149 if (!CApiInputValidator::validate($macro_rules, $host['macros'], '/'.($index + 1).'/macros', $error)) { 2150 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 2151 } 2152 } 2153 } 2154 unset($host); 2155 2156 if (array_column($hosts, 'macros')) { 2157 $db_hosts = $this->getHostMacros($db_hosts); 2158 $hosts = $this->validateHostMacros($hosts, $db_hosts); 2159 } 2160 2161 $inventory_fields = zbx_objectValues(getHostInventories(), 'db_field'); 2162 2163 $valid_inventory_modes = [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC]; 2164 $inventory_mode = new CLimitedSetValidator([ 2165 'values' => $valid_inventory_modes, 2166 'messageInvalid' => _s('Incorrect value for field "%1$s": %2$s.', 'inventory_mode', 2167 _s('value must be one of %1$s', implode(', ', $valid_inventory_modes))) 2168 ]); 2169 2170 $status_validator = new CLimitedSetValidator([ 2171 'values' => [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED], 2172 'messageInvalid' => _('Incorrect status for host "%1$s".') 2173 ]); 2174 2175 $update_discovered_validator = new CUpdateDiscoveredValidator([ 2176 'allowed' => ['hostid', 'status', 'inventory', 'description'], 2177 'messageAllowedField' => _('Cannot update "%2$s" for a discovered host "%1$s".') 2178 ]); 2179 2180 $host_name_parser = new CHostNameParser(); 2181 2182 $host_names = []; 2183 2184 foreach ($hosts as &$host) { 2185 $db_host = $db_hosts[$host['hostid']]; 2186 $host_name = array_key_exists('host', $host) ? $host['host'] : $db_host['host']; 2187 2188 if (array_key_exists('status', $host)) { 2189 $status_validator->setObjectName($host_name); 2190 $this->checkValidator($host['status'], $status_validator); 2191 } 2192 2193 if (array_key_exists('inventory_mode', $host)) { 2194 $inventory_mode->setObjectName($host_name); 2195 $this->checkValidator($host['inventory_mode'], $inventory_mode); 2196 } 2197 2198 if (array_key_exists('inventory', $host) && $host['inventory']) { 2199 if (array_key_exists('inventory_mode', $host) && $host['inventory_mode'] == HOST_INVENTORY_DISABLED) { 2200 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot set inventory fields for disabled inventory.')); 2201 } 2202 2203 $fields = array_keys($host['inventory']); 2204 foreach ($fields as $field) { 2205 if (!in_array($field, $inventory_fields)) { 2206 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect inventory field "%1$s".', $field)); 2207 } 2208 } 2209 } 2210 2211 // cannot update certain fields for discovered hosts 2212 $update_discovered_validator->setObjectName($host_name); 2213 $this->checkPartialValidator($host, $update_discovered_validator, $db_host); 2214 2215 if (array_key_exists('interfaces', $host) && $host['interfaces'] !== null 2216 && !is_array($host['interfaces'])) { 2217 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 2218 } 2219 2220 if (array_key_exists('host', $host)) { 2221 if ($host_name_parser->parse($host['host']) != CParser::PARSE_SUCCESS) { 2222 self::exception(ZBX_API_ERROR_PARAMETERS, 2223 _s('Incorrect characters used for host name "%1$s".', $host['host']) 2224 ); 2225 } 2226 2227 if (array_key_exists('host', $host_names) && array_key_exists($host['host'], $host_names['host'])) { 2228 self::exception(ZBX_API_ERROR_PARAMETERS, 2229 _s('Duplicate host. Host with the same host name "%1$s" already exists in data.', $host['host']) 2230 ); 2231 } 2232 2233 $host_names['host'][$host['host']] = $host['hostid']; 2234 } 2235 2236 if (array_key_exists('name', $host)) { 2237 // if visible name is empty replace it with host name 2238 if (zbx_empty(trim($host['name']))) { 2239 if (!array_key_exists('host', $host)) { 2240 self::exception(ZBX_API_ERROR_PARAMETERS, 2241 _s('Visible name cannot be empty if host name is missing.') 2242 ); 2243 } 2244 $host['name'] = $host['host']; 2245 } 2246 2247 if (array_key_exists('name', $host_names) && array_key_exists($host['name'], $host_names['name'])) { 2248 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 2249 'Duplicate host. Host with the same visible name "%1$s" already exists in data.', $host['name']) 2250 ); 2251 } 2252 $host_names['name'][$host['name']] = $host['hostid']; 2253 } 2254 2255 if (array_key_exists('tls_connect', $host) || array_key_exists('tls_accept', $host)) { 2256 $tls_connect = array_key_exists('tls_connect', $host) ? $host['tls_connect'] : $db_host['tls_connect']; 2257 $tls_accept = array_key_exists('tls_accept', $host) ? $host['tls_accept'] : $db_host['tls_accept']; 2258 2259 // Clean PSK fields. 2260 if ($tls_connect != HOST_ENCRYPTION_PSK && !($tls_accept & HOST_ENCRYPTION_PSK)) { 2261 if (!array_key_exists('tls_psk_identity', $host)) { 2262 $host['tls_psk_identity'] = ''; 2263 } 2264 if (!array_key_exists('tls_psk', $host)) { 2265 $host['tls_psk'] = ''; 2266 } 2267 } 2268 2269 // Clean certificate fields. 2270 if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE && !($tls_accept & HOST_ENCRYPTION_CERTIFICATE)) { 2271 if (!array_key_exists('tls_issuer', $host)) { 2272 $host['tls_issuer'] = ''; 2273 } 2274 if (!array_key_exists('tls_subject', $host)) { 2275 $host['tls_subject'] = ''; 2276 } 2277 } 2278 } 2279 2280 // Validate tags. 2281 if (array_key_exists('tags', $host)) { 2282 $this->validateTags($host); 2283 } 2284 } 2285 unset($host); 2286 2287 if (array_key_exists('host', $host_names) || array_key_exists('name', $host_names)) { 2288 $filter = []; 2289 2290 if (array_key_exists('host', $host_names)) { 2291 $filter['host'] = array_keys($host_names['host']); 2292 } 2293 2294 if (array_key_exists('name', $host_names)) { 2295 $filter['name'] = array_keys($host_names['name']); 2296 } 2297 2298 $hosts_exists = $this->get([ 2299 'output' => ['hostid', 'host', 'name'], 2300 'filter' => $filter, 2301 'searchByAny' => true, 2302 'nopermissions' => true, 2303 'preservekeys' => true 2304 ]); 2305 2306 foreach ($hosts_exists as $host_exists) { 2307 if (array_key_exists('host', $host_names) && array_key_exists($host_exists['host'], $host_names['host']) 2308 && bccomp($host_exists['hostid'], $host_names['host'][$host_exists['host']]) != 0) { 2309 self::exception(ZBX_API_ERROR_PARAMETERS, 2310 _s('Host with the same name "%1$s" already exists.', $host_exists['host']) 2311 ); 2312 } 2313 2314 if (array_key_exists('name', $host_names) && array_key_exists($host_exists['name'], $host_names['name']) 2315 && bccomp($host_exists['hostid'], $host_names['name'][$host_exists['name']]) != 0) { 2316 self::exception(ZBX_API_ERROR_PARAMETERS, 2317 _s('Host with the same visible name "%1$s" already exists.', $host_exists['name']) 2318 ); 2319 } 2320 } 2321 2322 $templates_exists = API::Template()->get([ 2323 'output' => ['hostid', 'host', 'name'], 2324 'filter' => $filter, 2325 'searchByAny' => true, 2326 'nopermissions' => true, 2327 'preservekeys' => true 2328 ]); 2329 2330 foreach ($templates_exists as $template_exists) { 2331 if (array_key_exists('host', $host_names) 2332 && array_key_exists($template_exists['host'], $host_names['host']) 2333 && bccomp($template_exists['templateid'], $host_names['host'][$template_exists['host']]) != 0) { 2334 self::exception(ZBX_API_ERROR_PARAMETERS, 2335 _s('Template with the same name "%1$s" already exists.', $template_exists['host']) 2336 ); 2337 } 2338 2339 if (array_key_exists('name', $host_names) 2340 && array_key_exists($template_exists['name'], $host_names['name']) 2341 && bccomp($template_exists['templateid'], $host_names['name'][$template_exists['name']]) != 0) { 2342 self::exception(ZBX_API_ERROR_PARAMETERS, 2343 _s('Template with the same visible name "%1$s" already exists.', $template_exists['name']) 2344 ); 2345 } 2346 } 2347 } 2348 2349 $this->validateEncryption($hosts, $db_hosts); 2350 2351 return $hosts; 2352 } 2353 2354 protected function requiresPostSqlFiltering(array $options) { 2355 return ($options['severities'] !== null || $options['withProblemsSuppressed'] !== null); 2356 } 2357 2358 protected function applyPostSqlFiltering(array $hosts, array $options) { 2359 $hosts = zbx_toHash($hosts, 'hostid'); 2360 2361 if ($options['severities'] !== null || $options['withProblemsSuppressed'] !== null) { 2362 $triggers = API::Trigger()->get([ 2363 'output' => [], 2364 'selectHosts' => ['hostid'], 2365 'hostids' => zbx_objectValues($hosts, 'hostid'), 2366 'skipDependent' => true, 2367 'status' => TRIGGER_STATUS_ENABLED, 2368 'preservekeys' => true 2369 ]); 2370 2371 $problems = API::Problem()->get([ 2372 'output' => ['objectid'], 2373 'objectids' => array_keys($triggers), 2374 'source' => EVENT_SOURCE_TRIGGERS, 2375 'object' => EVENT_OBJECT_TRIGGER, 2376 'suppressed' => $options['withProblemsSuppressed'], 2377 'severities' => $options['severities'] 2378 ]); 2379 2380 if (!$problems) { 2381 return []; 2382 } 2383 2384 // Keys are the trigger ids, that have problems. 2385 $problem_triggers = array_flip(array_column($problems, 'objectid')); 2386 2387 // Hostids, with triggerids on them. 2388 $host_triggers = []; 2389 foreach ($triggers as $triggerid => $trigger) { 2390 foreach ($trigger['hosts'] as $trigger_host) { 2391 $host_triggers[$trigger_host['hostid']][$triggerid] = true; 2392 } 2393 } 2394 2395 foreach ($hosts as $key => $host) { 2396 $problems_found = false; 2397 2398 if (array_key_exists($host['hostid'], $host_triggers)) { 2399 foreach (array_keys($host_triggers[$host['hostid']]) as $host_trigger) { 2400 if (array_key_exists($host_trigger, $problem_triggers)) { 2401 $problems_found = true; 2402 break; 2403 } 2404 } 2405 } 2406 2407 if (!$problems_found) { 2408 unset($hosts[$key]); 2409 } 2410 } 2411 } 2412 2413 return $hosts; 2414 } 2415} 2416