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