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 item general. 24 */ 25abstract class CItemGeneral extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 30 'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 31 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN] 32 ]; 33 34 const ERROR_EXISTS_TEMPLATE = 'existsTemplate'; 35 const ERROR_EXISTS = 'exists'; 36 const ERROR_NO_INTERFACE = 'noInterface'; 37 const ERROR_INVALID_KEY = 'invalidKey'; 38 39 protected $fieldRules; 40 41 /** 42 * @abstract 43 * 44 * @param array $options 45 * 46 * @return array 47 */ 48 abstract public function get($options = []); 49 50 public function __construct() { 51 parent::__construct(); 52 53 // template - if templated item, value is taken from template item, cannot be changed on host 54 // system - values should not be updated 55 // host - value should be null for template items 56 $this->fieldRules = [ 57 'type' => ['template' => 1], 58 'snmp_oid' => ['template' => 1], 59 'hostid' => [], 60 'name' => ['template' => 1], 61 'description' => [], 62 'key_' => ['template' => 1], 63 'master_itemid' => ['template' => 1], 64 'delay' => [], 65 'history' => [], 66 'trends' => [], 67 'status' => [], 68 'discover' => [], 69 'value_type' => ['template' => 1], 70 'trapper_hosts' => [], 71 'units' => ['template' => 1], 72 'formula' => ['template' => 1], 73 'error' => ['system' => 1], 74 'lastlogsize' => ['system' => 1], 75 'logtimefmt' => [], 76 'templateid' => ['system' => 1], 77 'valuemapid' => ['template' => 1], 78 'params' => [], 79 'ipmi_sensor' => ['template' => 1], 80 'authtype' => [], 81 'username' => [], 82 'password' => [], 83 'publickey' => [], 84 'privatekey' => [], 85 'mtime' => ['system' => 1], 86 'flags' => [], 87 'filter' => [], 88 'interfaceid' => ['host' => 1], 89 'inventory_link' => [], 90 'lifetime' => [], 91 'preprocessing' => ['template' => 1], 92 'overrides' => ['template' => 1], 93 'jmx_endpoint' => [], 94 'url' => ['template' => 1], 95 'timeout' => ['template' => 1], 96 'query_fields' => ['template' => 1], 97 'parameters' => ['template' => 1], 98 'posts' => ['template' => 1], 99 'status_codes' => ['template' => 1], 100 'follow_redirects' => ['template' => 1], 101 'post_type' => ['template' => 1], 102 'http_proxy' => ['template' => 1], 103 'headers' => ['template' => 1], 104 'retrieve_mode' => ['template' => 1], 105 'request_method' => ['template' => 1], 106 'output_format' => ['template' => 1], 107 'allow_traps' => [], 108 'ssl_cert_file' => ['template' => 1], 109 'ssl_key_file' => ['template' => 1], 110 'ssl_key_password' => ['template' => 1], 111 'verify_peer' => ['template' => 1], 112 'verify_host' => ['template' => 1] 113 ]; 114 115 $this->errorMessages = array_merge($this->errorMessages, [ 116 self::ERROR_NO_INTERFACE => _('Cannot find host interface on "%1$s" for item key "%2$s".') 117 ]); 118 } 119 120 /** 121 * Check items data. 122 * 123 * Any system field passed to the function will be unset. 124 * 125 * @throw APIException 126 * 127 * @param array $items passed by reference 128 * @param bool $update 129 */ 130 protected function checkInput(array &$items, $update = false) { 131 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 132 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', static::SUPPORTED_ITEM_TYPES)] 133 ]]; 134 if ($update) { 135 unset($api_input_rules['fields']['type']['flags']); 136 } 137 138 foreach ($items as $num => $item) { 139 $data = array_intersect_key($item, $api_input_rules['fields']); 140 if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($num + 1), $error)) { 141 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 142 } 143 } 144 145 if ($update) { 146 $itemDbFields = ['itemid' => null]; 147 148 $dbItemsFields = ['itemid', 'templateid']; 149 foreach ($this->fieldRules as $field => $rule) { 150 if (!isset($rule['system'])) { 151 $dbItemsFields[] = $field; 152 } 153 } 154 155 $dbItems = $this->get([ 156 'output' => $dbItemsFields, 157 'itemids' => zbx_objectValues($items, 'itemid'), 158 'editable' => true, 159 'preservekeys' => true 160 ]); 161 162 $dbHosts = API::Host()->get([ 163 'output' => ['hostid', 'status', 'name'], 164 'hostids' => zbx_objectValues($dbItems, 'hostid'), 165 'templated_hosts' => true, 166 'editable' => true, 167 'preservekeys' => true 168 ]); 169 } 170 else { 171 $itemDbFields = [ 172 'name' => null, 173 'key_' => null, 174 'hostid' => null, 175 'type' => null, 176 'value_type' => null, 177 'delay' => null 178 ]; 179 180 $dbHosts = API::Host()->get([ 181 'output' => ['hostid', 'status', 'name'], 182 'hostids' => zbx_objectValues($items, 'hostid'), 183 'templated_hosts' => true, 184 'editable' => true, 185 'preservekeys' => true 186 ]); 187 188 $discovery_rules = []; 189 190 if ($this instanceof CItemPrototype) { 191 $itemDbFields['ruleid'] = null; 192 $druleids = zbx_objectValues($items, 'ruleid'); 193 194 if ($druleids) { 195 $discovery_rules = API::DiscoveryRule()->get([ 196 'output' => ['hostid'], 197 'itemids' => $druleids, 198 'preservekeys' => true 199 ]); 200 } 201 } 202 } 203 204 // interfaces 205 $interfaces = API::HostInterface()->get([ 206 'output' => ['interfaceid', 'hostid', 'type'], 207 'hostids' => zbx_objectValues($dbHosts, 'hostid'), 208 'nopermissions' => true, 209 'preservekeys' => true 210 ]); 211 212 if ($update) { 213 $updateDiscoveredValidator = new CUpdateDiscoveredValidator([ 214 'allowed' => ['itemid', 'status'], 215 'messageAllowedField' => _('Cannot update "%2$s" for a discovered item "%1$s".') 216 ]); 217 foreach ($items as &$item) { 218 // check permissions 219 if (!array_key_exists($item['itemid'], $dbItems)) { 220 self::exception(ZBX_API_ERROR_PERMISSIONS, 221 _('No permissions to referred object or it does not exist!') 222 ); 223 } 224 225 $dbItem = $dbItems[$item['itemid']]; 226 227 if (array_key_exists('hostid', $item) && bccomp($dbItem['hostid'], $item['hostid']) != 0) { 228 self::exception(ZBX_API_ERROR_PARAMETERS, 229 _s('Incorrect value for field "%1$s": %2$s.', 'hostid', _('cannot be changed')) 230 ); 231 } 232 233 $itemName = array_key_exists('name', $item) ? $item['name'] : $dbItem['name']; 234 235 // discovered fields, except status, cannot be updated 236 $updateDiscoveredValidator->setObjectName($itemName); 237 $this->checkPartialValidator($item, $updateDiscoveredValidator, $dbItem); 238 239 $item += [ 240 'hostid' => $dbItem['hostid'], 241 'type' => $dbItem['type'], 242 'name' => $dbItem['name'], 243 'key_' => $dbItem['key_'], 244 'flags' => $dbItem['flags'] 245 ]; 246 } 247 unset($item); 248 } 249 250 $item_key_parser = new CItemKey(); 251 $ip_range_parser = new CIPRangeParser([ 252 'v6' => ZBX_HAVE_IPV6, 253 'ranges' => false, 254 'usermacros' => true, 255 'macros' => [ 256 '{HOST.HOST}', '{HOSTNAME}', '{HOST.NAME}', '{HOST.CONN}', '{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}' 257 ] 258 ]); 259 $update_interval_parser = new CUpdateIntervalParser([ 260 'usermacros' => true, 261 'lldmacros' => (get_class($this) === 'CItemPrototype') 262 ]); 263 264 $index = 0; 265 foreach ($items as $inum => &$item) { 266 $item = $this->clearValues($item); 267 $index++; 268 269 $fullItem = $items[$inum]; 270 271 if (!check_db_fields($itemDbFields, $item)) { 272 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 273 } 274 275 if ($update) { 276 $type = array_key_exists('type', $item) ? $item['type'] : $dbItems[$item['itemid']]['type']; 277 278 if ($type == ITEM_TYPE_HTTPAGENT) { 279 $this->validateHTTPCheck($fullItem, $dbItems[$item['itemid']]); 280 } 281 282 check_db_fields($dbItems[$item['itemid']], $fullItem); 283 284 $this->checkNoParameters( 285 $item, 286 ['templateid', 'state', 'lastlogsize', 'mtime', 'error'], 287 _('Cannot update "%1$s" for item "%2$s".'), 288 $item['name'] 289 ); 290 291 // apply rules 292 foreach ($this->fieldRules as $field => $rules) { 293 if ($fullItem['type'] == ITEM_TYPE_SCRIPT) { 294 $rules['template'] = 1; 295 } 296 297 if ((0 != $fullItem['templateid'] && isset($rules['template'])) || isset($rules['system'])) { 298 unset($item[$field]); 299 300 // For templated item and fields that should not be modified, use the value from DB. 301 if (array_key_exists($field, $dbItems[$item['itemid']]) 302 && array_key_exists($field, $fullItem)) { 303 $fullItem[$field] = $dbItems[$item['itemid']][$field]; 304 } 305 } 306 } 307 308 if (!isset($item['key_'])) { 309 $item['key_'] = $fullItem['key_']; 310 } 311 if (!isset($item['hostid'])) { 312 $item['hostid'] = $fullItem['hostid']; 313 } 314 315 // if a templated item is being assigned to an interface with a different type, ignore it 316 $itemInterfaceType = itemTypeInterface($dbItems[$item['itemid']]['type']); 317 if ($fullItem['templateid'] && isset($item['interfaceid']) && isset($interfaces[$item['interfaceid']]) 318 && $itemInterfaceType !== INTERFACE_TYPE_ANY && $interfaces[$item['interfaceid']]['type'] != $itemInterfaceType) { 319 320 unset($item['interfaceid']); 321 } 322 } 323 else { 324 if ($fullItem['type'] == ITEM_TYPE_HTTPAGENT) { 325 $this->validateHTTPCheck($fullItem, []); 326 } 327 328 if (!isset($dbHosts[$item['hostid']])) { 329 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 330 } 331 332 check_db_fields($itemDbFields, $fullItem); 333 334 $this->checkNoParameters( 335 $item, 336 ['templateid', 'state'], 337 _('Cannot set "%1$s" for item "%2$s".'), 338 $item['name'] 339 ); 340 341 if ($this instanceof CItemPrototype && (!array_key_exists($fullItem['ruleid'], $discovery_rules) 342 || $discovery_rules[$fullItem['ruleid']]['hostid'] != $fullItem['hostid'])) { 343 self::exception(ZBX_API_ERROR_PARAMETERS, 344 _('No permissions to referred object or it does not exist!') 345 ); 346 } 347 } 348 349 if ($fullItem['type'] == ITEM_TYPE_CALCULATED) { 350 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 351 'params' => ['type' => API_CALC_FORMULA, 'flags' => $this instanceof CItemPrototype ? API_ALLOW_LLD_MACRO : 0, 'length' => DB::getFieldLength('items', 'params')], 352 'value_type' => ['type' => API_INT32, 'in' => ITEM_VALUE_TYPE_UINT64.','.ITEM_VALUE_TYPE_FLOAT] 353 ]]; 354 355 $data = array_intersect_key($item, $api_input_rules['fields']); 356 357 if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($inum + 1), $error)) { 358 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 359 } 360 } 361 362 if ($fullItem['type'] == ITEM_TYPE_SCRIPT) { 363 if ($update) { 364 if ($dbItems[$item['itemid']]['type'] == $fullItem['type']) { 365 $flags = API_NOT_EMPTY; 366 } 367 else { 368 $flags = API_REQUIRED | API_NOT_EMPTY; 369 } 370 } 371 else { 372 $flags = API_REQUIRED | API_NOT_EMPTY; 373 } 374 375 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 376 'params' => ['type' => API_STRING_UTF8, 'flags' => $flags, 'length' => DB::getFieldLength('items', 'params')], 377 'timeout' => [ 378 'type' => API_TIME_UNIT, 'flags' => ($this instanceof CItemPrototype) 379 ? $flags | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO 380 : $flags | API_ALLOW_USER_MACRO, 381 'in' => '1:'.SEC_PER_MIN 382 ], 383 'parameters' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['name']], 'fields' => [ 384 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('item_parameter', 'name')], 385 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('item_parameter', 'value')] 386 ]] 387 ]]; 388 389 $data = array_intersect_key($item, $api_input_rules['fields']); 390 391 if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($inum + 1), $error)) { 392 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 393 } 394 } 395 396 $host = $dbHosts[$fullItem['hostid']]; 397 398 // Validate update interval. 399 if (!in_array($fullItem['type'], [ITEM_TYPE_TRAPPER, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT]) 400 && ($fullItem['type'] != ITEM_TYPE_ZABBIX_ACTIVE || strncmp($fullItem['key_'], 'mqtt.get', 8) !== 0) 401 && !validateDelay($update_interval_parser, 'delay', $fullItem['delay'], $error)) { 402 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 403 } 404 405 // For non-numeric types, whichever value was entered in trends field, is overwritten to zero. 406 if ($fullItem['value_type'] == ITEM_VALUE_TYPE_STR || $fullItem['value_type'] == ITEM_VALUE_TYPE_LOG 407 || $fullItem['value_type'] == ITEM_VALUE_TYPE_TEXT) { 408 $item['trends'] = '0'; 409 } 410 411 // check if the item requires an interface 412 if ($host['status'] == HOST_STATUS_TEMPLATE) { 413 unset($item['interfaceid']); 414 } 415 else { 416 $itemInterfaceType = itemTypeInterface($fullItem['type']); 417 418 if ($itemInterfaceType !== false) { 419 if (!array_key_exists('interfaceid', $fullItem) || !$fullItem['interfaceid']) { 420 self::exception(ZBX_API_ERROR_PARAMETERS, _('No interface found.')); 421 } 422 elseif (!isset($interfaces[$fullItem['interfaceid']]) || bccomp($interfaces[$fullItem['interfaceid']]['hostid'], $fullItem['hostid']) != 0) { 423 self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses host interface from non-parent host.')); 424 } 425 elseif ($itemInterfaceType !== INTERFACE_TYPE_ANY && $interfaces[$fullItem['interfaceid']]['type'] != $itemInterfaceType) { 426 self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses incorrect interface type.')); 427 } 428 } 429 // no interface required, just set it to null 430 else { 431 $item['interfaceid'] = 0; 432 } 433 } 434 435 // item key 436 if ($fullItem['type'] == ITEM_TYPE_DB_MONITOR) { 437 if (!isset($fullItem['flags']) || $fullItem['flags'] != ZBX_FLAG_DISCOVERY_RULE) { 438 if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR) == 0) { 439 self::exception(ZBX_API_ERROR_PARAMETERS, 440 _('Check the key, please. Default example was passed.') 441 ); 442 } 443 } 444 elseif ($fullItem['flags'] == ZBX_FLAG_DISCOVERY_RULE) { 445 if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR_DISCOVERY) == 0) { 446 self::exception(ZBX_API_ERROR_PARAMETERS, 447 _('Check the key, please. Default example was passed.') 448 ); 449 } 450 } 451 } 452 elseif (($fullItem['type'] == ITEM_TYPE_SSH && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_SSH) == 0) 453 || ($fullItem['type'] == ITEM_TYPE_TELNET && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_TELNET) == 0)) { 454 self::exception(ZBX_API_ERROR_PARAMETERS, _('Check the key, please. Default example was passed.')); 455 } 456 457 // key 458 if ($item_key_parser->parse($fullItem['key_']) != CParser::PARSE_SUCCESS) { 459 self::exception(ZBX_API_ERROR_PARAMETERS, 460 _params($this->getErrorMsg(self::ERROR_INVALID_KEY), [ 461 $fullItem['key_'], $fullItem['name'], $host['name'], $item_key_parser->getError() 462 ]) 463 ); 464 } 465 466 if (($fullItem['type'] == ITEM_TYPE_TRAPPER || $fullItem['type'] == ITEM_TYPE_HTTPAGENT) 467 && array_key_exists('trapper_hosts', $fullItem) && $fullItem['trapper_hosts'] !== '' 468 && !$ip_range_parser->parse($fullItem['trapper_hosts'])) { 469 self::exception(ZBX_API_ERROR_PARAMETERS, 470 _s('Incorrect value for field "%1$s": %2$s.', 'trapper_hosts', $ip_range_parser->getError()) 471 ); 472 } 473 474 // jmx 475 if ($fullItem['type'] == ITEM_TYPE_JMX) { 476 if (!array_key_exists('jmx_endpoint', $fullItem) && !$update) { 477 $item['jmx_endpoint'] = ZBX_DEFAULT_JMX_ENDPOINT; 478 } 479 if (array_key_exists('jmx_endpoint', $fullItem) && $fullItem['jmx_endpoint'] === '') { 480 self::exception(ZBX_API_ERROR_PARAMETERS, 481 _s('Incorrect value for field "%1$s": %2$s.', 'jmx_endpoint', _('cannot be empty')) 482 ); 483 } 484 485 if (($fullItem['username'] === '') !== ($fullItem['password'] === '')) { 486 self::exception(ZBX_API_ERROR_PARAMETERS, 487 _s('Incorrect value for field "%1$s": %2$s.', 'username', 488 _('both username and password should be either present or empty')) 489 ); 490 } 491 } 492 else { 493 if (array_key_exists('jmx_endpoint', $item) && $item['jmx_endpoint'] !== '') { 494 self::exception(ZBX_API_ERROR_PARAMETERS, 495 _s('Incorrect value for field "%1$s": %2$s.', 'jmx_endpoint', _('should be empty')) 496 ); 497 } 498 elseif (array_key_exists('jmx_endpoint', $fullItem) && $fullItem['jmx_endpoint'] !== '') { 499 $item['jmx_endpoint'] = ''; 500 } 501 } 502 503 // Dependent item. 504 if ($fullItem['type'] == ITEM_TYPE_DEPENDENT) { 505 if ($update) { 506 if (array_key_exists('master_itemid', $item) && !$item['master_itemid']) { 507 self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', 508 'master_itemid', _('cannot be empty') 509 )); 510 } 511 if ($dbItems[$fullItem['itemid']]['type'] != ITEM_TYPE_DEPENDENT 512 && !array_key_exists('master_itemid', $item)) { 513 self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', 514 'master_itemid', _('cannot be empty') 515 )); 516 } 517 } 518 elseif (!array_key_exists('master_itemid', $item) || !$item['master_itemid']) { 519 self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', 520 'master_itemid', _('cannot be empty') 521 )); 522 } 523 if (array_key_exists('master_itemid', $item) && !is_int($item['master_itemid']) 524 && !(is_string($item['master_itemid']) && ctype_digit($item['master_itemid']))) { 525 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value "%1$s" for "%2$s" field.', 526 $item['master_itemid'], 'master_itemid' 527 )); 528 } 529 } 530 else { 531 if (array_key_exists('master_itemid', $item) && $item['master_itemid']) { 532 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 533 'master_itemid', _('should be empty') 534 )); 535 } 536 $item['master_itemid'] = 0; 537 } 538 539 // ssh, telnet 540 if ($fullItem['type'] == ITEM_TYPE_SSH || $fullItem['type'] == ITEM_TYPE_TELNET) { 541 if ($fullItem['username'] === '') { 542 self::exception(ZBX_API_ERROR_PARAMETERS, _('No authentication user name specified.')); 543 } 544 545 if ($fullItem['type'] == ITEM_TYPE_SSH && $fullItem['authtype'] == ITEM_AUTHTYPE_PUBLICKEY) { 546 if ($fullItem['publickey'] === '') { 547 self::exception(ZBX_API_ERROR_PARAMETERS, _('No public key file specified.')); 548 } 549 if ($fullItem['privatekey'] === '') { 550 self::exception(ZBX_API_ERROR_PARAMETERS, _('No private key file specified.')); 551 } 552 } 553 } 554 555 // Prevent IPMI sensor field being empty if item key is not "ipmi.get". 556 if ($fullItem['type'] == ITEM_TYPE_IPMI && $fullItem['key_'] !== 'ipmi.get' 557 && (!array_key_exists('ipmi_sensor', $fullItem) || $fullItem['ipmi_sensor'] === '')) { 558 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 559 'ipmi_sensor', _('cannot be empty') 560 )); 561 } 562 563 // snmp trap 564 if ($fullItem['type'] == ITEM_TYPE_SNMPTRAP 565 && $fullItem['key_'] !== 'snmptrap.fallback' && $item_key_parser->getKey() !== 'snmptrap') { 566 self::exception(ZBX_API_ERROR_PARAMETERS, _('SNMP trap key is invalid.')); 567 } 568 569 // snmp oid 570 if ($fullItem['type'] == ITEM_TYPE_SNMP 571 && (!array_key_exists('snmp_oid', $fullItem) || $fullItem['snmp_oid'] === '')) { 572 self::exception(ZBX_API_ERROR_PARAMETERS, _('No SNMP OID specified.')); 573 } 574 575 $this->checkSpecificFields($fullItem, $update ? 'update' : 'create'); 576 577 $this->validateItemPreprocessing($fullItem); 578 $this->validateTags($item, '/'.$index); 579 } 580 unset($item); 581 582 $this->validateValueMaps($items); 583 584 $this->checkAndAddUuid($items, $dbHosts, $update); 585 $this->checkExistingItems($items); 586 } 587 588 /** 589 * Check that only items on templates have UUID. Add UUID to all host prototypes on templates, 590 * if it doesn't exist. 591 * 592 * @param array $items_to_create 593 * @param array $db_hosts 594 * @param bool $is_update 595 * 596 * @throws APIException 597 */ 598 protected function checkAndAddUuid(array &$items_to_create, array $db_hosts, bool $is_update): void { 599 if ($is_update) { 600 foreach ($items_to_create as $index => &$item) { 601 if (array_key_exists('uuid', $item)) { 602 self::exception(ZBX_API_ERROR_PARAMETERS, 603 _s('Invalid parameter "%1$s": %2$s.', '/' . ($index + 1), 604 _s('unexpected parameter "%1$s"', 'uuid') 605 ) 606 ); 607 } 608 } 609 610 return; 611 } 612 613 foreach ($items_to_create as $index => &$item) { 614 if ($db_hosts[$item['hostid']]['status'] != HOST_STATUS_TEMPLATE && array_key_exists('uuid', $item)) { 615 self::exception(ZBX_API_ERROR_PARAMETERS, 616 _s('Invalid parameter "%1$s": %2$s.', '/' . ($index + 1), _s('unexpected parameter "%1$s"', 'uuid')) 617 ); 618 } 619 620 if ($db_hosts[$item['hostid']]['status'] == HOST_STATUS_TEMPLATE && !array_key_exists('uuid', $item)) { 621 $item['uuid'] = generateUuidV4(); 622 } 623 } 624 unset($item); 625 626 $db_uuid = DB::select('items', [ 627 'output' => ['uuid'], 628 'filter' => ['uuid' => array_column($items_to_create, 'uuid')], 629 'limit' => 1 630 ]); 631 632 if ($db_uuid) { 633 self::exception(ZBX_API_ERROR_PARAMETERS, 634 _s('Entry with UUID "%1$s" already exists.', $db_uuid[0]['uuid']) 635 ); 636 } 637 } 638 639 /** 640 * Validates tags. 641 * 642 * @param array $item 643 * @param array $item['tags'] 644 * @param string $item['tags'][]['tag'] 645 * @param string $item['tags'][]['value'] 646 * 647 * @throws APIException if the input is invalid. 648 */ 649 protected function validateTags(array $item, string $path = '/') { 650 if (!array_key_exists('tags', $item)) { 651 return; 652 } 653 654 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 655 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 656 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('item_tag', 'tag')], 657 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('item_tag', 'value')] 658 ]] 659 ]]; 660 661 $item_tags = ['tags' => $item['tags']]; 662 if (!CApiInputValidator::validate($api_input_rules, $item_tags, $path, $error)) { 663 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 664 } 665 } 666 667 /** 668 * Check item specific fields. Each API like Item, Itemprototype and Discovery rule may inherit different fields 669 * to validate. 670 * 671 * @param array $item An array of single item data. 672 * @param string $method A string of "create" or "update" method. 673 * 674 * @return bool 675 */ 676 abstract protected function checkSpecificFields(array $item, $method); 677 678 protected function clearValues(array $item) { 679 if (isset($item['port']) && $item['port'] != '') { 680 $item['port'] = ltrim($item['port'], '0'); 681 if ($item['port'] == '') { 682 $item['port'] = 0; 683 } 684 } 685 686 if (array_key_exists('type', $item) && 687 ($item['type'] == ITEM_TYPE_DEPENDENT || $item['type'] == ITEM_TYPE_TRAPPER 688 || ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE && array_key_exists('key_', $item) 689 && strncmp($item['key_'], 'mqtt.get', 8) === 0))) { 690 $item['delay'] = 0; 691 } 692 693 return $item; 694 } 695 696 protected function errorInheritFlags($flag, $key, $host) { 697 switch ($flag) { 698 case ZBX_FLAG_DISCOVERY_NORMAL: 699 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item.', $key, $host)); 700 break; 701 case ZBX_FLAG_DISCOVERY_RULE: 702 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as a discovery rule.', $key, $host)); 703 break; 704 case ZBX_FLAG_DISCOVERY_PROTOTYPE: 705 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item prototype.', $key, $host)); 706 break; 707 case ZBX_FLAG_DISCOVERY_CREATED: 708 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item created from item prototype.', $key, $host)); 709 break; 710 default: 711 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as unknown item element.', $key, $host)); 712 } 713 } 714 715 /** 716 * Returns the interface that best matches the given item. 717 * 718 * @param array $item_type An item type 719 * @param array $interfaces An array of interfaces to choose from 720 * 721 * @return array|boolean The best matching interface; 722 * an empty array of no matching interface was found; 723 * false, if the item does not need an interface 724 */ 725 public static function findInterfaceForItem($item_type, array $interfaces) { 726 $interface_by_type = []; 727 foreach ($interfaces as $interface) { 728 if ($interface['main'] == 1) { 729 $interface_by_type[$interface['type']] = $interface; 730 } 731 } 732 733 // find item interface type 734 $type = itemTypeInterface($item_type); 735 736 // the item can use any interface 737 if ($type == INTERFACE_TYPE_ANY) { 738 $interface_types = [INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI]; 739 foreach ($interface_types as $interface_type) { 740 if (array_key_exists($interface_type, $interface_by_type)) { 741 return $interface_by_type[$interface_type]; 742 } 743 } 744 } 745 // the item uses a specific type of interface 746 elseif ($type !== false) { 747 return array_key_exists($type, $interface_by_type) ? $interface_by_type[$type] : []; 748 } 749 // the item does not need an interface 750 else { 751 return false; 752 } 753 } 754 755 /** 756 * Updates the children of the item on the given hosts and propagates the inheritance to the child hosts. 757 * 758 * @param array $tpl_items An array of items to inherit. 759 * @param array|null $hostids An array of hosts to inherit to; if set to null, the items will be inherited to all 760 * linked hosts or templates. 761 */ 762 protected function inherit(array $tpl_items, array $hostids = null) { 763 $tpl_items = zbx_toHash($tpl_items, 'itemid'); 764 765 // Inherit starting from common items and finishing up dependent. 766 while ($tpl_items) { 767 $_tpl_items = []; 768 769 foreach ($tpl_items as $tpl_item) { 770 if ($tpl_item['type'] != ITEM_TYPE_DEPENDENT 771 || !array_key_exists($tpl_item['master_itemid'], $tpl_items)) { 772 $_tpl_items[$tpl_item['itemid']] = $tpl_item; 773 } 774 } 775 776 foreach ($_tpl_items as $itemid => $_tpl_item) { 777 unset($tpl_items[$itemid]); 778 } 779 780 $this->_inherit($_tpl_items, $hostids); 781 } 782 } 783 784 /** 785 * Auxiliary method for item inheritance. See full description in inherit() method. 786 */ 787 private function _inherit(array $tpl_items, array $hostids = null) { 788 // Prepare the child items. 789 $new_items = $this->prepareInheritedItems($tpl_items, $hostids); 790 if (!$new_items) { 791 return; 792 } 793 794 $ins_items = []; 795 $upd_items = []; 796 797 foreach ($new_items as $new_item) { 798 if (array_key_exists('itemid', $new_item)) { 799 if ($this instanceof CItemPrototype) { 800 unset($new_item['ruleid']); 801 } 802 $upd_items[$new_item['itemid']] = $new_item; 803 } 804 else { 805 $ins_items[] = $new_item; 806 } 807 } 808 809 $this->validateDependentItems($new_items); 810 811 // Save the new items. 812 if ($ins_items) { 813 if ($this instanceof CItem) { 814 static::validateInventoryLinks($ins_items, false); 815 } 816 817 $this->createReal($ins_items); 818 } 819 820 if ($upd_items) { 821 if ($this instanceof CItem) { 822 static::validateInventoryLinks($upd_items, true); 823 } 824 825 $this->updateReal($upd_items); 826 } 827 828 $new_items = array_merge($upd_items, $ins_items); 829 830 // Inheriting items from the templates. 831 $db_items = DBselect( 832 'SELECT i.itemid'. 833 ' FROM items i,hosts h'. 834 ' WHERE i.hostid=h.hostid'. 835 ' AND '.dbConditionInt('i.itemid', zbx_objectValues($new_items, 'itemid')). 836 ' AND '.dbConditionInt('h.status', [HOST_STATUS_TEMPLATE]) 837 ); 838 839 $tpl_itemids = []; 840 while ($db_item = DBfetch($db_items)) { 841 $tpl_itemids[$db_item['itemid']] = true; 842 } 843 844 foreach ($new_items as $index => $new_item) { 845 if (!array_key_exists($new_item['itemid'], $tpl_itemids)) { 846 unset($new_items[$index]); 847 } 848 } 849 850 $this->inherit($new_items); 851 } 852 853 /** 854 * Prepares and returns an array of child items, inherited from items $tpl_items on the given hosts. 855 * 856 * @param array $tpl_items 857 * @param string $tpl_items[<itemid>]['itemid'] 858 * @param string $tpl_items[<itemid>]['hostid'] 859 * @param string $tpl_items[<itemid>]['key_'] 860 * @param int $tpl_items[<itemid>]['type'] 861 * @param array $tpl_items[<itemid>]['preprocessing'] (optional) 862 * @param int $tpl_items[<itemid>]['preprocessing'][]['type'] 863 * @param string $tpl_items[<itemid>]['preprocessing'][]['params'] 864 * @param int $tpl_items[<itemid>]['flags'] 865 * @param string $tpl_items[<itemid>]['master_itemid'] (optional) 866 * @param mixed $tpl_items[<itemid>][<field_name>] (optional) 867 * @param array|null $hostids 868 * 869 * @return array an array of unsaved child items 870 */ 871 private function prepareInheritedItems(array $tpl_items, array $hostids = null) { 872 $itemids_by_templateid = []; 873 foreach ($tpl_items as $tpl_item) { 874 $itemids_by_templateid[$tpl_item['hostid']][] = $tpl_item['itemid']; 875 } 876 877 // Fetch all child hosts. 878 $chd_hosts = API::Host()->get([ 879 'output' => ['hostid', 'host', 'status'], 880 'selectParentTemplates' => ['templateid'], 881 'selectInterfaces' => ['interfaceid', 'main', 'type'], 882 'templateids' => array_keys($itemids_by_templateid), 883 'hostids' => $hostids, 884 'preservekeys' => true, 885 'nopermissions' => true, 886 'templated_hosts' => true 887 ]); 888 if (!$chd_hosts) { 889 return []; 890 } 891 892 $chd_items_tpl = []; 893 $chd_items_key = []; 894 895 // Preparing list of items by item templateid. 896 $sql = 'SELECT i.itemid,i.hostid,i.type,i.key_,i.flags,i.templateid'. 897 ' FROM items i'. 898 ' WHERE '.dbConditionInt('i.templateid', zbx_objectValues($tpl_items, 'itemid')); 899 if ($hostids !== null) { 900 $sql .= ' AND '.dbConditionInt('i.hostid', $hostids); 901 } 902 $db_items = DBselect($sql); 903 904 while ($db_item = DBfetch($db_items)) { 905 $hostid = $db_item['hostid']; 906 unset($db_item['hostid']); 907 908 $chd_items_tpl[$hostid][$db_item['templateid']] = $db_item; 909 } 910 911 $hostids_by_key = []; 912 913 // Preparing list of items by item key. 914 foreach ($chd_hosts as $chd_host) { 915 $tpl_itemids = []; 916 917 foreach ($chd_host['parentTemplates'] as $parent_template) { 918 if (array_key_exists($parent_template['templateid'], $itemids_by_templateid)) { 919 $tpl_itemids = array_merge($tpl_itemids, $itemids_by_templateid[$parent_template['templateid']]); 920 } 921 } 922 923 foreach ($tpl_itemids as $tpl_itemid) { 924 if (!array_key_exists($chd_host['hostid'], $chd_items_tpl) 925 || !array_key_exists($tpl_itemid, $chd_items_tpl[$chd_host['hostid']])) { 926 $hostids_by_key[$tpl_items[$tpl_itemid]['key_']][] = $chd_host['hostid']; 927 } 928 } 929 } 930 931 foreach ($hostids_by_key as $key_ => $key_hostids) { 932 $sql_select = ($this instanceof CItemPrototype) ? ',id.parent_itemid AS ruleid' : ''; 933 // "LEFT JOIN" is needed to check flags on inherited and existing item, item prototype or lld rule. 934 // For example, when linking an item prototype with same key as in an item on target host or template. 935 $sql_join = ($this instanceof CItemPrototype) ? ' LEFT JOIN item_discovery id ON i.itemid=id.itemid' : ''; 936 $db_items = DBselect( 937 'SELECT i.itemid,i.hostid,i.type,i.key_,i.flags,i.templateid'.$sql_select. 938 ' FROM items i'.$sql_join. 939 ' WHERE '.dbConditionInt('i.hostid', $key_hostids). 940 ' AND '.dbConditionString('i.key_', [$key_]) 941 ); 942 943 while ($db_item = DBfetch($db_items)) { 944 $hostid = $db_item['hostid']; 945 unset($db_item['hostid']); 946 947 $chd_items_key[$hostid][$db_item['key_']] = $db_item; 948 } 949 } 950 951 // List of the discovery rules. 952 if ($this instanceof CItemPrototype) { 953 // List of itemids without 'ruleid' property. 954 $tpl_itemids = []; 955 $tpl_ruleids = []; 956 foreach ($tpl_items as $tpl_item) { 957 if (!array_key_exists('ruleid', $tpl_item)) { 958 $tpl_itemids[] = $tpl_item['itemid']; 959 } 960 else { 961 $tpl_ruleids[$tpl_item['ruleid']] = true; 962 } 963 } 964 965 if ($tpl_itemids) { 966 $db_rules = DBselect( 967 'SELECT id.parent_itemid,id.itemid'. 968 ' FROM item_discovery id'. 969 ' WHERE '.dbConditionInt('id.itemid', $tpl_itemids) 970 ); 971 972 while ($db_rule = DBfetch($db_rules)) { 973 $tpl_items[$db_rule['itemid']]['ruleid'] = $db_rule['parent_itemid']; 974 $tpl_ruleids[$db_rule['parent_itemid']] = true; 975 } 976 } 977 978 $sql = 'SELECT i.hostid,i.templateid,i.itemid'. 979 ' FROM items i'. 980 ' WHERE '.dbConditionInt('i.templateid', array_keys($tpl_ruleids)); 981 if ($hostids !== null) { 982 $sql .= ' AND '.dbConditionInt('i.hostid', $hostids); 983 } 984 $db_rules = DBselect($sql); 985 986 // List of child lld ruleids by child hostid and parent lld ruleid. 987 $chd_ruleids = []; 988 while ($db_rule = DBfetch($db_rules)) { 989 $chd_ruleids[$db_rule['hostid']][$db_rule['templateid']] = $db_rule['itemid']; 990 } 991 } 992 993 $new_items = []; 994 // List of the updated item keys by hostid. 995 $upd_hostids_by_key = []; 996 997 foreach ($chd_hosts as $chd_host) { 998 $tpl_itemids = []; 999 1000 foreach ($chd_host['parentTemplates'] as $parent_template) { 1001 if (array_key_exists($parent_template['templateid'], $itemids_by_templateid)) { 1002 $tpl_itemids = array_merge($tpl_itemids, $itemids_by_templateid[$parent_template['templateid']]); 1003 } 1004 } 1005 1006 foreach ($tpl_itemids as $tpl_itemid) { 1007 $tpl_item = $tpl_items[$tpl_itemid]; 1008 1009 $chd_item = null; 1010 1011 // Update by templateid. 1012 if (array_key_exists($chd_host['hostid'], $chd_items_tpl) 1013 && array_key_exists($tpl_item['itemid'], $chd_items_tpl[$chd_host['hostid']])) { 1014 $chd_item = $chd_items_tpl[$chd_host['hostid']][$tpl_item['itemid']]; 1015 1016 if ($tpl_item['key_'] !== $chd_item['key_']) { 1017 $upd_hostids_by_key[$tpl_item['key_']][] = $chd_host['hostid']; 1018 } 1019 } 1020 // Update by key. 1021 elseif (array_key_exists($chd_host['hostid'], $chd_items_key) 1022 && array_key_exists($tpl_item['key_'], $chd_items_key[$chd_host['hostid']])) { 1023 $chd_item = $chd_items_key[$chd_host['hostid']][$tpl_item['key_']]; 1024 1025 // Check if an item of a different type with the same key exists. 1026 if ($tpl_item['flags'] != $chd_item['flags']) { 1027 $this->errorInheritFlags($chd_item['flags'], $chd_item['key_'], $chd_host['host']); 1028 } 1029 1030 // Check if item already linked to another template. 1031 if ($chd_item['templateid'] != 0 && bccomp($chd_item['templateid'], $tpl_item['itemid']) != 0) { 1032 self::exception(ZBX_API_ERROR_PARAMETERS, _params( 1033 $this->getErrorMsg(self::ERROR_EXISTS_TEMPLATE), [$tpl_item['key_'], $chd_host['host']] 1034 )); 1035 } 1036 1037 if ($this instanceof CItemPrototype) { 1038 $chd_ruleid = $chd_ruleids[$chd_host['hostid']][$tpl_item['ruleid']]; 1039 if (bccomp($chd_item['ruleid'], $chd_ruleid) != 0) { 1040 self::exception(ZBX_API_ERROR_PARAMETERS, 1041 _s('Item prototype "%1$s" already exists on "%2$s", linked to another rule.', 1042 $chd_item['key_'], $chd_host['host'] 1043 ) 1044 ); 1045 } 1046 } 1047 } 1048 1049 // copying item 1050 $new_item = $tpl_item; 1051 $new_item['uuid'] = ''; 1052 1053 if ($chd_item !== null) { 1054 $new_item['itemid'] = $chd_item['itemid']; 1055 } 1056 else { 1057 unset($new_item['itemid']); 1058 if ($this instanceof CItemPrototype) { 1059 $new_item['ruleid'] = $chd_ruleids[$chd_host['hostid']][$tpl_item['ruleid']]; 1060 } 1061 } 1062 $new_item['hostid'] = $chd_host['hostid']; 1063 $new_item['templateid'] = $tpl_item['itemid']; 1064 1065 if ($chd_host['status'] != HOST_STATUS_TEMPLATE) { 1066 if ($chd_item === null || $new_item['type'] != $chd_item['type']) { 1067 $interface = self::findInterfaceForItem($new_item['type'], $chd_host['interfaces']); 1068 1069 if ($interface) { 1070 $new_item['interfaceid'] = $interface['interfaceid']; 1071 } 1072 elseif ($interface !== false) { 1073 self::exception(ZBX_API_ERROR_PARAMETERS, _params( 1074 $this->getErrorMsg(self::ERROR_NO_INTERFACE), [$chd_host['host'], $new_item['key_']] 1075 )); 1076 } 1077 } 1078 1079 if ($this instanceof CItem || $this instanceof CDiscoveryRule) { 1080 if (!array_key_exists('itemid', $new_item)) { 1081 $new_item['rtdata'] = true; 1082 } 1083 } 1084 } 1085 1086 if (array_key_exists('preprocessing', $new_item)) { 1087 foreach ($new_item['preprocessing'] as $preprocessing) { 1088 if ($chd_item) { 1089 $preprocessing['itemid'] = $chd_item['itemid']; 1090 } 1091 else { 1092 unset($preprocessing['itemid']); 1093 } 1094 } 1095 } 1096 1097 $new_items[] = $new_item; 1098 } 1099 } 1100 1101 // Check if item with a new key already exists on the child host. 1102 if ($upd_hostids_by_key) { 1103 $sql_where = []; 1104 foreach ($upd_hostids_by_key as $key => $hostids) { 1105 $sql_where[] = dbConditionInt('i.hostid', $hostids).' AND i.key_='.zbx_dbstr($key); 1106 } 1107 1108 $sql = 'SELECT i.hostid,i.key_'. 1109 ' FROM items i'. 1110 ' WHERE ('.implode(') OR (', $sql_where).')'; 1111 $db_items = DBselect($sql, 1); 1112 1113 if ($db_item = DBfetch($db_items)) { 1114 self::exception(ZBX_API_ERROR_PARAMETERS, _params($this->getErrorMsg(self::ERROR_EXISTS), 1115 [$db_item['key_'], $chd_hosts[$db_item['hostid']]['host']] 1116 )); 1117 } 1118 } 1119 1120 return $this->prepareDependentItems($tpl_items, $new_items, $hostids); 1121 } 1122 1123 /** 1124 * Update relations for inherited dependent items to master items. 1125 * 1126 * @param array $tpl_items 1127 * @param int $tpl_items[<itemid>]['type'] 1128 * @param string $tpl_items[<itemid>]['master_itemid'] 1129 * @param array $new_items 1130 * @param string $new_items[<itemid>]['hostid'] 1131 * @param int $new_items[<itemid>]['type'] 1132 * @param string $new_items[<itemid>]['templateid'] 1133 * @param array|null $hostids 1134 * 1135 * @return array an array of synchronized inherited items. 1136 */ 1137 private function prepareDependentItems(array $tpl_items, array $new_items, array $hostids = null) { 1138 $tpl_master_itemids = []; 1139 1140 foreach ($tpl_items as $tpl_item) { 1141 if ($tpl_item['type'] == ITEM_TYPE_DEPENDENT) { 1142 $tpl_master_itemids[$tpl_item['master_itemid']] = true; 1143 } 1144 } 1145 1146 if ($tpl_master_itemids) { 1147 $sql = 'SELECT i.itemid,i.hostid,i.templateid'. 1148 ' FROM items i'. 1149 ' WHERE '.dbConditionId('i.templateid', array_keys($tpl_master_itemids)); 1150 if ($hostids !== null) { 1151 $sql .= ' AND '.dbConditionId('i.hostid', $hostids); 1152 } 1153 $db_items = DBselect($sql); 1154 1155 $master_links = []; 1156 1157 while ($db_item = DBfetch($db_items)) { 1158 $master_links[$db_item['templateid']][$db_item['hostid']] = $db_item['itemid']; 1159 } 1160 1161 foreach ($new_items as &$new_item) { 1162 if ($new_item['type'] == ITEM_TYPE_DEPENDENT) { 1163 $tpl_item = $tpl_items[$new_item['templateid']]; 1164 1165 if (array_key_exists('master_itemid', $tpl_item)) { 1166 $new_item['master_itemid'] = $master_links[$tpl_item['master_itemid']][$new_item['hostid']]; 1167 } 1168 } 1169 } 1170 unset($new_item); 1171 } 1172 1173 return $new_items; 1174 } 1175 1176 /** 1177 * Validate item pre-processing. 1178 * 1179 * @param array $item An array of single item data. 1180 * @param array $item['preprocessing'] An array of item pre-processing data. 1181 * @param string $item['preprocessing'][]['type'] The preprocessing option type. Possible values: 1182 * 1 - ZBX_PREPROC_MULTIPLIER; 1183 * 2 - ZBX_PREPROC_RTRIM; 1184 * 3 - ZBX_PREPROC_LTRIM; 1185 * 4 - ZBX_PREPROC_TRIM; 1186 * 5 - ZBX_PREPROC_REGSUB; 1187 * 6 - ZBX_PREPROC_BOOL2DEC; 1188 * 7 - ZBX_PREPROC_OCT2DEC; 1189 * 8 - ZBX_PREPROC_HEX2DEC; 1190 * 9 - ZBX_PREPROC_DELTA_VALUE; 1191 * 10 - ZBX_PREPROC_DELTA_SPEED; 1192 * 11 - ZBX_PREPROC_XPATH; 1193 * 12 - ZBX_PREPROC_JSONPATH; 1194 * 13 - ZBX_PREPROC_VALIDATE_RANGE; 1195 * 14 - ZBX_PREPROC_VALIDATE_REGEX; 1196 * 15 - ZBX_PREPROC_VALIDATE_NOT_REGEX; 1197 * 16 - ZBX_PREPROC_ERROR_FIELD_JSON; 1198 * 17 - ZBX_PREPROC_ERROR_FIELD_XML; 1199 * 18 - ZBX_PREPROC_ERROR_FIELD_REGEX; 1200 * 19 - ZBX_PREPROC_THROTTLE_VALUE; 1201 * 20 - ZBX_PREPROC_THROTTLE_TIMED_VALUE; 1202 * 21 - ZBX_PREPROC_SCRIPT; 1203 * 22 - ZBX_PREPROC_PROMETHEUS_PATTERN; 1204 * 23 - ZBX_PREPROC_PROMETHEUS_TO_JSON; 1205 * 24 - ZBX_PREPROC_CSV_TO_JSON; 1206 * 25 - ZBX_PREPROC_STR_REPLACE; 1207 * 26 - ZBX_PREPROC_VALIDATE_NOT_SUPPORTED; 1208 * @param string $item['preprocessing'][]['params'] Additional parameters used by preprocessing 1209 * option. Multiple parameters are separated by LF 1210 * (\n) character. 1211 * @param string $item['preprocessing'][]['error_handler'] Action type used in case of preprocessing step 1212 * failure. Possible values: 1213 * 0 - ZBX_PREPROC_FAIL_DEFAULT; 1214 * 1 - ZBX_PREPROC_FAIL_DISCARD_VALUE; 1215 * 2 - ZBX_PREPROC_FAIL_SET_VALUE; 1216 * 3 - ZBX_PREPROC_FAIL_SET_ERROR. 1217 * @param string $item['preprocessing'][]['error_handler_params'] Error handler parameters. 1218 */ 1219 protected function validateItemPreprocessing(array $item) { 1220 if (array_key_exists('preprocessing', $item)) { 1221 if (!is_array($item['preprocessing'])) { 1222 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1223 } 1224 1225 $type_validator = new CLimitedSetValidator(['values' => static::SUPPORTED_PREPROCESSING_TYPES]); 1226 1227 $error_handler_validator = new CLimitedSetValidator([ 1228 'values' => [ZBX_PREPROC_FAIL_DEFAULT, ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE, 1229 ZBX_PREPROC_FAIL_SET_ERROR 1230 ] 1231 ]); 1232 1233 $unsupported_error_handler_validator = new CLimitedSetValidator([ 1234 'values' => [ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE, ZBX_PREPROC_FAIL_SET_ERROR] 1235 ]); 1236 1237 $prometheus_pattern_parser = new CPrometheusPatternParser(['usermacros' => true, 1238 'lldmacros' => ($this instanceof CItemPrototype) 1239 ]); 1240 $prometheus_output_parser = new CPrometheusOutputParser(['usermacros' => true, 1241 'lldmacros' => ($this instanceof CItemPrototype) 1242 ]); 1243 1244 $required_fields = ['type', 'params', 'error_handler', 'error_handler_params']; 1245 $delta = false; 1246 $throttling = false; 1247 $prometheus = false; 1248 1249 foreach ($item['preprocessing'] as $preprocessing) { 1250 $missing_keys = array_diff($required_fields, array_keys($preprocessing)); 1251 1252 if ($missing_keys) { 1253 self::exception(ZBX_API_ERROR_PARAMETERS, 1254 _s('Item pre-processing is missing parameters: %1$s', implode(', ', $missing_keys)) 1255 ); 1256 } 1257 1258 if (is_array($preprocessing['type'])) { 1259 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1260 } 1261 elseif ($preprocessing['type'] === '' || $preprocessing['type'] === null 1262 || $preprocessing['type'] === false) { 1263 self::exception(ZBX_API_ERROR_PARAMETERS, 1264 _s('Incorrect value for field "%1$s": %2$s.', 'type', _('cannot be empty')) 1265 ); 1266 } 1267 1268 if (!$type_validator->validate($preprocessing['type'])) { 1269 self::exception(ZBX_API_ERROR_PARAMETERS, 1270 _s('Incorrect value for field "%1$s": %2$s.', 'type', 1271 _s('unexpected value "%1$s"', $preprocessing['type']) 1272 ) 1273 ); 1274 } 1275 1276 $preprocessing['params'] = str_replace("\r\n", "\n", $preprocessing['params']); 1277 1278 switch ($preprocessing['type']) { 1279 case ZBX_PREPROC_MULTIPLIER: 1280 // Check if custom multiplier is a valid number. 1281 $params = $preprocessing['params']; 1282 1283 if (is_array($params)) { 1284 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1285 } 1286 elseif ($params === '' || $params === null || $params === false) { 1287 self::exception(ZBX_API_ERROR_PARAMETERS, 1288 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) 1289 ); 1290 } 1291 1292 if (is_numeric($params)) { 1293 break; 1294 } 1295 1296 $types = ['usermacros' => true]; 1297 1298 if ($this instanceof CItemPrototype) { 1299 $types['lldmacros'] = true; 1300 } 1301 1302 if (!(new CMacrosResolverGeneral)->getMacroPositions($params, $types)) { 1303 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1304 'params', _('a numeric value is expected') 1305 )); 1306 } 1307 break; 1308 1309 case ZBX_PREPROC_RTRIM: 1310 case ZBX_PREPROC_LTRIM: 1311 case ZBX_PREPROC_TRIM: 1312 case ZBX_PREPROC_XPATH: 1313 case ZBX_PREPROC_JSONPATH: 1314 case ZBX_PREPROC_VALIDATE_REGEX: 1315 case ZBX_PREPROC_VALIDATE_NOT_REGEX: 1316 case ZBX_PREPROC_ERROR_FIELD_JSON: 1317 case ZBX_PREPROC_ERROR_FIELD_XML: 1318 case ZBX_PREPROC_SCRIPT: 1319 // Check 'params' if not empty. 1320 if (is_array($preprocessing['params'])) { 1321 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1322 } 1323 elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null 1324 || $preprocessing['params'] === false) { 1325 self::exception(ZBX_API_ERROR_PARAMETERS, 1326 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) 1327 ); 1328 } 1329 break; 1330 1331 case ZBX_PREPROC_REGSUB: 1332 case ZBX_PREPROC_ERROR_FIELD_REGEX: 1333 case ZBX_PREPROC_STR_REPLACE: 1334 // Check if 'params' are not empty and if second parameter contains (after \n) is not empty. 1335 if (is_array($preprocessing['params'])) { 1336 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1337 } 1338 elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null 1339 || $preprocessing['params'] === false) { 1340 self::exception(ZBX_API_ERROR_PARAMETERS, 1341 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) 1342 ); 1343 } 1344 1345 $params = explode("\n", $preprocessing['params']); 1346 1347 if ($params[0] === '') { 1348 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1349 'params', _('first parameter is expected') 1350 )); 1351 } 1352 1353 if (($preprocessing['type'] == ZBX_PREPROC_REGSUB 1354 || $preprocessing['type'] == ZBX_PREPROC_ERROR_FIELD_REGEX) 1355 && (!array_key_exists(1, $params) || $params[1] === '')) { 1356 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1357 'params', _('second parameter is expected') 1358 )); 1359 } 1360 break; 1361 1362 case ZBX_PREPROC_VALIDATE_RANGE: 1363 if (is_array($preprocessing['params'])) { 1364 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1365 } 1366 elseif (trim($preprocessing['params']) === '' || $preprocessing['params'] === null 1367 || $preprocessing['params'] === false) { 1368 self::exception(ZBX_API_ERROR_PARAMETERS, 1369 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) 1370 ); 1371 } 1372 1373 $params = explode("\n", $preprocessing['params']); 1374 1375 if ($params[0] !== '' && !is_numeric($params[0]) 1376 && (new CUserMacroParser())->parse($params[0]) != CParser::PARSE_SUCCESS 1377 && (!($this instanceof CItemPrototype) 1378 || ((new CLLDMacroFunctionParser())->parse($params[0]) != CParser::PARSE_SUCCESS 1379 && (new CLLDMacroParser())->parse($params[0]) != CParser::PARSE_SUCCESS))) { 1380 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1381 'params', _('a numeric value is expected') 1382 )); 1383 } 1384 1385 if ($params[1] !== '' && !is_numeric($params[1]) 1386 && (new CUserMacroParser())->parse($params[1]) != CParser::PARSE_SUCCESS 1387 && (!($this instanceof CItemPrototype) 1388 || ((new CLLDMacroFunctionParser())->parse($params[1]) != CParser::PARSE_SUCCESS 1389 && (new CLLDMacroParser())->parse($params[1]) != CParser::PARSE_SUCCESS))) { 1390 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1391 'params', _('a numeric value is expected') 1392 )); 1393 } 1394 1395 if (is_numeric($params[0]) && is_numeric($params[1]) && $params[0] > $params[1]) { 1396 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1397 'Incorrect value for field "%1$s": %2$s.', 1398 'params', 1399 _s('"%1$s" value must be less than or equal to "%2$s" value', _('min'), _('max')) 1400 )); 1401 } 1402 break; 1403 1404 case ZBX_PREPROC_BOOL2DEC: 1405 case ZBX_PREPROC_OCT2DEC: 1406 case ZBX_PREPROC_HEX2DEC: 1407 case ZBX_PREPROC_THROTTLE_VALUE: 1408 // Check if 'params' is empty, because it must be empty. 1409 if (is_array($preprocessing['params'])) { 1410 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1411 } 1412 elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null 1413 && $preprocessing['params'] !== false) { 1414 self::exception(ZBX_API_ERROR_PARAMETERS, 1415 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty')) 1416 ); 1417 } 1418 1419 if ($preprocessing['type'] == ZBX_PREPROC_THROTTLE_VALUE) { 1420 if ($throttling) { 1421 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one throttling step is allowed.')); 1422 } 1423 else { 1424 $throttling = true; 1425 } 1426 } 1427 break; 1428 1429 case ZBX_PREPROC_DELTA_VALUE: 1430 case ZBX_PREPROC_DELTA_SPEED: 1431 case ZBX_PREPROC_XML_TO_JSON: 1432 // Check if 'params' is empty, because it must be empty. 1433 if (is_array($preprocessing['params'])) { 1434 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1435 } 1436 elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null 1437 && $preprocessing['params'] !== false) { 1438 self::exception(ZBX_API_ERROR_PARAMETERS, 1439 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty')) 1440 ); 1441 } 1442 1443 if ($preprocessing['type'] == ZBX_PREPROC_DELTA_VALUE 1444 || $preprocessing['type'] == ZBX_PREPROC_DELTA_SPEED) { 1445 // Check if one of the deltas (Delta per second or Delta value) already exists. 1446 if ($delta) { 1447 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one change step is allowed.')); 1448 } 1449 else { 1450 $delta = true; 1451 } 1452 } 1453 break; 1454 1455 case ZBX_PREPROC_THROTTLE_TIMED_VALUE: 1456 $api_input_rules = [ 1457 'type' => API_TIME_UNIT, 1458 'flags' => ($this instanceof CItem) 1459 ? API_NOT_EMPTY | API_ALLOW_USER_MACRO 1460 : API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 1461 'in' => '1:'.ZBX_MAX_TIMESHIFT 1462 ]; 1463 1464 if (!CApiInputValidator::validate($api_input_rules, $preprocessing['params'], 'params', 1465 $error)) { 1466 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1467 } 1468 1469 if ($throttling) { 1470 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one throttling step is allowed.')); 1471 } 1472 else { 1473 $throttling = true; 1474 } 1475 break; 1476 1477 case ZBX_PREPROC_PROMETHEUS_PATTERN: 1478 case ZBX_PREPROC_PROMETHEUS_TO_JSON: 1479 if ($prometheus) { 1480 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one Prometheus step is allowed.')); 1481 } 1482 1483 $prometheus = true; 1484 1485 if (is_array($preprocessing['params'])) { 1486 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1487 } 1488 1489 if ($preprocessing['type'] == ZBX_PREPROC_PROMETHEUS_PATTERN) { 1490 if ($preprocessing['params'] === '' || $preprocessing['params'] === null 1491 || $preprocessing['params'] === false) { 1492 self::exception(ZBX_API_ERROR_PARAMETERS, 1493 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) 1494 ); 1495 } 1496 1497 $params = explode("\n", $preprocessing['params']); 1498 1499 if ($params[0] === '') { 1500 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1501 'params', _('first parameter is expected') 1502 )); 1503 } 1504 1505 if ($prometheus_pattern_parser->parse($params[0]) != CParser::PARSE_SUCCESS) { 1506 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1507 'params', _('invalid Prometheus pattern') 1508 )); 1509 } 1510 1511 if ($params[1] !== '' 1512 && $prometheus_output_parser->parse($params[1]) != CParser::PARSE_SUCCESS) { 1513 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1514 'params', _('invalid Prometheus output') 1515 )); 1516 } 1517 } 1518 // Prometheus to JSON can be empty and has only one parameter. 1519 elseif ($preprocessing['params'] !== '') { 1520 if ($prometheus_pattern_parser->parse($preprocessing['params']) != CParser::PARSE_SUCCESS) { 1521 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1522 'params', _('invalid Prometheus pattern') 1523 )); 1524 } 1525 } 1526 break; 1527 1528 case ZBX_PREPROC_CSV_TO_JSON: 1529 if (is_array($preprocessing['params'])) { 1530 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1531 } 1532 elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null 1533 || $preprocessing['params'] === false) { 1534 self::exception(ZBX_API_ERROR_PARAMETERS, 1535 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) 1536 ); 1537 } 1538 1539 $params = explode("\n", $preprocessing['params']); 1540 1541 $params_cnt = count($params); 1542 if ($params_cnt > 3) { 1543 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1544 } 1545 elseif ($params_cnt == 1) { 1546 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1547 'params', _('second parameter is expected') 1548 )); 1549 } 1550 elseif ($params_cnt == 2) { 1551 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1552 'params', _('third parameter is expected') 1553 )); 1554 } 1555 else { 1556 // Correct amount of parameters, but check if they are valid. 1557 1558 if (mb_strlen($params[0]) > 1) { 1559 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1560 'params', _('value of first parameter is too long') 1561 )); 1562 } 1563 1564 if (mb_strlen($params[1]) > 1) { 1565 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1566 'params', _('value of second parameter is too long') 1567 )); 1568 } 1569 1570 $with_header_row_validator = new CLimitedSetValidator([ 1571 'values' => [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER] 1572 ]); 1573 1574 if (!$with_header_row_validator->validate($params[2])) { 1575 self::exception(ZBX_API_ERROR_PARAMETERS, 1576 _s('Incorrect value for field "%1$s": %2$s.', 'params', 1577 _s('value of third parameter must be one of %1$s', 1578 implode(', ', [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER]) 1579 ) 1580 ) 1581 ); 1582 } 1583 } 1584 break; 1585 1586 case ZBX_PREPROC_VALIDATE_NOT_SUPPORTED: 1587 // Check if 'params' is empty, because it must be empty. 1588 if (is_array($preprocessing['params'])) { 1589 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1590 } 1591 elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null 1592 && $preprocessing['params'] !== false) { 1593 self::exception(ZBX_API_ERROR_PARAMETERS, 1594 _s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty')) 1595 ); 1596 } 1597 1598 $preprocessing_types = array_column($item['preprocessing'], 'type'); 1599 1600 if (count(array_keys($preprocessing_types, ZBX_PREPROC_VALIDATE_NOT_SUPPORTED)) > 1) { 1601 self::exception(ZBX_API_ERROR_PARAMETERS, 1602 _('Only one not supported value check is allowed.') 1603 ); 1604 } 1605 break; 1606 } 1607 1608 switch ($preprocessing['type']) { 1609 case ZBX_PREPROC_RTRIM: 1610 case ZBX_PREPROC_LTRIM: 1611 case ZBX_PREPROC_TRIM: 1612 case ZBX_PREPROC_THROTTLE_VALUE: 1613 case ZBX_PREPROC_THROTTLE_TIMED_VALUE: 1614 case ZBX_PREPROC_SCRIPT: 1615 case ZBX_PREPROC_STR_REPLACE: 1616 if (is_array($preprocessing['error_handler'])) { 1617 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1618 } 1619 elseif ($preprocessing['error_handler'] != ZBX_PREPROC_FAIL_DEFAULT) { 1620 self::exception(ZBX_API_ERROR_PARAMETERS, 1621 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler', 1622 _s('unexpected value "%1$s"', $preprocessing['error_handler']) 1623 ) 1624 ); 1625 } 1626 1627 if (is_array($preprocessing['error_handler_params'])) { 1628 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1629 } 1630 elseif ($preprocessing['error_handler_params'] !== '' 1631 && $preprocessing['error_handler_params'] !== null 1632 && $preprocessing['error_handler_params'] !== false) { 1633 self::exception(ZBX_API_ERROR_PARAMETERS, 1634 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', 1635 _('should be empty') 1636 ) 1637 ); 1638 } 1639 break; 1640 1641 case ZBX_PREPROC_VALIDATE_NOT_SUPPORTED: 1642 if (is_array($preprocessing['error_handler'])) { 1643 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1644 } 1645 elseif (!$unsupported_error_handler_validator->validate($preprocessing['error_handler'])) { 1646 self::exception(ZBX_API_ERROR_PARAMETERS, 1647 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler', 1648 _s('unexpected value "%1$s"', $preprocessing['error_handler']) 1649 ) 1650 ); 1651 } 1652 1653 if (is_array($preprocessing['error_handler_params'])) { 1654 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1655 } 1656 elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DISCARD_VALUE 1657 && $preprocessing['error_handler_params'] !== '' 1658 && $preprocessing['error_handler_params'] !== null 1659 && $preprocessing['error_handler_params'] !== false) { 1660 self::exception(ZBX_API_ERROR_PARAMETERS, 1661 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', 1662 _('should be empty') 1663 ) 1664 ); 1665 } 1666 elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_SET_ERROR 1667 && ($preprocessing['error_handler_params'] === '' 1668 || $preprocessing['error_handler_params'] === null 1669 || $preprocessing['error_handler_params'] === false)) { 1670 self::exception(ZBX_API_ERROR_PARAMETERS, 1671 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', 1672 _('cannot be empty') 1673 ) 1674 ); 1675 } 1676 break; 1677 1678 default: 1679 if (is_array($preprocessing['error_handler'])) { 1680 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1681 } 1682 elseif (!$error_handler_validator->validate($preprocessing['error_handler'])) { 1683 self::exception(ZBX_API_ERROR_PARAMETERS, 1684 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler', 1685 _s('unexpected value "%1$s"', $preprocessing['error_handler']) 1686 ) 1687 ); 1688 } 1689 1690 if (is_array($preprocessing['error_handler_params'])) { 1691 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1692 } 1693 elseif (($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DEFAULT 1694 || $preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DISCARD_VALUE) 1695 && $preprocessing['error_handler_params'] !== '' 1696 && $preprocessing['error_handler_params'] !== null 1697 && $preprocessing['error_handler_params'] !== false) { 1698 self::exception(ZBX_API_ERROR_PARAMETERS, 1699 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', 1700 _('should be empty') 1701 ) 1702 ); 1703 } 1704 elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_SET_ERROR 1705 && ($preprocessing['error_handler_params'] === '' 1706 || $preprocessing['error_handler_params'] === null 1707 || $preprocessing['error_handler_params'] === false)) { 1708 self::exception(ZBX_API_ERROR_PARAMETERS, 1709 _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', 1710 _('cannot be empty') 1711 ) 1712 ); 1713 } 1714 } 1715 } 1716 } 1717 } 1718 1719 /** 1720 * Method validates preprocessing steps independently from other item properties. 1721 * 1722 * @param array $preprocessing_steps An array of item pre-processing step details. 1723 * See self::validateItemPreprocessing for details. 1724 * 1725 * @return bool|string 1726 */ 1727 public function validateItemPreprocessingSteps(array $preprocessing_steps) { 1728 try { 1729 $this->validateItemPreprocessing(['preprocessing' => $preprocessing_steps]); 1730 1731 return true; 1732 } 1733 catch (APIException $error) { 1734 return $error->getMessage(); 1735 } 1736 } 1737 1738 /** 1739 * Insert item pre-processing data into DB. 1740 * 1741 * @param array $items An array of items. 1742 * @param string $items[]['itemid'] 1743 * @param array $items[]['preprocessing'] An array of item pre-processing data. 1744 */ 1745 protected function createItemPreprocessing(array $items) { 1746 $item_preproc = []; 1747 1748 foreach ($items as $item) { 1749 if (array_key_exists('preprocessing', $item)) { 1750 $step = 1; 1751 1752 foreach ($item['preprocessing'] as $preprocessing) { 1753 $item_preproc[] = [ 1754 'itemid' => $item['itemid'], 1755 'step' => ($preprocessing['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) ? 0 : $step++, 1756 'type' => $preprocessing['type'], 1757 'params' => $preprocessing['params'], 1758 'error_handler' => $preprocessing['error_handler'], 1759 'error_handler_params' => $preprocessing['error_handler_params'] 1760 ]; 1761 } 1762 } 1763 } 1764 1765 if ($item_preproc) { 1766 DB::insertBatch('item_preproc', $item_preproc); 1767 } 1768 } 1769 1770 /** 1771 * Update item pre-processing data in DB. Delete old records and create new ones. 1772 * 1773 * @param array $items 1774 * @param string $items[]['itemid'] 1775 * @param array $items[]['preprocessing'] 1776 * @param int $items[]['preprocessing'][]['type'] 1777 * @param string $items[]['preprocessing'][]['params'] 1778 * @param int $items[]['preprocessing'][]['error_handler'] 1779 * @param string $items[]['preprocessing'][]['error_handler_params'] 1780 */ 1781 protected function updateItemPreprocessing(array $items) { 1782 $item_preprocs = []; 1783 1784 foreach ($items as $item) { 1785 if (array_key_exists('preprocessing', $item)) { 1786 $item_preprocs[$item['itemid']] = []; 1787 $step = 1; 1788 1789 foreach ($item['preprocessing'] as $item_preproc) { 1790 $curr_step = ($item_preproc['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) ? 0 : $step++; 1791 $item_preprocs[$item['itemid']][$curr_step] = [ 1792 'type' => $item_preproc['type'], 1793 'params' => $item_preproc['params'], 1794 'error_handler' => $item_preproc['error_handler'], 1795 'error_handler_params' => $item_preproc['error_handler_params'] 1796 ]; 1797 } 1798 } 1799 } 1800 1801 if (!$item_preprocs) { 1802 return; 1803 } 1804 1805 $ins_item_preprocs = []; 1806 $upd_item_preprocs = []; 1807 $del_item_preprocids = []; 1808 1809 $options = [ 1810 'output' => ['item_preprocid', 'itemid', 'step', 'type', 'params', 'error_handler', 'error_handler_params'], 1811 'filter' => ['itemid' => array_keys($item_preprocs)] 1812 ]; 1813 $db_item_preprocs = DBselect(DB::makeSql('item_preproc', $options)); 1814 1815 while ($db_item_preproc = DBfetch($db_item_preprocs)) { 1816 if (array_key_exists($db_item_preproc['step'], $item_preprocs[$db_item_preproc['itemid']])) { 1817 $item_preproc = $item_preprocs[$db_item_preproc['itemid']][$db_item_preproc['step']]; 1818 $upd_item_preproc = []; 1819 1820 if ($item_preproc['type'] != $db_item_preproc['type']) { 1821 $upd_item_preproc['type'] = $item_preproc['type']; 1822 } 1823 if ($item_preproc['params'] !== $db_item_preproc['params']) { 1824 $upd_item_preproc['params'] = $item_preproc['params']; 1825 } 1826 if ($item_preproc['error_handler'] != $db_item_preproc['error_handler']) { 1827 $upd_item_preproc['error_handler'] = $item_preproc['error_handler']; 1828 } 1829 if ($item_preproc['error_handler_params'] !== $db_item_preproc['error_handler_params']) { 1830 $upd_item_preproc['error_handler_params'] = $item_preproc['error_handler_params']; 1831 } 1832 1833 if ($upd_item_preproc) { 1834 $upd_item_preprocs[] = [ 1835 'values' => $upd_item_preproc, 1836 'where' => ['item_preprocid' => $db_item_preproc['item_preprocid']] 1837 ]; 1838 } 1839 unset($item_preprocs[$db_item_preproc['itemid']][$db_item_preproc['step']]); 1840 } 1841 else { 1842 $del_item_preprocids[] = $db_item_preproc['item_preprocid']; 1843 } 1844 } 1845 1846 foreach ($item_preprocs as $itemid => $preprocs) { 1847 foreach ($preprocs as $step => $preproc) { 1848 $ins_item_preprocs[] = [ 1849 'itemid' => $itemid, 1850 'step' => $step 1851 ] + $preproc; 1852 } 1853 } 1854 1855 if ($del_item_preprocids) { 1856 DB::delete('item_preproc', ['item_preprocid' => $del_item_preprocids]); 1857 } 1858 1859 if ($upd_item_preprocs) { 1860 DB::update('item_preproc', $upd_item_preprocs); 1861 } 1862 1863 if ($ins_item_preprocs) { 1864 DB::insertBatch('item_preproc', $ins_item_preprocs); 1865 } 1866 } 1867 1868 /** 1869 * Create item parameters. 1870 * 1871 * @param array $items Array of items. 1872 * @param array $items[]['parameters'] Item parameters. 1873 * @param array $items[]['parameters'][]['name'] Parameter name. 1874 * @param array $items[]['parameters'][]['value'] Parameter value. 1875 * @param array $itemids Array of item IDs that were created before. 1876 */ 1877 protected function createItemParameters(array $items, array $itemids): void { 1878 $item_parameters = []; 1879 1880 foreach ($items as $key => $item) { 1881 $items[$key]['itemid'] = $itemids[$key]; 1882 1883 if (!array_key_exists('parameters', $item) || !$item['parameters']) { 1884 continue; 1885 } 1886 1887 foreach ($item['parameters'] as $parameter) { 1888 $item_parameters[] = [ 1889 'itemid' => $items[$key]['itemid'], 1890 'name' => $parameter['name'], 1891 'value' => $parameter['value'] 1892 ]; 1893 } 1894 } 1895 1896 if ($item_parameters) { 1897 DB::insertBatch('item_parameter', $item_parameters); 1898 } 1899 } 1900 1901 /** 1902 * Update item parameters. 1903 * 1904 * @param array $items Array of items. 1905 * @param int|string $items[]['itemid'] Item ID. 1906 * @param int|string $items[]['type'] Item type. 1907 * @param array $items[]['parameters'] Item parameters. 1908 * @param array $items[]['parameters'][]['name'] Parameter name. 1909 * @param array $items[]['parameters'][]['value'] Parameter value. 1910 */ 1911 protected function updateItemParameters(array $items): void { 1912 $db_item_parameters_by_itemid = []; 1913 1914 foreach ($items as $item) { 1915 if ($item['type'] != ITEM_TYPE_SCRIPT || array_key_exists('parameters', $item)) { 1916 $db_item_parameters_by_itemid[$item['itemid']] = []; 1917 } 1918 } 1919 1920 if (!$db_item_parameters_by_itemid) { 1921 return; 1922 } 1923 1924 $options = [ 1925 'output' => ['item_parameterid', 'itemid', 'name', 'value'], 1926 'filter' => ['itemid' => array_keys($db_item_parameters_by_itemid)] 1927 ]; 1928 $result = DBselect(DB::makeSql('item_parameter', $options)); 1929 1930 while ($row = DBfetch($result)) { 1931 $db_item_parameters_by_itemid[$row['itemid']][$row['name']] = [ 1932 'item_parameterid' => $row['item_parameterid'], 1933 'value' => $row['value'] 1934 ]; 1935 } 1936 1937 $ins_item_parameters = []; 1938 $upd_item_parameters = []; 1939 $del_item_parameterids = []; 1940 1941 foreach ($db_item_parameters_by_itemid as $itemid => $db_item_parameters) { 1942 $item = $items[$itemid]; 1943 1944 if ($item['type'] == ITEM_TYPE_SCRIPT && array_key_exists('parameters', $item)) { 1945 foreach ($item['parameters'] as $parameter) { 1946 if (array_key_exists($parameter['name'], $db_item_parameters)) { 1947 if ($db_item_parameters[$parameter['name']]['value'] !== $parameter['value']) { 1948 $upd_item_parameters[] = [ 1949 'values' => ['value' => $parameter['value']], 1950 'where' => [ 1951 'item_parameterid' => $db_item_parameters[$parameter['name']]['item_parameterid'] 1952 ] 1953 ]; 1954 } 1955 unset($db_item_parameters[$parameter['name']]); 1956 } 1957 else { 1958 $ins_item_parameters[] = [ 1959 'itemid' => $itemid, 1960 'name' => $parameter['name'], 1961 'value' => $parameter['value'] 1962 ]; 1963 } 1964 } 1965 } 1966 1967 $del_item_parameterids = array_merge($del_item_parameterids, 1968 array_column($db_item_parameters, 'item_parameterid') 1969 ); 1970 } 1971 1972 if ($del_item_parameterids) { 1973 DB::delete('item_parameter', ['item_parameterid' => $del_item_parameterids]); 1974 } 1975 1976 if ($upd_item_parameters) { 1977 DB::update('item_parameter', $upd_item_parameters); 1978 } 1979 1980 if ($ins_item_parameters) { 1981 DB::insertBatch('item_parameter', $ins_item_parameters); 1982 } 1983 } 1984 1985 /** 1986 * Check if any item from list already exists. 1987 * If items have item ids it will check for existing item with different itemid. 1988 * 1989 * @throw APIException 1990 * 1991 * @param array $items 1992 */ 1993 protected function checkExistingItems(array $items) { 1994 $itemKeysByHostId = []; 1995 $itemIds = []; 1996 foreach ($items as $item) { 1997 if (!isset($itemKeysByHostId[$item['hostid']])) { 1998 $itemKeysByHostId[$item['hostid']] = []; 1999 } 2000 $itemKeysByHostId[$item['hostid']][] = $item['key_']; 2001 2002 if (isset($item['itemid'])) { 2003 $itemIds[] = $item['itemid']; 2004 } 2005 } 2006 2007 $sqlWhere = []; 2008 foreach ($itemKeysByHostId as $hostId => $keys) { 2009 $sqlWhere[] = '(i.hostid='.zbx_dbstr($hostId).' AND '.dbConditionString('i.key_', $keys).')'; 2010 } 2011 2012 if ($sqlWhere) { 2013 $sql = 'SELECT i.key_,h.host'. 2014 ' FROM items i,hosts h'. 2015 ' WHERE i.hostid=h.hostid AND ('.implode(' OR ', $sqlWhere).')'; 2016 2017 // if we update existing items we need to exclude them from result. 2018 if ($itemIds) { 2019 $sql .= ' AND '.dbConditionInt('i.itemid', $itemIds, true); 2020 } 2021 $dbItems = DBselect($sql, 1); 2022 while ($dbItem = DBfetch($dbItems)) { 2023 self::exception(ZBX_API_ERROR_PARAMETERS, 2024 _s('Item with key "%1$s" already exists on "%2$s".', $dbItem['key_'], $dbItem['host'])); 2025 } 2026 } 2027 } 2028 2029 protected function addRelatedObjects(array $options, array $result) { 2030 $result = parent::addRelatedObjects($options, $result); 2031 2032 // adding hosts 2033 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 2034 $relationMap = $this->createRelationMap($result, 'itemid', 'hostid'); 2035 $hosts = API::Host()->get([ 2036 'hostids' => $relationMap->getRelatedIds(), 2037 'templated_hosts' => true, 2038 'output' => $options['selectHosts'], 2039 'nopermissions' => true, 2040 'preservekeys' => true 2041 ]); 2042 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 2043 } 2044 2045 // adding preprocessing 2046 if ($options['selectPreprocessing'] !== null && $options['selectPreprocessing'] != API_OUTPUT_COUNT) { 2047 $db_item_preproc = API::getApiService()->select('item_preproc', [ 2048 'output' => $this->outputExtend($options['selectPreprocessing'], ['itemid', 'step']), 2049 'filter' => ['itemid' => array_keys($result)] 2050 ]); 2051 2052 CArrayHelper::sort($db_item_preproc, ['step']); 2053 2054 foreach ($result as &$item) { 2055 $item['preprocessing'] = []; 2056 } 2057 unset($item); 2058 2059 foreach ($db_item_preproc as $step) { 2060 $itemid = $step['itemid']; 2061 unset($step['item_preprocid'], $step['itemid'], $step['step']); 2062 2063 if (array_key_exists($itemid, $result)) { 2064 $result[$itemid]['preprocessing'][] = $step; 2065 } 2066 } 2067 } 2068 2069 // Add value mapping. 2070 if (($this instanceof CItemPrototype || $this instanceof CItem) && $options['selectValueMap'] !== null) { 2071 if ($options['selectValueMap'] === API_OUTPUT_EXTEND) { 2072 $options['selectValueMap'] = ['valuemapid', 'name', 'mappings']; 2073 } 2074 2075 foreach ($result as &$item) { 2076 $item['valuemap'] = []; 2077 } 2078 unset($item); 2079 2080 $valuemaps = DB::select('valuemap', [ 2081 'output' => array_diff($this->outputExtend($options['selectValueMap'], ['valuemapid', 'hostid']), 2082 ['mappings'] 2083 ), 2084 'filter' => ['valuemapid' => array_keys(array_flip(array_column($result, 'valuemapid')))], 2085 'preservekeys' => true 2086 ]); 2087 2088 if ($this->outputIsRequested('mappings', $options['selectValueMap']) && $valuemaps) { 2089 $params = [ 2090 'output' => ['valuemapid', 'type', 'value', 'newvalue'], 2091 'filter' => ['valuemapid' => array_keys($valuemaps)], 2092 'sortfield' => ['sortorder'] 2093 ]; 2094 $query = DBselect(DB::makeSql('valuemap_mapping', $params)); 2095 2096 while ($mapping = DBfetch($query)) { 2097 $valuemaps[$mapping['valuemapid']]['mappings'][] = [ 2098 'type' => $mapping['type'], 2099 'value' => $mapping['value'], 2100 'newvalue' => $mapping['newvalue'] 2101 ]; 2102 } 2103 } 2104 2105 foreach ($result as &$item) { 2106 if (array_key_exists('valuemapid', $item) && array_key_exists($item['valuemapid'], $valuemaps)) { 2107 $item['valuemap'] = array_intersect_key($valuemaps[$item['valuemapid']], 2108 array_flip($options['selectValueMap']) 2109 ); 2110 } 2111 } 2112 unset($item); 2113 } 2114 2115 if (!$options['countOutput'] && $this->outputIsRequested('parameters', $options['output'])) { 2116 $item_parameters = DBselect( 2117 'SELECT ip.itemid,ip.name,ip.value'. 2118 ' FROM item_parameter ip'. 2119 ' WHERE '.dbConditionInt('ip.itemid', array_keys($result)) 2120 ); 2121 2122 foreach ($result as &$item) { 2123 $item['parameters'] = []; 2124 } 2125 unset($item); 2126 2127 while ($row = DBfetch($item_parameters)) { 2128 $result[$row['itemid']]['parameters'][] = [ 2129 'name' => $row['name'], 2130 'value' => $row['value'] 2131 ]; 2132 } 2133 } 2134 2135 return $result; 2136 } 2137 2138 /** 2139 * Validate items with type ITEM_TYPE_DEPENDENT for create or update operation. 2140 * 2141 * @param array $items 2142 * @param string $items[]['itemid'] (mandatory for updated items and item prototypes) 2143 * @param string $items[]['hostid'] 2144 * @param int $items[]['type'] 2145 * @param string $items[]['master_itemid'] (mandatory for ITEM_TYPE_DEPENDENT) 2146 * @param int $items[]['flags'] (mandatory for items) 2147 * 2148 * @throws APIException for invalid data. 2149 */ 2150 protected function validateDependentItems(array $items) { 2151 $dep_items = []; 2152 $upd_itemids = []; 2153 2154 foreach ($items as $item) { 2155 if ($item['type'] == ITEM_TYPE_DEPENDENT) { 2156 if ($this instanceof CDiscoveryRule || $this instanceof CItemPrototype 2157 || $item['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) { 2158 $dep_items[] = $item; 2159 } 2160 2161 if (array_key_exists('itemid', $item)) { 2162 $upd_itemids[] = $item['itemid']; 2163 } 2164 } 2165 } 2166 2167 if (!$dep_items) { 2168 return; 2169 } 2170 2171 if ($this instanceof CItemPrototype && $upd_itemids) { 2172 $db_links = DBselect( 2173 'SELECT id.itemid,id.parent_itemid AS ruleid'. 2174 ' FROM item_discovery id'. 2175 ' WHERE '.dbConditionId('id.itemid', $upd_itemids) 2176 ); 2177 2178 $links = []; 2179 2180 while ($db_link = DBfetch($db_links)) { 2181 $links[$db_link['itemid']] = $db_link['ruleid']; 2182 } 2183 2184 foreach ($dep_items as &$dep_item) { 2185 if (array_key_exists('itemid', $dep_item)) { 2186 $dep_item['ruleid'] = $links[$dep_item['itemid']]; 2187 } 2188 } 2189 unset($dep_item); 2190 } 2191 2192 $master_itemids = []; 2193 2194 foreach ($dep_items as $dep_item) { 2195 $master_itemids[$dep_item['master_itemid']] = true; 2196 } 2197 2198 $master_items = []; 2199 2200 // Fill relations array by master items (item prototypes). Discovery rule should not be master item. 2201 do { 2202 if ($this instanceof CItemPrototype) { 2203 $db_master_items = DBselect( 2204 'SELECT i.itemid,i.hostid,i.master_itemid,i.flags,id.parent_itemid AS ruleid'. 2205 ' FROM items i'. 2206 ' LEFT JOIN item_discovery id'. 2207 ' ON i.itemid=id.itemid'. 2208 ' WHERE '.dbConditionId('i.itemid', array_keys($master_itemids)). 2209 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_PROTOTYPE]) 2210 ); 2211 } 2212 // CDiscoveryRule, CItem 2213 else { 2214 $db_master_items = DBselect( 2215 'SELECT i.itemid,i.hostid,i.master_itemid'. 2216 ' FROM items i'. 2217 ' WHERE '.dbConditionId('i.itemid', array_keys($master_itemids)). 2218 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL]) 2219 ); 2220 } 2221 2222 while ($db_master_item = DBfetch($db_master_items)) { 2223 $master_items[$db_master_item['itemid']] = $db_master_item; 2224 2225 unset($master_itemids[$db_master_item['itemid']]); 2226 } 2227 2228 if ($master_itemids) { 2229 reset($master_itemids); 2230 2231 self::exception(ZBX_API_ERROR_PERMISSIONS, 2232 _s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', 2233 _s('Item "%1$s" does not exist or you have no access to this item', key($master_itemids)) 2234 ) 2235 ); 2236 } 2237 2238 $master_itemids = []; 2239 2240 foreach ($master_items as $master_item) { 2241 if ($master_item['master_itemid'] != 0 2242 && !array_key_exists($master_item['master_itemid'], $master_items)) { 2243 $master_itemids[$master_item['master_itemid']] = true; 2244 } 2245 } 2246 } while ($master_itemids); 2247 2248 foreach ($dep_items as $dep_item) { 2249 $master_item = $master_items[$dep_item['master_itemid']]; 2250 2251 if ($dep_item['hostid'] != $master_item['hostid']) { 2252 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 2253 'master_itemid', _('hostid of dependent item and master item should match') 2254 )); 2255 } 2256 2257 if ($this instanceof CItemPrototype && $master_item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE 2258 && $dep_item['ruleid'] != $master_item['ruleid']) { 2259 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 2260 'master_itemid', _('ruleid of dependent item and master item should match') 2261 )); 2262 } 2263 2264 if (array_key_exists('itemid', $dep_item)) { 2265 $master_itemid = $dep_item['master_itemid']; 2266 2267 while ($master_itemid != 0) { 2268 if ($master_itemid == $dep_item['itemid']) { 2269 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 2270 'master_itemid', _('circular item dependency is not allowed') 2271 )); 2272 } 2273 2274 $master_itemid = $master_items[$master_itemid]['master_itemid']; 2275 } 2276 } 2277 } 2278 2279 // Fill relations array by dependent items (item prototypes). 2280 $root_itemids = []; 2281 2282 foreach ($master_items as $master_item) { 2283 if ($master_item['master_itemid'] == 0) { 2284 $root_itemids[] = $master_item['itemid']; 2285 } 2286 } 2287 2288 $dependent_items = []; 2289 2290 foreach ($dep_items as $dep_item) { 2291 if (array_key_exists('itemid', $dep_item)) { 2292 $dependent_items[$dep_item['master_itemid']][] = $dep_item['itemid']; 2293 } 2294 } 2295 2296 $master_itemids = $root_itemids; 2297 2298 do { 2299 $sql = 'SELECT i.master_itemid,i.itemid'. 2300 ' FROM items i'. 2301 ' WHERE '.dbConditionId('i.master_itemid', $master_itemids); 2302 if ($upd_itemids) { 2303 $sql .= ' AND '.dbConditionId('i.itemid', $upd_itemids, true); // Exclude updated items. 2304 } 2305 2306 $db_items = DBselect($sql); 2307 2308 while ($db_item = DBfetch($db_items)) { 2309 $dependent_items[$db_item['master_itemid']][] = $db_item['itemid']; 2310 } 2311 2312 $_master_itemids = $master_itemids; 2313 $master_itemids = []; 2314 2315 foreach ($_master_itemids as $master_itemid) { 2316 if (array_key_exists($master_itemid, $dependent_items)) { 2317 $master_itemids = array_merge($master_itemids, $dependent_items[$master_itemid]); 2318 } 2319 } 2320 } while ($master_itemids); 2321 2322 foreach ($dep_items as $dep_item) { 2323 if (!array_key_exists('itemid', $dep_item)) { 2324 $dependent_items[$dep_item['master_itemid']][] = false; 2325 } 2326 } 2327 2328 foreach ($root_itemids as $root_itemid) { 2329 self::checkDependencyDepth($dependent_items, $root_itemid); 2330 } 2331 } 2332 2333 /** 2334 * Validate depth and amount of elements in the tree of the dependent items. 2335 * 2336 * @param array $dependent_items 2337 * @param string $dependent_items[<master_itemid>][] List if the dependent item IDs ("false" for new items) 2338 * by master_itemid. 2339 * @param string $root_itemid ID of the item being checked. 2340 * @param int $level Current dependency level. 2341 * 2342 * @throws APIException for invalid data. 2343 */ 2344 private static function checkDependencyDepth(array $dependent_items, $root_itemid, $level = 0) { 2345 $count = 0; 2346 2347 if (array_key_exists($root_itemid, $dependent_items)) { 2348 if (++$level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) { 2349 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 2350 'master_itemid', _('maximum number of dependency levels reached') 2351 )); 2352 } 2353 2354 foreach ($dependent_items[$root_itemid] as $master_itemid) { 2355 $count++; 2356 2357 if ($master_itemid !== false) { 2358 $count += self::checkDependencyDepth($dependent_items, $master_itemid, $level); 2359 } 2360 } 2361 2362 if ($count > ZBX_DEPENDENT_ITEM_MAX_COUNT) { 2363 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 2364 'master_itemid', _('maximum dependent items count reached') 2365 )); 2366 } 2367 } 2368 2369 return $count; 2370 } 2371 2372 /** 2373 * Converts headers field text to hash with header name as key. 2374 * 2375 * @param string $headers Headers string, one header per line, line delimiter "\r\n". 2376 * 2377 * @return array 2378 */ 2379 protected function headersStringToArray($headers) { 2380 $result = []; 2381 2382 foreach (explode("\r\n", $headers) as $header) { 2383 $header = explode(': ', $header, 2); 2384 2385 if (count($header) == 2) { 2386 $result[$header[0]] = $header[1]; 2387 } 2388 } 2389 2390 return $result; 2391 } 2392 2393 /** 2394 * Converts headers fields hash to string. 2395 * 2396 * @param array $headers Array of headers where key is header name. 2397 * 2398 * @return string 2399 */ 2400 protected function headersArrayToString(array $headers) { 2401 $result = []; 2402 2403 foreach ($headers as $k => $v) { 2404 $result[] = $k.': '.$v; 2405 } 2406 2407 return implode("\r\n", $result); 2408 } 2409 2410 /** 2411 * Validate item with type ITEM_TYPE_HTTPAGENT. 2412 * 2413 * @param array $item Array of item fields. 2414 * @param array $db_item Array of item database fields for update action or empty array for create action. 2415 * 2416 * @throws APIException for invalid data. 2417 */ 2418 protected function validateHTTPCheck(array $item, array $db_item) { 2419 $rules = [ 2420 'timeout' => [ 2421 'type' => API_TIME_UNIT, 'flags' => ($this instanceof CItemPrototype) 2422 ? API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO 2423 : API_NOT_EMPTY | API_ALLOW_USER_MACRO, 2424 'in' => '1:'.SEC_PER_MIN 2425 ], 2426 'url' => [ 2427 'type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 2428 'length' => DB::getFieldLength('items', 'url') 2429 ], 2430 'status_codes' => [ 2431 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'status_codes') 2432 ], 2433 'follow_redirects' => [ 2434 'type' => API_INT32, 2435 'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON]) 2436 ], 2437 'post_type' => [ 2438 'type' => API_INT32, 2439 'in' => implode(',', [ZBX_POSTTYPE_RAW, ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML]) 2440 ], 2441 'http_proxy' => [ 2442 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'http_proxy') 2443 ], 2444 'headers' => [ 2445 'type' => API_STRINGS_UTF8 2446 ], 2447 'retrieve_mode' => [ 2448 'type' => API_INT32, 2449 'in' => implode(',', [ 2450 HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, 2451 HTTPTEST_STEP_RETRIEVE_MODE_BOTH 2452 ]) 2453 ], 2454 'request_method' => [ 2455 'type' => API_INT32, 2456 'in' => implode(',', [ 2457 HTTPCHECK_REQUEST_GET, HTTPCHECK_REQUEST_POST, HTTPCHECK_REQUEST_PUT, HTTPCHECK_REQUEST_HEAD 2458 ]) 2459 ], 2460 'output_format' => [ 2461 'type' => API_INT32, 2462 'in' => implode(',', [HTTPCHECK_STORE_RAW, HTTPCHECK_STORE_JSON]) 2463 ], 2464 'allow_traps' => [ 2465 'type' => API_INT32, 2466 'in' => implode(',', [HTTPCHECK_ALLOW_TRAPS_OFF, HTTPCHECK_ALLOW_TRAPS_ON]) 2467 ], 2468 'ssl_cert_file' => [ 2469 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_cert_file') 2470 ], 2471 'ssl_key_file' => [ 2472 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_key_file') 2473 ], 2474 'ssl_key_password' => [ 2475 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_key_password') 2476 ], 2477 'verify_peer' => [ 2478 'type' => API_INT32, 2479 'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON]) 2480 ], 2481 'verify_host' => [ 2482 'type' => API_INT32, 2483 'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON]) 2484 ], 2485 'authtype' => [ 2486 'type' => API_INT32, 2487 'in' => implode(',', [ 2488 HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS, 2489 HTTPTEST_AUTH_DIGEST 2490 ]) 2491 ] 2492 ]; 2493 2494 $data = $item + $db_item; 2495 2496 if (array_key_exists('authtype', $data) 2497 && ($data['authtype'] == HTTPTEST_AUTH_BASIC || $data['authtype'] == HTTPTEST_AUTH_NTLM 2498 || $data['authtype'] == HTTPTEST_AUTH_KERBEROS || $data['authtype'] == HTTPTEST_AUTH_DIGEST)) { 2499 $rules += [ 2500 'username' => [ 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'username')], 2501 'password' => [ 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'password')] 2502 ]; 2503 } 2504 2505 // Strict validation for 'retrieve_mode' only for create action. 2506 if (array_key_exists('request_method', $data) && $data['request_method'] == HTTPCHECK_REQUEST_HEAD 2507 && array_key_exists('retrieve_mode', $item)) { 2508 $rules['retrieve_mode']['in'] = (string) HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; 2509 } 2510 2511 if (array_key_exists('post_type', $data) 2512 && ($data['post_type'] == ZBX_POSTTYPE_JSON || $data['post_type'] == ZBX_POSTTYPE_XML)) { 2513 $rules['posts'] = [ 2514 'type' => API_STRING_UTF8, 2515 'length' => DB::getFieldLength('items', 'posts') 2516 ]; 2517 } 2518 2519 if (array_key_exists('templateid', $data) && $data['templateid']) { 2520 $rules['interfaceid'] = [ 2521 'type' => API_ID, 'flags' => API_REQUIRED | API_NOT_EMPTY 2522 ]; 2523 } 2524 2525 if (array_key_exists('trapper_hosts', $item) && $item['trapper_hosts'] !== '' 2526 && (!array_key_exists('allow_traps', $data) || $data['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF)) { 2527 self::exception(ZBX_API_ERROR_PARAMETERS, 2528 _s('Incorrect value for field "%1$s": %2$s.', 'trapper_hosts', _('should be empty')) 2529 ); 2530 } 2531 2532 // Keep values only for fields with defined validation rules. 2533 $data = array_intersect_key($data, $rules); 2534 2535 if (!CApiInputValidator::validate(['type' => API_OBJECT, 'fields' => $rules], $data, '', $error)) { 2536 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 2537 } 2538 2539 if (array_key_exists('query_fields', $item)) { 2540 if (!is_array($item['query_fields'])) { 2541 self::exception(ZBX_API_ERROR_PARAMETERS, 2542 _s('Invalid parameter "%1$s": %2$s.', 'query_fields', _('an array is expected')) 2543 ); 2544 } 2545 2546 foreach ($item['query_fields'] as $v) { 2547 if (!is_array($v) || count($v) > 1 || key($v) === '') { 2548 self::exception(ZBX_API_ERROR_PARAMETERS, 2549 _s('Invalid parameter "%1$s": %2$s.', 'query_fields', _('nonempty key and value pair expected')) 2550 ); 2551 } 2552 } 2553 2554 if (strlen(json_encode($item['query_fields'])) > DB::getFieldLength('items', 'query_fields')) { 2555 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 'query_fields', 2556 _('cannot convert to JSON, result value too long') 2557 )); 2558 } 2559 } 2560 2561 if (array_key_exists('headers', $item)) { 2562 if (!is_array($item['headers'])) { 2563 self::exception(ZBX_API_ERROR_PARAMETERS, 2564 _s('Invalid parameter "%1$s": %2$s.', 'headers', _('an array is expected')) 2565 ); 2566 } 2567 2568 foreach ($item['headers'] as $k => $v) { 2569 if (trim($k) === '' || !is_string($v) || $v === '') { 2570 self::exception(ZBX_API_ERROR_PARAMETERS, 2571 _s('Invalid parameter "%1$s": %2$s.', 'headers', _('nonempty key and value pair expected')) 2572 ); 2573 } 2574 } 2575 } 2576 2577 if (array_key_exists('status_codes', $item) && $item['status_codes']) { 2578 $ranges_parser = new CRangesParser([ 2579 'usermacros' => true, 2580 'lldmacros' => ($this instanceof CItemPrototype) 2581 ]); 2582 2583 if ($ranges_parser->parse($item['status_codes']) != CParser::PARSE_SUCCESS) { 2584 self::exception(ZBX_API_ERROR_PARAMETERS, 2585 _s('Incorrect value "%1$s" for "%2$s" field.', $item['status_codes'], 'status_codes') 2586 ); 2587 } 2588 } 2589 2590 if ((array_key_exists('post_type', $item) || array_key_exists('posts', $item)) 2591 && ($data['post_type'] == ZBX_POSTTYPE_JSON || $data['post_type'] == ZBX_POSTTYPE_XML)) { 2592 $posts = array_key_exists('posts', $data) ? $data['posts'] : ''; 2593 libxml_use_internal_errors(true); 2594 2595 if ($data['post_type'] == ZBX_POSTTYPE_XML 2596 && simplexml_load_string($posts, null, LIBXML_IMPORT_FLAGS) === false) { 2597 $errors = libxml_get_errors(); 2598 libxml_clear_errors(); 2599 2600 if (!$errors) { 2601 self::exception(ZBX_API_ERROR_PARAMETERS, 2602 _s('Invalid parameter "%1$s": %2$s.', 'posts', _('XML is expected')) 2603 ); 2604 } 2605 else { 2606 $error = reset($errors); 2607 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 'posts', 2608 _s('%1$s [Line: %2$s | Column: %3$s]', '('.$error->code.') '.trim($error->message), 2609 $error->line, $error->column 2610 ))); 2611 } 2612 } 2613 2614 if ($data['post_type'] == ZBX_POSTTYPE_JSON) { 2615 if (trim($posts, " \r\n") === '') { 2616 self::exception(ZBX_API_ERROR_PARAMETERS, 2617 _s('Invalid parameter "%1$s": %2$s.', 'posts', _('JSON is expected')) 2618 ); 2619 } 2620 2621 $types = [ 2622 'usermacros' => true, 2623 'macros_n' => [ 2624 '{HOST.IP}', '{HOST.CONN}', '{HOST.DNS}', '{HOST.HOST}', '{HOST.NAME}', '{ITEM.ID}', 2625 '{ITEM.KEY}' 2626 ] 2627 ]; 2628 2629 if ($this instanceof CItemPrototype) { 2630 $types['lldmacros'] = true; 2631 } 2632 2633 $matches = (new CMacrosResolverGeneral)->getMacroPositions($posts, $types); 2634 2635 $shift = 0; 2636 2637 foreach ($matches as $pos => $substr) { 2638 $posts = substr_replace($posts, '1', $pos + $shift, strlen($substr)); 2639 $shift = $shift + 1 - strlen($substr); 2640 } 2641 2642 json_decode($posts); 2643 2644 if (json_last_error()) { 2645 self::exception(ZBX_API_ERROR_PARAMETERS, 2646 _s('Invalid parameter "%1$s": %2$s.', 'posts', _('JSON is expected')) 2647 ); 2648 } 2649 } 2650 } 2651 } 2652 2653 /** 2654 * Remove NCLOB value type fields from resulting query SELECT part if DISTINCT will be used. 2655 * 2656 * @param string $table_name Table name. 2657 * @param string $table_alias Table alias value. 2658 * @param array $options Array of query options. 2659 * @param array $sql_parts Array of query parts already initialized from $options. 2660 * 2661 * @return array The resulting SQL parts array. 2662 */ 2663 protected function applyQueryOutputOptions($table_name, $table_alias, array $options, array $sql_parts) { 2664 if (!$options['countOutput'] && self::dbDistinct($sql_parts)) { 2665 $schema = $this->getTableSchema(); 2666 $nclob_fields = []; 2667 2668 foreach ($schema['fields'] as $field_name => $field) { 2669 if ($field['type'] == DB::FIELD_TYPE_NCLOB 2670 && $this->outputIsRequested($field_name, $options['output'])) { 2671 $nclob_fields[] = $field_name; 2672 } 2673 } 2674 2675 if ($nclob_fields) { 2676 $output = ($options['output'] === API_OUTPUT_EXTEND) 2677 ? array_keys($schema['fields']) 2678 : $options['output']; 2679 2680 $options['output'] = array_diff($output, $nclob_fields); 2681 } 2682 } 2683 2684 return parent::applyQueryOutputOptions($table_name, $table_alias, $options, $sql_parts); 2685 } 2686 2687 /** 2688 * Add NCLOB type fields if there was DISTINCT in query. 2689 * 2690 * @param array $options Array of query options. 2691 * @param array $result Query results. 2692 * 2693 * @return array The result array with added NCLOB fields. 2694 */ 2695 protected function addNclobFieldValues(array $options, array $result): array { 2696 $schema = $this->getTableSchema(); 2697 $nclob_fields = []; 2698 2699 foreach ($schema['fields'] as $field_name => $field) { 2700 if ($field['type'] == DB::FIELD_TYPE_NCLOB && $this->outputIsRequested($field_name, $options['output'])) { 2701 $nclob_fields[] = $field_name; 2702 } 2703 } 2704 2705 if (!$nclob_fields) { 2706 return $result; 2707 } 2708 2709 $pk = $schema['key']; 2710 $options = [ 2711 'output' => $nclob_fields, 2712 'filter' => [$pk => array_keys($result)] 2713 ]; 2714 2715 $db_items = DBselect(DB::makeSql($this->tableName, $options)); 2716 2717 while ($db_item = DBfetch($db_items)) { 2718 $result[$db_item[$pk]] += $db_item; 2719 } 2720 2721 return $result; 2722 } 2723 2724 /** 2725 * Update item tags. 2726 * 2727 * @param array $items 2728 * @param string $items[]['itemid'] 2729 * @param array $items[]['tags'] 2730 * @param string $items[]['tags'][]['tag'] 2731 * @param string $items[]['tags'][]['value'] 2732 */ 2733 protected function updateItemTags(array $items): void { 2734 $items = array_filter($items, function ($item) { 2735 return array_key_exists('tags', $item); 2736 }); 2737 2738 // Select tags from database. 2739 $db_tags = DBselect( 2740 'SELECT itemtagid, itemid, tag, value'. 2741 ' FROM item_tag'. 2742 ' WHERE '.dbConditionInt('itemid', array_keys($items)) 2743 ); 2744 2745 array_walk($items, function (&$item) { 2746 $item['db_tags'] = []; 2747 }); 2748 2749 while ($db_tag = DBfetch($db_tags)) { 2750 $items[$db_tag['itemid']]['db_tags'][] = $db_tag; 2751 } 2752 2753 // Find which tags must be added/deleted. 2754 $new_tags = []; 2755 $del_tagids = []; 2756 foreach ($items as $item) { 2757 CArrayHelper::sort($item['tags'], ['tag', 'value']); 2758 2759 foreach ($item['db_tags'] as $del_tag_key => $tag_delete) { 2760 foreach ($item['tags'] as $new_tag_key => $tag_add) { 2761 if ($tag_delete['tag'] === $tag_add['tag'] && $tag_delete['value'] === $tag_add['value']) { 2762 unset($item['db_tags'][$del_tag_key], $item['tags'][$new_tag_key]); 2763 continue 2; 2764 } 2765 } 2766 } 2767 2768 $del_tagids = array_merge($del_tagids, array_column($item['db_tags'], 'itemtagid')); 2769 2770 foreach ($item['tags'] as $tag_add) { 2771 $tag_add['itemid'] = $item['itemid']; 2772 $new_tags[] = $tag_add; 2773 } 2774 } 2775 2776 if ($del_tagids) { 2777 DB::delete('item_tag', ['itemtagid' => $del_tagids]); 2778 } 2779 if ($new_tags) { 2780 DB::insert('item_tag', $new_tags); 2781 } 2782 } 2783 2784 /** 2785 * Record item tags into database. 2786 * 2787 * @param array $items 2788 * @param array $items[]['tags'] 2789 * @param string $items[]['tags'][]['tag'] 2790 * @param string $items[]['tags'][]['value'] 2791 * @param int $items[]['itemid'] 2792 */ 2793 protected function createItemTags(array $items): void { 2794 $new_tags = []; 2795 foreach ($items as $key => $item) { 2796 if (array_key_exists('tags', $item)) { 2797 foreach ($item['tags'] as $tag) { 2798 $tag['itemid'] = $item['itemid']; 2799 $new_tags[] = $tag; 2800 } 2801 } 2802 } 2803 2804 if ($new_tags) { 2805 DB::insert('item_tag', $new_tags); 2806 } 2807 } 2808 2809 /** 2810 * Check that valuemap belong to same host as item. 2811 * 2812 * @param array $items 2813 */ 2814 protected function validateValueMaps(array $items): void { 2815 $valuemapids_by_hostid = []; 2816 2817 foreach ($items as $item) { 2818 if (array_key_exists('valuemapid', $item) && $item['valuemapid'] != 0) { 2819 $valuemapids_by_hostid[$item['hostid']][$item['valuemapid']] = true; 2820 } 2821 } 2822 2823 $sql_where = []; 2824 foreach ($valuemapids_by_hostid as $hostid => $valuemapids) { 2825 $sql_where[] = '(vm.hostid='.zbx_dbstr($hostid).' AND '. 2826 dbConditionId('vm.valuemapid', array_keys($valuemapids)).')'; 2827 } 2828 2829 if ($sql_where) { 2830 $result = DBselect( 2831 'SELECT vm.valuemapid,vm.hostid'. 2832 ' FROM valuemap vm'. 2833 ' WHERE '.implode(' OR ', $sql_where) 2834 ); 2835 while ($row = DBfetch($result)) { 2836 unset($valuemapids_by_hostid[$row['hostid']][$row['valuemapid']]); 2837 2838 if (!$valuemapids_by_hostid[$row['hostid']]) { 2839 unset($valuemapids_by_hostid[$row['hostid']]); 2840 } 2841 } 2842 2843 if ($valuemapids_by_hostid) { 2844 $hostid = key($valuemapids_by_hostid); 2845 $valuemapid = key($valuemapids_by_hostid[$hostid]); 2846 2847 $host_row = DBfetch(DBselect('SELECT h.host FROM hosts h WHERE h.hostid='.zbx_dbstr($hostid))); 2848 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Valuemap with ID "%1$s" is not available on "%2$s".', 2849 $valuemapid, $host_row['host'] 2850 )); 2851 } 2852 } 2853 } 2854} 2855