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 host prototypes. 24 */ 25class CHostPrototype extends CHostBase { 26 27 protected $sortColumns = ['hostid', 'host', 'name', 'status', 'discover']; 28 29 /** 30 * Get host prototypes. 31 * 32 * @param array $options 33 * @param bool $options['selectMacros'] Array of macros fields to be selected or string "extend". 34 * 35 * @return array 36 */ 37 public function get(array $options) { 38 $hosts_fields = array_keys($this->getTableSchema('hosts')['fields']); 39 $output_fields = ['hostid', 'host', 'name', 'status', 'templateid', 'inventory_mode', 'discover']; 40 $link_fields = ['group_prototypeid', 'groupid', 'hostid', 'templateid']; 41 $group_fields = ['group_prototypeid', 'name', 'hostid', 'templateid']; 42 $discovery_fields = array_keys($this->getTableSchema('items')['fields']); 43 $hostmacro_fields = array_keys($this->getTableSchema('hostmacro')['fields']); 44 45 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 46 // filter 47 'hostids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 48 'discoveryids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 49 'filter' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 50 'hostid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 51 'host' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 52 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 53 'status' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])], 54 'templateid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 55 'inventory_mode' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC])] 56 ]], 57 'search' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 58 'host' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 59 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 60 ]], 61 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 62 'startSearch' => ['type' => API_FLAG, 'default' => false], 63 'excludeSearch' => ['type' => API_FLAG, 'default' => false], 64 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], 65 // output 66 'output' => ['type' => API_OUTPUT, 'in' => 'inventory_mode,'.implode(',', $output_fields), 'default' => $output_fields], 67 'countOutput' => ['type' => API_FLAG, 'default' => false], 68 'groupCount' => ['type' => API_FLAG, 'default' => false], 69 'selectGroupLinks' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $link_fields), 'default' => null], 70 'selectGroupPrototypes' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $group_fields), 'default' => null], 71 'selectDiscoveryRule' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $discovery_fields), 'default' => null], 72 'selectParentHost' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $hosts_fields), 'default' => null], 73 'selectTemplates' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', $hosts_fields), 'default' => null], 74 'selectMacros' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $hostmacro_fields), 'default' => null], 75 // sort and limit 76 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], 77 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 78 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], 79 // flags 80 'inherited' => ['type' => API_BOOLEAN, 'flags' => API_ALLOW_NULL, 'default' => null], 81 'editable' => ['type' => API_BOOLEAN, 'default' => false], 82 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false], 83 'nopermissions' => ['type' => API_BOOLEAN, 'default' => false] // TODO: This property and frontend usage SHOULD BE removed. 84 ]]; 85 if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { 86 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 87 } 88 89 $options['filter']['flags'] = ZBX_FLAG_DISCOVERY_PROTOTYPE; 90 91 if ($options['output'] === API_OUTPUT_EXTEND) { 92 $options['output'] = $output_fields; 93 } 94 95 // build and execute query 96 $sql = $this->createSelectQuery($this->tableName(), $options); 97 $res = DBselect($sql, $options['limit']); 98 99 // fetch results 100 $result = []; 101 while ($row = DBfetch($res)) { 102 // a count query, return a single result 103 if ($options['countOutput']) { 104 if ($options['groupCount']) { 105 $result[] = $row; 106 } 107 else { 108 $result = $row['rowscount']; 109 } 110 } 111 // a normal select query 112 else { 113 $result[$row[$this->pk()]] = $row; 114 } 115 } 116 117 if ($options['countOutput']) { 118 return $result; 119 } 120 121 if ($result) { 122 $result = $this->addRelatedObjects($options, $result); 123 $result = $this->unsetExtraFields($result, ['triggerid'], $options['output']); 124 } 125 126 if (!$options['preservekeys']) { 127 $result = zbx_cleanHashes($result); 128 } 129 130 return $result; 131 } 132 133 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 134 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 135 136 if (!$options['countOutput'] && $this->outputIsRequested('inventory_mode', $options['output'])) { 137 $sqlParts['select']['inventory_mode'] = 138 dbConditionCoalesce('hinv.inventory_mode', HOST_INVENTORY_DISABLED, 'inventory_mode'); 139 } 140 141 if ((!$options['countOutput'] && $this->outputIsRequested('inventory_mode', $options['output'])) 142 || ($options['filter'] && array_key_exists('inventory_mode', $options['filter']))) { 143 $sqlParts['left_join'][] = ['alias' => 'hinv', 'table' => 'host_inventory', 'using' => 'hostid']; 144 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 145 } 146 147 return $sqlParts; 148 } 149 150 151 /** 152 * Check for duplicated names. 153 * 154 * @param string $field_name 155 * @param array $names_by_ruleid 156 * 157 * @throws APIException if host prototype with same name already exists. 158 */ 159 private function checkDuplicates($field_name, array $names_by_ruleid) { 160 $sql_where = []; 161 foreach ($names_by_ruleid as $ruleid => $names) { 162 $sql_where[] = '(i.itemid='.$ruleid.' AND '.dbConditionString('h.'.$field_name, $names).')'; 163 } 164 165 $db_host_prototypes = DBfetchArray(DBselect( 166 'SELECT i.name AS rule,h.'.$field_name. 167 ' FROM items i,host_discovery hd,hosts h'. 168 ' WHERE i.itemid=hd.parent_itemid'. 169 ' AND hd.hostid=h.hostid'. 170 ' AND ('.implode(' OR ', $sql_where).')', 171 1 172 )); 173 174 if ($db_host_prototypes) { 175 $error = ($field_name === 'host') 176 ? _('Host prototype with host name "%1$s" already exists in discovery rule "%2$s".') 177 : _('Host prototype with visible name "%1$s" already exists in discovery rule "%2$s".'); 178 179 self::exception(ZBX_API_ERROR_PARAMETERS, 180 sprintf($error, $db_host_prototypes[0][$field_name], $db_host_prototypes[0]['rule']) 181 ); 182 } 183 } 184 185 /** 186 * Validates the input parameters for the create() method. 187 * 188 * @throws APIException if the input is invalid. 189 * 190 * @param array $host_prototypes 191 */ 192 protected function validateCreate(array &$host_prototypes) { 193 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['ruleid', 'host'], ['ruleid', 'name']], 'fields' => [ 194 'ruleid' => ['type' => API_ID, 'flags' => API_REQUIRED], 195 'host' => ['type' => API_H_NAME, 'flags' => API_REQUIRED | API_REQUIRED_LLD_MACRO, 'length' => DB::getFieldLength('hosts', 'host')], 196 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('hosts', 'name'), 'default_source' => 'host'], 197 'status' => ['type' => API_INT32, 'in' => implode(',', [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])], 198 'discover' => ['type' => API_INT32, 'in' => implode(',', [HOST_DISCOVER, HOST_NO_DISCOVER])], 199 'groupLinks' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['groupid']], 'fields' => [ 200 'groupid' => ['type' => API_ID, 'flags' => API_REQUIRED] 201 ]], 202 'groupPrototypes' => ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 203 'name' => ['type' => API_HG_NAME, 'flags' => API_REQUIRED | API_REQUIRED_LLD_MACRO, 'length' => DB::getFieldLength('hstgrp', 'name')] 204 ]], 205 'inventory_mode' => ['type' => API_INT32, 'in' => implode(',', [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC])], 206 'templates' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 207 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] 208 ]], 209 'macros' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 210 'macro' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY], 211 'value' => ['type' => API_STRING_UTF8, 'flag' => API_REQUIRED | API_NOT_EMPTY], 212 'type' => ['type' => API_INT32, 'flag' => API_REQUIRED, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET])], 213 'description' => ['type' => API_STRING_UTF8] 214 ]] 215 ]]; 216 if (!CApiInputValidator::validate($api_input_rules, $host_prototypes, '/', $error)) { 217 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 218 } 219 220 $hosts_by_ruleid = []; 221 $names_by_ruleid = []; 222 $groupids = []; 223 224 foreach ($host_prototypes as $host_prototype) { 225 // Collect host group ID links for latter validation. 226 foreach ($host_prototype['groupLinks'] as $group_prototype) { 227 $groupids[$group_prototype['groupid']] = true; 228 } 229 230 $hosts_by_ruleid[$host_prototype['ruleid']][] = $host_prototype['host']; 231 $names_by_ruleid[$host_prototype['ruleid']][] = $host_prototype['name']; 232 } 233 234 $ruleids = array_unique(zbx_objectValues($host_prototypes, 'ruleid')); 235 $groupids = array_keys($groupids); 236 237 $this->checkDiscoveryRulePermissions($ruleids); 238 $this->checkHostGroupsPermissions($groupids); 239 240 // Check if the host is discovered. 241 $db_discovered_hosts = DBfetchArray(DBselect( 242 'SELECT h.host'. 243 ' FROM items i,hosts h'. 244 ' WHERE i.hostid=h.hostid'. 245 ' AND '.dbConditionInt('i.itemid', $ruleids). 246 ' AND h.flags='.ZBX_FLAG_DISCOVERY_CREATED, 247 1 248 )); 249 250 if ($db_discovered_hosts) { 251 self::exception(ZBX_API_ERROR_PARAMETERS, 252 _s('Cannot create a host prototype on a discovered host "%1$s".', $db_discovered_hosts[0]['host']) 253 ); 254 } 255 256 $this->checkDuplicates('host', $hosts_by_ruleid); 257 $this->checkDuplicates('name', $names_by_ruleid); 258 } 259 260 /** 261 * Creates the given host prototypes. 262 * 263 * @param array $host_prototypes 264 * 265 * @return array 266 */ 267 public function create(array $host_prototypes) { 268 // 'templateid' validation happens during linkage. 269 $this->validateCreate($host_prototypes); 270 271 // Merge groups into group prototypes. 272 foreach ($host_prototypes as &$host_prototype) { 273 $host_prototype['groupPrototypes'] = array_merge( 274 array_key_exists('groupPrototypes', $host_prototype) ? $host_prototype['groupPrototypes'] : [], 275 $host_prototype['groupLinks'] 276 ); 277 unset($host_prototype['groupLinks']); 278 } 279 unset($host_prototype); 280 281 $host_prototypes = $this->createReal($host_prototypes); 282 $this->createMacros(array_column($host_prototypes, 'macros', 'hostid')); 283 $this->inherit($host_prototypes); 284 285 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_HOST_PROTOTYPE, $host_prototypes); 286 287 return ['hostids' => zbx_objectValues($host_prototypes, 'hostid')]; 288 } 289 290 /** 291 * Creates the host prototypes and inherits them to linked hosts and templates. 292 * 293 * @param array $hostPrototypes 294 * 295 * @return array an array of host prototypes with host IDs 296 */ 297 protected function createReal(array $hostPrototypes) { 298 foreach ($hostPrototypes as &$hostPrototype) { 299 $hostPrototype['flags'] = ZBX_FLAG_DISCOVERY_PROTOTYPE; 300 } 301 unset($hostPrototype); 302 303 // save the host prototypes 304 $hostPrototypeIds = DB::insert($this->tableName(), $hostPrototypes); 305 306 $groupPrototypes = []; 307 $hostPrototypeDiscoveryRules = []; 308 $hostPrototypeInventory = []; 309 foreach ($hostPrototypes as $key => $hostPrototype) { 310 $hostPrototypes[$key]['hostid'] = $hostPrototype['hostid'] = $hostPrototypeIds[$key]; 311 312 // save group prototypes 313 foreach ($hostPrototype['groupPrototypes'] as $groupPrototype) { 314 $groupPrototype['hostid'] = $hostPrototype['hostid']; 315 $groupPrototypes[] = $groupPrototype; 316 } 317 318 // discovery rules 319 $hostPrototypeDiscoveryRules[] = [ 320 'hostid' => $hostPrototype['hostid'], 321 'parent_itemid' => $hostPrototype['ruleid'] 322 ]; 323 324 // inventory 325 if (array_key_exists('inventory_mode', $hostPrototype) 326 && $hostPrototype['inventory_mode'] != HOST_INVENTORY_DISABLED) { 327 $hostPrototypeInventory[] = [ 328 'hostid' => $hostPrototype['hostid'], 329 'inventory_mode' => $hostPrototype['inventory_mode'] 330 ]; 331 } 332 } 333 334 // save group prototypes 335 $groupPrototypes = DB::save('group_prototype', $groupPrototypes); 336 $i = 0; 337 foreach ($hostPrototypes as &$hostPrototype) { 338 foreach ($hostPrototype['groupPrototypes'] as &$groupPrototype) { 339 $groupPrototype['group_prototypeid'] = $groupPrototypes[$i]['group_prototypeid']; 340 $i++; 341 } 342 unset($groupPrototype); 343 } 344 unset($hostPrototype); 345 346 // link host prototypes to discovery rules 347 DB::insert('host_discovery', $hostPrototypeDiscoveryRules, false); 348 349 // save inventory 350 DB::insertBatch('host_inventory', $hostPrototypeInventory, false); 351 352 // link templates 353 foreach ($hostPrototypes as $hostPrototype) { 354 if (isset($hostPrototype['templates']) && $hostPrototype['templates']) { 355 $this->link(zbx_objectValues($hostPrototype['templates'], 'templateid'), [$hostPrototype['hostid']]); 356 } 357 } 358 359 return $hostPrototypes; 360 } 361 362 /** 363 * Validates the input parameters for the update() method. 364 * 365 * @throws APIException if the input is invalid. 366 * 367 * @param array $host_prototypes 368 * @param array $db_host_prototypes 369 */ 370 protected function validateUpdate(array &$host_prototypes, array &$db_host_prototypes = null) { 371 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['hostid']], 'fields' => [ 372 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED], 373 'host' => ['type' => API_H_NAME, 'flags' => API_REQUIRED_LLD_MACRO, 'length' => DB::getFieldLength('hosts', 'host')], 374 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('hosts', 'name')], 375 'status' => ['type' => API_INT32, 'in' => implode(',', [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])], 376 'discover' => ['type' => API_INT32, 'in' => implode(',', [ZBX_PROTOTYPE_DISCOVER, ZBX_PROTOTYPE_NO_DISCOVER])], 377 'groupLinks' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['group_prototypeid'], ['groupid']], 'fields' => [ 378 'group_prototypeid' => ['type' => API_ID], 379 'groupid' => ['type' => API_ID] 380 ]], 381 'groupPrototypes' => ['type' => API_OBJECTS, 'uniq' => [['group_prototypeid'], ['name']], 'fields' => [ 382 'group_prototypeid' => ['type' => API_ID], 383 'name' => ['type' => API_HG_NAME, 'flags' => API_REQUIRED_LLD_MACRO, 'length' => DB::getFieldLength('hstgrp', 'name')] 384 ]], 385 'inventory_mode' => ['type' => API_INT32, 'in' => implode(',', [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC])], 386 'templates' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 387 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] 388 ]], 389 'macros' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 390 'hostmacroid' => ['type' => API_ID], 391 'macro' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY], 392 'value' => ['type' => API_STRING_UTF8], 393 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET])], 394 'description' => ['type' => API_STRING_UTF8] 395 ]] 396 ]]; 397 if (!CApiInputValidator::validate($api_input_rules, $host_prototypes, '/', $error)) { 398 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 399 } 400 401 $db_host_prototypes = $this->get([ 402 'output' => ['hostid', 'host', 'name', 'status'], 403 'selectDiscoveryRule' => ['itemid'], 404 'selectGroupLinks' => ['group_prototypeid', 'groupid'], 405 'selectGroupPrototypes' => ['group_prototypeid', 'name'], 406 'hostids' => zbx_objectValues($host_prototypes, 'hostid'), 407 'editable' => true, 408 'preservekeys' => true 409 ]); 410 411 $hosts_by_ruleid = []; 412 $names_by_ruleid = []; 413 414 foreach ($host_prototypes as &$host_prototype) { 415 // Check if this host prototype exists. 416 if (!array_key_exists($host_prototype['hostid'], $db_host_prototypes)) { 417 self::exception(ZBX_API_ERROR_PERMISSIONS, 418 _('No permissions to referred object or it does not exist!') 419 ); 420 } 421 422 $db_host_prototype = $db_host_prototypes[$host_prototype['hostid']]; 423 $host_prototype['ruleid'] = $db_host_prototype['discoveryRule']['itemid']; 424 425 if (array_key_exists('host', $host_prototype) && $host_prototype['host'] !== $db_host_prototype['host']) { 426 $hosts_by_ruleid[$host_prototype['ruleid']][] = $host_prototype['host']; 427 } 428 429 if (array_key_exists('name', $host_prototype) && $host_prototype['name'] !== $db_host_prototype['name']) { 430 $names_by_ruleid[$host_prototype['ruleid']][] = $host_prototype['name']; 431 } 432 } 433 unset($host_prototype); 434 435 $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['ruleid', 'host'], ['ruleid', 'name']]]; 436 if (!CApiInputValidator::validateUniqueness($api_input_rules, $host_prototypes, '/', $error)) { 437 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 438 } 439 440 $groupids = []; 441 $db_groupids = []; 442 443 foreach ($host_prototypes as $host_prototype) { 444 $db_host_prototype = $db_host_prototypes[$host_prototype['hostid']]; 445 446 foreach ($db_host_prototype['groupLinks'] as $db_group_link) { 447 $db_groupids[$db_group_link['groupid']] = true; 448 } 449 450 $db_group_links = zbx_toHash($db_host_prototype['groupLinks'], 'group_prototypeid'); 451 $db_group_prototypes = zbx_toHash($db_host_prototype['groupPrototypes'], 'group_prototypeid'); 452 453 // Validate 'group_prototypeid' in 'groupLinks' property. 454 if (array_key_exists('groupLinks', $host_prototype)) { 455 foreach ($host_prototype['groupLinks'] as $group_link) { 456 if (!$group_link) { 457 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 458 } 459 460 // Don't allow invalid 'group_prototypeid' parameters which do not belong to this 'hostid'. 461 if (array_key_exists('group_prototypeid', $group_link) 462 && !array_key_exists($group_link['group_prototypeid'], $db_group_links)) { 463 self::exception(ZBX_API_ERROR_PERMISSIONS, 464 _('No permissions to referred object or it does not exist!') 465 ); 466 } 467 468 if (array_key_exists('groupid', $group_link)) { 469 $groupids[$group_link['groupid']] = true; 470 } 471 } 472 } 473 474 // Validate 'group_prototypeid' in 'groupPrototypes' property. 475 if (array_key_exists('groupPrototypes', $host_prototype)) { 476 foreach ($host_prototype['groupPrototypes'] as $group_prototype) { 477 if (!$group_prototype) { 478 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 479 } 480 481 // Don't allow invalid 'group_prototypeid' parameters which do not belong to this 'hostid'. 482 if (array_key_exists('group_prototypeid', $group_prototype) 483 && !array_key_exists($group_prototype['group_prototypeid'], $db_group_prototypes)) { 484 self::exception(ZBX_API_ERROR_PERMISSIONS, 485 _('No permissions to referred object or it does not exist!') 486 ); 487 } 488 } 489 } 490 } 491 492 // Collect only new given groupids for validation. 493 $groupids = array_diff_key($groupids, $db_groupids); 494 495 if ($groupids) { 496 $this->checkHostGroupsPermissions(array_keys($groupids)); 497 } 498 499 $host_prototypes = $this->extendObjectsByKey($host_prototypes, $db_host_prototypes, 'hostid', 500 ['host', 'name', 'groupLinks', 'groupPrototypes'] 501 ); 502 503 if ($hosts_by_ruleid) { 504 $this->checkDuplicates('host', $hosts_by_ruleid); 505 } 506 if ($names_by_ruleid) { 507 $this->checkDuplicates('name', $names_by_ruleid); 508 } 509 } 510 511 /** 512 * Updates the given host prototypes. 513 * 514 * @param array $host_prototypes 515 * 516 * @return array 517 */ 518 public function update(array $host_prototypes) { 519 $this->validateUpdate($host_prototypes, $db_host_prototypes); 520 521 // merge group links into group prototypes 522 foreach ($host_prototypes as &$host_prototype) { 523 $host_prototype['groupPrototypes'] = 524 array_merge($host_prototype['groupPrototypes'], $host_prototype['groupLinks']); 525 unset($host_prototype['groupLinks']); 526 } 527 unset($host_prototype); 528 529 $macros = array_column($host_prototypes, 'macros', 'hostid'); 530 531 if ($macros) { 532 $this->updateMacros($macros); 533 } 534 535 $host_prototypes = $this->updateReal($host_prototypes); 536 $this->inherit($host_prototypes); 537 538 foreach ($db_host_prototypes as &$db_host_prototype) { 539 unset($db_host_prototype['discoveryRule'], $db_host_prototype['groupLinks'], 540 $db_host_prototype['groupPrototypes'] 541 ); 542 } 543 unset($db_host_prototype); 544 545 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_HOST_PROTOTYPE, $host_prototypes, $db_host_prototypes); 546 547 return ['hostids' => zbx_objectValues($host_prototypes, 'hostid')]; 548 } 549 550 /** 551 * Updates the host prototypes and propagates the changes to linked hosts and templates. 552 * 553 * @param array $host_prototypes 554 * 555 * @return array 556 */ 557 protected function updateReal(array $host_prototypes) { 558 // save the host prototypes 559 foreach ($host_prototypes as $host_prototype) { 560 DB::updateByPk($this->tableName(), $host_prototype['hostid'], $host_prototype); 561 } 562 563 $ex_host_prototypes = $this->get([ 564 'output' => ['hostid', 'inventory_mode'], 565 'selectGroupLinks' => API_OUTPUT_EXTEND, 566 'selectGroupPrototypes' => API_OUTPUT_EXTEND, 567 'selectTemplates' => ['templateid'], 568 'hostids' => zbx_objectValues($host_prototypes, 'hostid'), 569 'preservekeys' => true 570 ]); 571 572 // update related objects 573 $inventory_create = []; 574 $inventory_deleteids = []; 575 foreach ($host_prototypes as $key => $host_prototype) { 576 $ex_host_prototype = $ex_host_prototypes[$host_prototype['hostid']]; 577 578 // group prototypes 579 if (isset($host_prototype['groupPrototypes'])) { 580 foreach ($host_prototype['groupPrototypes'] as &$group_prototype) { 581 $group_prototype['hostid'] = $host_prototype['hostid']; 582 } 583 unset($group_prototype); 584 585 // save group prototypes 586 $ex_group_prototypes = zbx_toHash( 587 array_merge($ex_host_prototype['groupLinks'], $ex_host_prototype['groupPrototypes']), 588 'group_prototypeid' 589 ); 590 $modified_group_prototypes = []; 591 foreach ($host_prototype['groupPrototypes'] as $group_prototype) { 592 if (isset($group_prototype['group_prototypeid'])) { 593 unset($ex_group_prototypes[$group_prototype['group_prototypeid']]); 594 } 595 596 $modified_group_prototypes[] = $group_prototype; 597 } 598 if ($ex_group_prototypes) { 599 $this->deleteGroupPrototypes(array_keys($ex_group_prototypes)); 600 } 601 $host_prototypes[$key]['groupPrototypes'] = DB::save('group_prototype', $modified_group_prototypes); 602 } 603 604 // templates 605 if (isset($host_prototype['templates'])) { 606 $existing_templateids = zbx_objectValues($ex_host_prototype['templates'], 'templateid'); 607 $new_templateids = zbx_objectValues($host_prototype['templates'], 'templateid'); 608 $this->unlink(array_diff($existing_templateids, $new_templateids), [$host_prototype['hostid']]); 609 $this->link(array_diff($new_templateids, $existing_templateids), [$host_prototype['hostid']]); 610 } 611 612 // inventory 613 if (array_key_exists('inventory_mode', $host_prototype)) { 614 if ($host_prototype['inventory_mode'] == HOST_INVENTORY_DISABLED) { 615 $inventory_deleteids[] = $host_prototype['hostid']; 616 } 617 else { 618 $inventory = ['inventory_mode' => $host_prototype['inventory_mode']]; 619 620 if ($ex_host_prototype['inventory_mode'] != HOST_INVENTORY_DISABLED) { 621 if ($host_prototype['inventory_mode'] != $ex_host_prototype['inventory_mode']) { 622 DB::update('host_inventory', [ 623 'values' => $inventory, 624 'where' => ['hostid' => $host_prototype['hostid']] 625 ]); 626 } 627 } 628 else { 629 $inventory_create[] = $inventory + ['hostid' => $host_prototype['hostid']]; 630 } 631 } 632 } 633 } 634 635 // save inventory 636 DB::insertBatch('host_inventory', $inventory_create, false); 637 DB::delete('host_inventory', ['hostid' => $inventory_deleteids]); 638 639 return $host_prototypes; 640 } 641 642 /** 643 * Updates the children of the host prototypes on the given hosts and propagates the inheritance to the child hosts. 644 * 645 * @param array $hostPrototypes array of host prototypes to inherit 646 * @param array $hostids array of hosts to inherit to; if set to null, the children will be updated on all 647 * child hosts 648 * 649 * @return bool 650 */ 651 protected function inherit(array $hostPrototypes, array $hostids = null) { 652 if (empty($hostPrototypes)) { 653 return true; 654 } 655 656 // prepare the child host prototypes 657 $newHostPrototypes = $this->prepareInheritedObjects($hostPrototypes, $hostids); 658 if (!$newHostPrototypes) { 659 return true; 660 } 661 662 $insertHostPrototypes = []; 663 $updateHostPrototypes = []; 664 foreach ($newHostPrototypes as $newHostPrototype) { 665 if (isset($newHostPrototype['hostid'])) { 666 $updateHostPrototypes[] = $newHostPrototype; 667 } 668 else { 669 $insertHostPrototypes[] = $newHostPrototype; 670 } 671 } 672 673 // save the new host prototypes 674 if (!zbx_empty($insertHostPrototypes)) { 675 $insertHostPrototypes = $this->createReal($insertHostPrototypes); 676 $this->createMacros(array_column($insertHostPrototypes, 'macros', 'hostid')); 677 } 678 679 if (!zbx_empty($updateHostPrototypes)) { 680 $updateHostPrototypes = $this->updateReal($updateHostPrototypes); 681 $macros = array_column($updateHostPrototypes, 'macros', 'hostid'); 682 683 if ($macros) { 684 $this->updateMacros($macros); 685 } 686 } 687 688 $host_prototypes = array_merge($updateHostPrototypes, $insertHostPrototypes); 689 690 if ($host_prototypes) { 691 $sql = 'SELECT hd.hostid'. 692 ' FROM host_discovery hd,items i,hosts h'. 693 ' WHERE hd.parent_itemid=i.itemid'. 694 ' AND i.hostid=h.hostid'. 695 ' AND h.status='.HOST_STATUS_TEMPLATE. 696 ' AND '.dbConditionInt('hd.hostid', zbx_objectValues($host_prototypes, 'hostid')); 697 $valid_prototypes = DBfetchArrayAssoc(DBselect($sql), 'hostid'); 698 699 foreach ($host_prototypes as $key => $host_prototype) { 700 if (!array_key_exists($host_prototype['hostid'], $valid_prototypes)) { 701 unset($host_prototypes[$key]); 702 } 703 } 704 } 705 706 // propagate the inheritance to the children 707 return $this->inherit($host_prototypes); 708 } 709 710 711 /** 712 * Prepares and returns an array of child host prototypes, inherited from host prototypes $hostPrototypes 713 * on the given hosts. 714 * 715 * Each host prototype must have the "ruleid" parameter set. 716 * 717 * @param array $host_prototypes 718 * @param array $hostIds 719 * 720 * @return array an array of unsaved child host prototypes 721 */ 722 protected function prepareInheritedObjects(array $host_prototypes, array $hostIds = null) { 723 // fetch the related discovery rules with their hosts 724 $discoveryRules = API::DiscoveryRule()->get([ 725 'output' => ['itemid', 'hostid'], 726 'selectHosts' => ['hostid'], 727 'itemids' => array_column($host_prototypes, 'ruleid'), 728 'templated' => true, 729 'nopermissions' => true, 730 'preservekeys' => true 731 ]); 732 733 // Remove host prototypes which don't belong to templates, so they cannot be inherited. 734 $host_prototypes = array_filter($host_prototypes, function ($host_prototype) use ($discoveryRules) { 735 return array_key_exists($host_prototype['ruleid'], $discoveryRules); 736 }); 737 738 // fetch all child hosts to inherit to 739 // do not inherit host prototypes on discovered hosts 740 $chdHosts = API::Host()->get([ 741 'output' => ['hostid', 'host', 'status'], 742 'selectParentTemplates' => ['templateid'], 743 'templateids' => zbx_objectValues($discoveryRules, 'hostid'), 744 'hostids' => $hostIds, 745 'nopermissions' => true, 746 'templated_hosts' => true, 747 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL] 748 ]); 749 if (empty($chdHosts)) { 750 return []; 751 } 752 753 // fetch the child discovery rules 754 $childDiscoveryRules = API::DiscoveryRule()->get([ 755 'output' => ['itemid', 'templateid', 'hostid'], 756 'filter' => [ 757 'templateid' => array_keys($discoveryRules) 758 ], 759 'nopermissions' => true, 760 'preservekeys' => true 761 ]); 762 763 // fetch child host prototypes and group them by discovery rule 764 $childHostPrototypes = API::HostPrototype()->get([ 765 'output' => ['hostid', 'host', 'templateid'], 766 'selectGroupLinks' => API_OUTPUT_EXTEND, 767 'selectGroupPrototypes' => API_OUTPUT_EXTEND, 768 'selectDiscoveryRule' => ['itemid'], 769 'discoveryids' => zbx_objectValues($childDiscoveryRules, 'itemid'), 770 'nopermissions' => true 771 ]); 772 foreach ($childDiscoveryRules as &$childDiscoveryRule) { 773 $childDiscoveryRule['hostPrototypes'] = []; 774 } 775 unset($childDiscoveryRule); 776 foreach ($childHostPrototypes as $childHostPrototype) { 777 $discoveryRuleId = $childHostPrototype['discoveryRule']['itemid']; 778 unset($childHostPrototype['discoveryRule']); 779 780 $childDiscoveryRules[$discoveryRuleId]['hostPrototypes'][] = $childHostPrototype; 781 } 782 783 // match each discovery that the parent host prototypes belong to to the child discovery rule for each host 784 $discoveryRuleChildren = []; 785 foreach ($childDiscoveryRules as $childRule) { 786 $discoveryRuleChildren[$childRule['templateid']][$childRule['hostid']] = $childRule['itemid']; 787 } 788 789 $newHostPrototypes = []; 790 foreach ($chdHosts as $host) { 791 $hostId = $host['hostid']; 792 793 // skip items not from parent templates of current host 794 $templateIds = zbx_toHash($host['parentTemplates'], 'templateid'); 795 $parentHostPrototypes = []; 796 foreach ($host_prototypes as $inum => $parentHostPrototype) { 797 $parentTemplateId = $discoveryRules[$parentHostPrototype['ruleid']]['hostid']; 798 799 if (isset($templateIds[$parentTemplateId])) { 800 $parentHostPrototypes[$inum] = $parentHostPrototype; 801 } 802 } 803 804 foreach ($parentHostPrototypes as $parentHostPrototype) { 805 $childDiscoveryRuleId = $discoveryRuleChildren[$parentHostPrototype['ruleid']][$hostId]; 806 $exHostPrototype = null; 807 808 // check if the child discovery rule already has host prototypes 809 $exHostPrototypes = $childDiscoveryRules[$childDiscoveryRuleId]['hostPrototypes']; 810 if ($exHostPrototypes) { 811 $exHostPrototypesHosts = zbx_toHash($exHostPrototypes, 'host'); 812 $exHostPrototypesTemplateIds = zbx_toHash($exHostPrototypes, 'templateid'); 813 814 // look for an already created inherited host prototype 815 // if one exists - update it 816 if (isset($exHostPrototypesTemplateIds[$parentHostPrototype['hostid']])) { 817 $exHostPrototype = $exHostPrototypesTemplateIds[$parentHostPrototype['hostid']]; 818 819 // check if there's a host prototype on the target host with the same host name but from a different template 820 // or no template 821 if (isset($exHostPrototypesHosts[$parentHostPrototype['host']]) 822 && !idcmp($exHostPrototypesHosts[$parentHostPrototype['host']]['templateid'], $parentHostPrototype['hostid'])) { 823 824 $discoveryRule = DBfetch(DBselect('SELECT i.name FROM items i WHERE i.itemid='.zbx_dbstr($exHostPrototype['discoveryRule']['itemid']))); 825 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host prototype "%1$s" already exists on "%2$s".', $parentHostPrototype['host'], $discoveryRule['name'])); 826 } 827 } 828 829 // look for a host prototype with the same host name 830 // if one exists - convert it to an inherited host prototype 831 if (isset($exHostPrototypesHosts[$parentHostPrototype['host']])) { 832 $exHostPrototype = $exHostPrototypesHosts[$parentHostPrototype['host']]; 833 834 // check that this host prototype is not inherited from a different template 835 if ($exHostPrototype['templateid'] > 0 && !idcmp($exHostPrototype['templateid'], $parentHostPrototype['hostid'])) { 836 $discoveryRule = DBfetch(DBselect('SELECT i.name FROM items i WHERE i.itemid='.zbx_dbstr($exHostPrototype['discoveryRule']['itemid']))); 837 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host prototype "%1$s" already exists on "%2$s", inherited from another template.', $parentHostPrototype['host'], $discoveryRule['name'])); 838 } 839 } 840 } 841 842 // copy host prototype 843 $newHostPrototype = $parentHostPrototype; 844 $newHostPrototype['ruleid'] = $discoveryRuleChildren[$parentHostPrototype['ruleid']][$hostId]; 845 $newHostPrototype['templateid'] = $parentHostPrototype['hostid']; 846 847 // update an existing inherited host prototype 848 if ($exHostPrototype) { 849 // look for existing group prototypes to update 850 $exGroupPrototypesByTemplateId = zbx_toHash($exHostPrototype['groupPrototypes'], 'templateid'); 851 $exGroupPrototypesByName = zbx_toHash($exHostPrototype['groupPrototypes'], 'name'); 852 $exGroupPrototypesByGroupId = zbx_toHash($exHostPrototype['groupLinks'], 'groupid'); 853 854 // look for a group prototype that can be updated 855 foreach ($newHostPrototype['groupPrototypes'] as &$groupPrototype) { 856 // updated an inherited item prototype by templateid 857 if (isset($exGroupPrototypesByTemplateId[$groupPrototype['group_prototypeid']])) { 858 $groupPrototype['group_prototypeid'] = $exGroupPrototypesByTemplateId[$groupPrototype['group_prototypeid']]['group_prototypeid']; 859 } 860 // updated an inherited item prototype by name 861 elseif (isset($groupPrototype['name']) && !zbx_empty($groupPrototype['name']) 862 && isset($exGroupPrototypesByName[$groupPrototype['name']])) { 863 864 $groupPrototype['templateid'] = $groupPrototype['group_prototypeid']; 865 $groupPrototype['group_prototypeid'] = $exGroupPrototypesByName[$groupPrototype['name']]['group_prototypeid']; 866 } 867 // updated an inherited item prototype by group ID 868 elseif (isset($groupPrototype['groupid']) && $groupPrototype['groupid'] 869 && isset($exGroupPrototypesByGroupId[$groupPrototype['groupid']])) { 870 871 $groupPrototype['templateid'] = $groupPrototype['group_prototypeid']; 872 $groupPrototype['group_prototypeid'] = $exGroupPrototypesByGroupId[$groupPrototype['groupid']]['group_prototypeid']; 873 } 874 // create a new child group prototype 875 else { 876 $groupPrototype['templateid'] = $groupPrototype['group_prototypeid']; 877 unset($groupPrototype['group_prototypeid']); 878 } 879 880 unset($groupPrototype['hostid']); 881 } 882 unset($groupPrototype); 883 884 $newHostPrototype['hostid'] = $exHostPrototype['hostid']; 885 } 886 // create a new inherited host prototype 887 else { 888 foreach ($newHostPrototype['groupPrototypes'] as &$groupPrototype) { 889 $groupPrototype['templateid'] = $groupPrototype['group_prototypeid']; 890 unset($groupPrototype['group_prototypeid'], $groupPrototype['hostid']); 891 } 892 unset($groupPrototype); 893 894 unset($newHostPrototype['hostid']); 895 } 896 $newHostPrototypes[] = $newHostPrototype; 897 } 898 } 899 900 return $newHostPrototypes; 901 } 902 903 /** 904 * Inherits all host prototypes from the templates given in "templateids" to hosts or templates given in "hostids". 905 * 906 * @param array $data 907 * 908 * @return bool 909 */ 910 public function syncTemplates(array $data) { 911 $data['templateids'] = zbx_toArray($data['templateids']); 912 $data['hostids'] = zbx_toArray($data['hostids']); 913 914 $discoveryRules = API::DiscoveryRule()->get([ 915 'output' => ['itemid'], 916 'hostids' => $data['templateids'] 917 ]); 918 $hostPrototypes = $this->get([ 919 'discoveryids' => zbx_objectValues($discoveryRules, 'itemid'), 920 'preservekeys' => true, 921 'output' => API_OUTPUT_EXTEND, 922 'selectGroupLinks' => API_OUTPUT_EXTEND, 923 'selectGroupPrototypes' => API_OUTPUT_EXTEND, 924 'selectMacros' => ['macro', 'type', 'value', 'description'], 925 'selectTemplates' => ['templateid'], 926 'selectDiscoveryRule' => ['itemid'] 927 ]); 928 929 foreach ($hostPrototypes as &$hostPrototype) { 930 // merge group links into group prototypes 931 foreach ($hostPrototype['groupLinks'] as $group) { 932 $hostPrototype['groupPrototypes'][] = $group; 933 } 934 unset($hostPrototype['groupLinks']); 935 936 // the ID of the discovery rule must be passed in the "ruleid" parameter 937 $hostPrototype['ruleid'] = $hostPrototype['discoveryRule']['itemid']; 938 unset($hostPrototype['discoveryRule']); 939 } 940 unset($hostPrototype); 941 942 $this->inherit($hostPrototypes, $data['hostids']); 943 944 return true; 945 } 946 947 /** 948 * Validates the input parameters for the delete() method. 949 * 950 * @throws APIException if the input is invalid 951 * 952 * @param array $hostPrototypeIds 953 * @param bool $nopermissions 954 */ 955 protected function validateDelete($hostPrototypeIds, $nopermissions) { 956 if (!$hostPrototypeIds) { 957 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 958 } 959 960 if (!$nopermissions) { 961 $this->checkHostPrototypePermissions($hostPrototypeIds); 962 $this->checkNotInherited($hostPrototypeIds); 963 } 964 } 965 966 /** 967 * Delete host prototypes. 968 * 969 * @param array $hostPrototypeIds 970 * @param bool $nopermissions if set to true, permission and template checks will be skipped 971 * 972 * @return array 973 */ 974 public function delete(array $hostPrototypeIds, $nopermissions = false) { 975 $this->validateDelete($hostPrototypeIds, $nopermissions); 976 977 // include child IDs 978 $parentHostPrototypeIds = $hostPrototypeIds; 979 $childHostPrototypeIds = []; 980 do { 981 $query = DBselect('SELECT h.hostid FROM hosts h WHERE '.dbConditionInt('h.templateid', $parentHostPrototypeIds)); 982 $parentHostPrototypeIds = []; 983 while ($hostPrototype = DBfetch($query)) { 984 $parentHostPrototypeIds[] = $hostPrototype['hostid']; 985 $childHostPrototypeIds[] = $hostPrototype['hostid']; 986 } 987 } while (!empty($parentHostPrototypeIds)); 988 989 $hostPrototypeIds = array_merge($hostPrototypeIds, $childHostPrototypeIds); 990 991 // Lock host prototypes before delete to prevent server from adding new LLD hosts. 992 DBselect( 993 'SELECT NULL'. 994 ' FROM hosts h'. 995 ' WHERE '.dbConditionInt('h.hostid', $hostPrototypeIds). 996 ' FOR UPDATE' 997 ); 998 999 $deleteHostPrototypes = $this->get([ 1000 'hostids' => $hostPrototypeIds, 1001 'output' => ['host'], 1002 'selectGroupPrototypes' => ['group_prototypeid'], 1003 'selectParentHost' => ['host'], 1004 'nopermissions' => true 1005 ]); 1006 1007 // delete discovered hosts 1008 $discoveredHosts = DBfetchArray(DBselect( 1009 'SELECT hostid FROM host_discovery WHERE '.dbConditionInt('parent_hostid', $hostPrototypeIds) 1010 )); 1011 if ($discoveredHosts) { 1012 API::Host()->delete(zbx_objectValues($discoveredHosts, 'hostid'), true); 1013 } 1014 1015 // delete group prototypes and discovered groups 1016 $groupPrototypeIds = []; 1017 foreach ($deleteHostPrototypes as $groupPrototype) { 1018 foreach ($groupPrototype['groupPrototypes'] as $groupPrototype) { 1019 $groupPrototypeIds[] = $groupPrototype['group_prototypeid']; 1020 } 1021 } 1022 $this->deleteGroupPrototypes($groupPrototypeIds); 1023 1024 // delete host prototypes 1025 DB::delete($this->tableName(), ['hostid' => $hostPrototypeIds]); 1026 1027 // TODO: REMOVE info 1028 foreach ($deleteHostPrototypes as $hostProtototype) { 1029 info(_s('Deleted: Host prototype "%1$s" on "%2$s".', $hostProtototype['host'], $hostProtototype['parentHost']['host'])); 1030 } 1031 1032 return ['hostids' => $hostPrototypeIds]; 1033 } 1034 1035 protected function link(array $templateids, array $targetids) { 1036 $this->checkHostPrototypePermissions($targetids); 1037 1038 $links = parent::link($templateids, $targetids); 1039 1040 foreach ($targetids as $targetid) { 1041 $linked_templates = API::Template()->get([ 1042 'output' => [], 1043 'hostids' => [$targetid], 1044 'nopermissions' => true 1045 ]); 1046 1047 $result = DBselect( 1048 'SELECT i.key_,count(*)'. 1049 ' FROM items i'. 1050 ' WHERE '.dbConditionInt('i.hostid', array_merge($templateids, array_keys($linked_templates))). 1051 ' GROUP BY i.key_'. 1052 ' HAVING count(*)>1', 1053 1 1054 ); 1055 if ($row = DBfetch($result)) { 1056 $target_templates = API::HostPrototype()->get([ 1057 'output' => ['name'], 1058 'hostids' => [$targetid], 1059 'nopermissions' => true 1060 ]); 1061 1062 self::exception(ZBX_API_ERROR_PARAMETERS, 1063 _s('Item "%1$s" already exists on "%2$s", inherited from another template.', $row['key_'], 1064 $target_templates[0]['name'] 1065 ) 1066 ); 1067 } 1068 } 1069 1070 return $links; 1071 } 1072 1073 /** 1074 * Checks if the current user has access to the given LLD rules. 1075 * 1076 * @throws APIException if the user doesn't have write permissions for the given LLD rules 1077 * 1078 * @param array $ruleids 1079 */ 1080 protected function checkDiscoveryRulePermissions(array $ruleids) { 1081 $count = API::DiscoveryRule()->get([ 1082 'countOutput' => true, 1083 'itemids' => $ruleids, 1084 'editable' => true 1085 ]); 1086 1087 if ($count != count($ruleids)) { 1088 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 1089 } 1090 } 1091 1092 /** 1093 * Checks if the current user has access to the given host groups. 1094 * 1095 * @throws APIException if the user doesn't have write permissions for the given host groups 1096 * 1097 * @param array $groupids 1098 */ 1099 protected function checkHostGroupsPermissions(array $groupids) { 1100 $db_groups = API::HostGroup()->get([ 1101 'output' => ['name', 'flags'], 1102 'groupids' => $groupids, 1103 'editable' => true, 1104 'preservekeys' => true 1105 ]); 1106 1107 foreach ($groupids as $groupid) { 1108 if (!array_key_exists($groupid, $db_groups)) { 1109 self::exception(ZBX_API_ERROR_PERMISSIONS, 1110 _('No permissions to referred object or it does not exist!') 1111 ); 1112 } 1113 1114 $db_group = $db_groups[$groupid]; 1115 1116 // Check if group prototypes use discovered host groups. 1117 if ($db_group['flags'] == ZBX_FLAG_DISCOVERY_CREATED) { 1118 self::exception(ZBX_API_ERROR_PARAMETERS, 1119 _s('Group prototype cannot be based on a discovered host group "%1$s".', $db_group['name']) 1120 ); 1121 } 1122 } 1123 } 1124 1125 /** 1126 * Checks if the current user has access to the given host prototypes. 1127 * 1128 * @throws APIException if the user doesn't have write permissions for the host prototypes. 1129 * 1130 * @param array $hostPrototypeIds 1131 */ 1132 protected function checkHostPrototypePermissions(array $hostPrototypeIds) { 1133 if ($hostPrototypeIds) { 1134 $hostPrototypeIds = array_unique($hostPrototypeIds); 1135 1136 $count = $this->get([ 1137 'countOutput' => true, 1138 'hostids' => $hostPrototypeIds, 1139 'editable' => true 1140 ]); 1141 1142 if ($count != count($hostPrototypeIds)) { 1143 self::exception(ZBX_API_ERROR_PERMISSIONS, 1144 _('No permissions to referred object or it does not exist!') 1145 ); 1146 } 1147 } 1148 } 1149 1150 /** 1151 * Checks if the given host prototypes are not inherited from a template. 1152 * 1153 * @throws APIException if at least one host prototype is inherited 1154 * 1155 * @param array $hostPrototypeIds 1156 */ 1157 protected function checkNotInherited(array $hostPrototypeIds) { 1158 $query = DBSelect('SELECT hostid FROM hosts h WHERE h.templateid>0 AND '.dbConditionInt('h.hostid', $hostPrototypeIds), 1); 1159 1160 if ($hostPrototype = DBfetch($query)) { 1161 self::exception(ZBX_API_ERROR_PERMISSIONS, _('Cannot delete templated host prototype.')); 1162 } 1163 } 1164 1165 protected function applyQueryFilterOptions($tableName, $tableAlias, array $options, array $sqlParts) { 1166 $sqlParts = parent::applyQueryFilterOptions($tableName, $tableAlias, $options, $sqlParts); 1167 1168 // do not return host prototypes from discovered hosts 1169 $sqlParts['from'][] = 'host_discovery hd'; 1170 $sqlParts['from'][] = 'items i'; 1171 $sqlParts['from'][] = 'hosts ph'; 1172 $sqlParts['where'][] = $this->fieldId('hostid').'=hd.hostid'; 1173 $sqlParts['where'][] = 'hd.parent_itemid=i.itemid'; 1174 $sqlParts['where'][] = 'i.hostid=ph.hostid'; 1175 $sqlParts['where'][] = 'ph.flags='.ZBX_FLAG_DISCOVERY_NORMAL; 1176 1177 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 1178 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 1179 1180 $sqlParts['where'][] = 'EXISTS ('. 1181 'SELECT NULL'. 1182 ' FROM '. 1183 'host_discovery hd,items i,hosts_groups hgg'. 1184 ' JOIN rights r'. 1185 ' ON r.id=hgg.groupid'. 1186 ' AND '.dbConditionInt('r.groupid', getUserGroupsByUserId(self::$userData['userid'])). 1187 ' WHERE h.hostid=hd.hostid'. 1188 ' AND hd.parent_itemid=i.itemid'. 1189 ' AND i.hostid=hgg.hostid'. 1190 ' GROUP BY hgg.hostid'. 1191 ' HAVING MIN(r.permission)>'.PERM_DENY. 1192 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 1193 ')'; 1194 } 1195 1196 // discoveryids 1197 if ($options['discoveryids'] !== null) { 1198 $sqlParts['where'][] = dbConditionInt('hd.parent_itemid', (array) $options['discoveryids']); 1199 1200 if ($options['groupCount']) { 1201 $sqlParts['group']['hd'] = 'hd.parent_itemid'; 1202 } 1203 } 1204 1205 // inherited 1206 if ($options['inherited'] !== null) { 1207 $sqlParts['where'][] = ($options['inherited']) ? 'h.templateid IS NOT NULL' : 'h.templateid IS NULL'; 1208 } 1209 1210 if ($options['filter'] && array_key_exists('inventory_mode', $options['filter'])) { 1211 if ($options['filter']['inventory_mode'] !== null) { 1212 $inventory_mode_query = (array) $options['filter']['inventory_mode']; 1213 1214 $inventory_mode_where = []; 1215 $null_position = array_search(HOST_INVENTORY_DISABLED, $inventory_mode_query); 1216 1217 if ($null_position !== false) { 1218 unset($inventory_mode_query[$null_position]); 1219 $inventory_mode_where[] = 'hinv.inventory_mode IS NULL'; 1220 } 1221 1222 if ($null_position === false || $inventory_mode_query) { 1223 $inventory_mode_where[] = dbConditionInt('hinv.inventory_mode', $inventory_mode_query); 1224 } 1225 1226 $sqlParts['where'][] = (count($inventory_mode_where) > 1) 1227 ? '('.implode(' OR ', $inventory_mode_where).')' 1228 : $inventory_mode_where[0]; 1229 } 1230 } 1231 1232 return $sqlParts; 1233 } 1234 1235 /** 1236 * Retrieves and adds additional requested data to the result set. 1237 * 1238 * @param array $options 1239 * @param array $result 1240 * 1241 * @return array 1242 */ 1243 protected function addRelatedObjects(array $options, array $result) { 1244 $result = parent::addRelatedObjects($options, $result); 1245 1246 $hostPrototypeIds = array_keys($result); 1247 1248 // adding discovery rule 1249 if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) { 1250 $relationMap = $this->createRelationMap($result, 'hostid', 'parent_itemid', 'host_discovery'); 1251 $discoveryRules = API::DiscoveryRule()->get([ 1252 'output' => $options['selectDiscoveryRule'], 1253 'itemids' => $relationMap->getRelatedIds(), 1254 'nopermissions' => true, 1255 'preservekeys' => true 1256 ]); 1257 $result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule'); 1258 } 1259 1260 // adding group links 1261 if ($options['selectGroupLinks'] !== null && $options['selectGroupLinks'] != API_OUTPUT_COUNT) { 1262 $groupPrototypes = DBFetchArray(DBselect( 1263 'SELECT hg.group_prototypeid,hg.hostid'. 1264 ' FROM group_prototype hg'. 1265 ' WHERE '.dbConditionInt('hg.hostid', $hostPrototypeIds). 1266 ' AND hg.groupid IS NOT NULL' 1267 )); 1268 $relationMap = $this->createRelationMap($groupPrototypes, 'hostid', 'group_prototypeid'); 1269 $groupPrototypes = API::getApiService()->select('group_prototype', [ 1270 'output' => $options['selectGroupLinks'], 1271 'group_prototypeids' => $relationMap->getRelatedIds(), 1272 'preservekeys' => true 1273 ]); 1274 foreach ($groupPrototypes as &$groupPrototype) { 1275 unset($groupPrototype['name']); 1276 } 1277 unset($groupPrototype); 1278 $result = $relationMap->mapMany($result, $groupPrototypes, 'groupLinks'); 1279 } 1280 1281 // adding group prototypes 1282 if ($options['selectGroupPrototypes'] !== null && $options['selectGroupPrototypes'] != API_OUTPUT_COUNT) { 1283 $groupPrototypes = DBFetchArray(DBselect( 1284 'SELECT hg.group_prototypeid,hg.hostid'. 1285 ' FROM group_prototype hg'. 1286 ' WHERE '.dbConditionInt('hg.hostid', $hostPrototypeIds). 1287 ' AND hg.groupid IS NULL' 1288 )); 1289 $relationMap = $this->createRelationMap($groupPrototypes, 'hostid', 'group_prototypeid'); 1290 $groupPrototypes = API::getApiService()->select('group_prototype', [ 1291 'output' => $options['selectGroupPrototypes'], 1292 'group_prototypeids' => $relationMap->getRelatedIds(), 1293 'preservekeys' => true 1294 ]); 1295 foreach ($groupPrototypes as &$groupPrototype) { 1296 unset($groupPrototype['groupid']); 1297 } 1298 unset($groupPrototype); 1299 $result = $relationMap->mapMany($result, $groupPrototypes, 'groupPrototypes'); 1300 } 1301 1302 // adding host 1303 if ($options['selectParentHost'] !== null && $options['selectParentHost'] != API_OUTPUT_COUNT) { 1304 $hosts = []; 1305 $relationMap = new CRelationMap(); 1306 $dbRules = DBselect( 1307 'SELECT hd.hostid,i.hostid AS parent_hostid'. 1308 ' FROM host_discovery hd,items i'. 1309 ' WHERE '.dbConditionInt('hd.hostid', $hostPrototypeIds). 1310 ' AND hd.parent_itemid=i.itemid' 1311 ); 1312 while ($relation = DBfetch($dbRules)) { 1313 $relationMap->addRelation($relation['hostid'], $relation['parent_hostid']); 1314 } 1315 1316 $related_ids = $relationMap->getRelatedIds(); 1317 1318 if ($related_ids) { 1319 $hosts = API::Host()->get([ 1320 'output' => $options['selectParentHost'], 1321 'hostids' => $related_ids, 1322 'templated_hosts' => true, 1323 'nopermissions' => true, 1324 'preservekeys' => true 1325 ]); 1326 } 1327 1328 $result = $relationMap->mapOne($result, $hosts, 'parentHost'); 1329 } 1330 1331 // adding templates 1332 if ($options['selectTemplates'] !== null) { 1333 if ($options['selectTemplates'] != API_OUTPUT_COUNT) { 1334 $templates = []; 1335 $relationMap = $this->createRelationMap($result, 'hostid', 'templateid', 'hosts_templates'); 1336 $related_ids = $relationMap->getRelatedIds(); 1337 1338 if ($related_ids) { 1339 $templates = API::Template()->get([ 1340 'output' => $options['selectTemplates'], 1341 'templateids' => $related_ids, 1342 'preservekeys' => true 1343 ]); 1344 } 1345 1346 $result = $relationMap->mapMany($result, $templates, 'templates'); 1347 } 1348 else { 1349 $templates = API::Template()->get([ 1350 'hostids' => $hostPrototypeIds, 1351 'countOutput' => true, 1352 'groupCount' => true 1353 ]); 1354 $templates = zbx_toHash($templates, 'hostid'); 1355 foreach ($result as $hostid => $host) { 1356 $result[$hostid]['templates'] = array_key_exists($hostid, $templates) 1357 ? $templates[$hostid]['rowscount'] 1358 : '0'; 1359 } 1360 } 1361 } 1362 1363 // adding macros 1364 if ($options['selectMacros'] !== null && $options['selectMacros'] != API_OUTPUT_COUNT) { 1365 $macros = API::UserMacro()->get([ 1366 'output' => $this->outputExtend($options['selectMacros'], ['hostid', 'hostmacroid']), 1367 'hostids' => $hostPrototypeIds, 1368 'preservekeys' => true 1369 ]); 1370 1371 $relationMap = $this->createRelationMap($macros, 'hostid', 'hostmacroid'); 1372 $macros = $this->unsetExtraFields($macros, ['hostid', 'hostmacroid'], $options['selectMacros']); 1373 $result = $relationMap->mapMany($result, $macros, 'macros'); 1374 } 1375 1376 return $result; 1377 } 1378 1379 /** 1380 * Deletes the given group prototype and all discovered groups. 1381 * Deletes also group prototype children. 1382 * 1383 * @param array $groupPrototypeIds 1384 */ 1385 protected function deleteGroupPrototypes(array $groupPrototypeIds) { 1386 // Lock group prototypes before delete to prevent server from adding new LLD elements. 1387 DBselect( 1388 'SELECT NULL'. 1389 ' FROM group_prototype gp'. 1390 ' WHERE '.dbConditionInt('gp.group_prototypeid', $groupPrototypeIds). 1391 ' FOR UPDATE' 1392 ); 1393 1394 // delete child group prototypes 1395 $groupPrototypeChildren = DBfetchArray(DBselect( 1396 'SELECT gp.group_prototypeid FROM group_prototype gp WHERE '.dbConditionInt('templateid', $groupPrototypeIds) 1397 )); 1398 if ($groupPrototypeChildren) { 1399 $this->deleteGroupPrototypes(zbx_objectValues($groupPrototypeChildren, 'group_prototypeid')); 1400 } 1401 1402 // delete discovered groups 1403 $hostGroups = DBfetchArray(DBselect( 1404 'SELECT groupid FROM group_discovery WHERE '.dbConditionInt('parent_group_prototypeid', $groupPrototypeIds) 1405 )); 1406 if ($hostGroups) { 1407 API::HostGroup()->delete(zbx_objectValues($hostGroups, 'groupid'), true); 1408 } 1409 1410 // delete group prototypes 1411 DB::delete('group_prototype', ['group_prototypeid' => $groupPrototypeIds]); 1412 } 1413 1414 /** 1415 * Create host prototype macro with prototype id as key and macros array. 1416 * 1417 * @param array $host_prototype_macro Array of host prototype macros. 1418 */ 1419 protected function createMacros(array $host_prototype_macros) { 1420 $create = []; 1421 1422 foreach ($host_prototype_macros as $host_prototypeid => $macros) { 1423 foreach ($macros as $macro) { 1424 $create[] = ['hostid' => $host_prototypeid] + $macro; 1425 } 1426 } 1427 1428 if ($create) { 1429 API::UserMacro()->create($create); 1430 } 1431 } 1432 1433 /** 1434 * Update host prototype macros, key is host prototype id and value is array of arrays with macro objects. 1435 * 1436 * @param array $macros Array with macros objects. 1437 */ 1438 protected function updateMacros(array $update_macros) { 1439 $create = []; 1440 $update = []; 1441 $db_macros = API::UserMacro()->get([ 1442 'output' => ['hostid', 'macro', 'type', 'value', 'description'], 1443 'hostids' => array_keys($update_macros), 1444 'preservekeys' => true 1445 ]); 1446 $host_macros = array_fill_keys(array_column($db_macros, 'hostid'), []); 1447 1448 foreach ($db_macros as $hostmacroid => $db_macro) { 1449 $host_macros[$db_macro['hostid']][$db_macro['macro']] = $hostmacroid; 1450 } 1451 1452 foreach ($update_macros as $hostid => $macros) { 1453 foreach ($macros as $macro) { 1454 if (array_key_exists($hostid, $host_macros) 1455 && array_key_exists($macro['macro'], $host_macros[$hostid])) { 1456 $hostmacroid = $host_macros[$hostid][$macro['macro']]; 1457 $diff = array_diff($macro, $db_macros[$hostmacroid]); 1458 unset($diff['hostid'], $diff['hostmacroid']); 1459 1460 if ($diff) { 1461 $update[] = ['hostmacroid' => $hostmacroid] + $diff; 1462 } 1463 1464 unset($db_macros[$hostmacroid], $host_macros[$hostid][$macro['macro']]); 1465 } 1466 else { 1467 $create[] = ['hostid' => $hostid] + $macro; 1468 } 1469 } 1470 } 1471 1472 if ($create) { 1473 API::UserMacro()->create($create); 1474 } 1475 1476 if ($update) { 1477 API::UserMacro()->update($update); 1478 } 1479 1480 if ($db_macros) { 1481 API::UserMacro()->delete(array_keys($db_macros)); 1482 } 1483 } 1484} 1485