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 * 25 * @package API 26 */ 27abstract class CItemGeneral extends CApiService { 28 29 const ERROR_EXISTS_TEMPLATE = 'existsTemplate'; 30 const ERROR_EXISTS = 'exists'; 31 const ERROR_NO_INTERFACE = 'noInterface'; 32 const ERROR_INVALID_KEY = 'invalidKey'; 33 34 protected $fieldRules; 35 36 /** 37 * @abstract 38 * 39 * @param array $options 40 * 41 * @return array 42 */ 43 abstract public function get($options = []); 44 45 public function __construct() { 46 parent::__construct(); 47 48 // template - if templated item, value is taken from template item, cannot be changed on host 49 // system - values should not be updated 50 // host - value should be null for template items 51 $this->fieldRules = [ 52 'type' => ['template' => 1], 53 'snmp_community' => [], 54 'snmp_oid' => ['template' => 1], 55 'hostid' => [], 56 'name' => ['template' => 1], 57 'description' => [], 58 'key_' => ['template' => 1], 59 'delay' => [], 60 'history' => [], 61 'trends' => [], 62 'status' => [], 63 'value_type' => ['template' => 1], 64 'trapper_hosts' => [], 65 'units' => ['template' => 1], 66 'multiplier' => ['template' => 1], 67 'delta' => ['template' => 1], 68 'snmpv3_contextname' => [], 69 'snmpv3_securityname' => [], 70 'snmpv3_securitylevel' => [], 71 'snmpv3_authprotocol' => [], 72 'snmpv3_authpassphrase' => [], 73 'snmpv3_privprotocol' => [], 74 'snmpv3_privpassphrase' => [], 75 'formula' => ['template' => 1], 76 'error' => ['system' => 1], 77 'lastlogsize' => ['system' => 1], 78 'logtimefmt' => [], 79 'templateid' => ['system' => 1], 80 'valuemapid' => ['template' => 1], 81 'delay_flex' => [], 82 'params' => [], 83 'ipmi_sensor' => ['template' => 1], 84 'data_type' => ['template' => 1], 85 'authtype' => [], 86 'username' => [], 87 'password' => [], 88 'publickey' => [], 89 'privatekey' => [], 90 'mtime' => ['system' => 1], 91 'flags' => [], 92 'filter' => [], 93 'interfaceid' => ['host' => 1], 94 'port' => [], 95 'inventory_link' => [], 96 'lifetime' => [] 97 ]; 98 99 $this->errorMessages = array_merge($this->errorMessages, [ 100 self::ERROR_NO_INTERFACE => _('Cannot find host interface on "%1$s" for item key "%2$s".') 101 ]); 102 } 103 104 /** 105 * Check items data. 106 * 107 * Any system field passed to the function will be unset. 108 * 109 * @throw APIException 110 * 111 * @param array $items passed by reference 112 * @param bool $update 113 * 114 * @return void 115 */ 116 protected function checkInput(array &$items, $update = false) { 117 if ($update) { 118 $itemDbFields = ['itemid' => null]; 119 120 $dbItemsFields = ['itemid', 'templateid']; 121 foreach ($this->fieldRules as $field => $rule) { 122 if (!isset($rule['system'])) { 123 $dbItemsFields[] = $field; 124 } 125 } 126 127 $dbItems = $this->get([ 128 'output' => $dbItemsFields, 129 'itemids' => zbx_objectValues($items, 'itemid'), 130 'editable' => true, 131 'preservekeys' => true 132 ]); 133 134 $dbHosts = API::Host()->get([ 135 'output' => ['hostid', 'status', 'name'], 136 'hostids' => zbx_objectValues($dbItems, 'hostid'), 137 'templated_hosts' => true, 138 'editable' => true, 139 'selectApplications' => ['applicationid', 'flags'], 140 'preservekeys' => true 141 ]); 142 } 143 else { 144 $itemDbFields = [ 145 'name' => null, 146 'key_' => null, 147 'hostid' => null, 148 'type' => null, 149 'value_type' => null, 150 'delay' => '0', 151 'delay_flex' => '' 152 ]; 153 154 $dbHosts = API::Host()->get([ 155 'output' => ['hostid', 'status', 'name'], 156 'hostids' => zbx_objectValues($items, 'hostid'), 157 'templated_hosts' => true, 158 'editable' => true, 159 'selectApplications' => ['applicationid', 'flags'], 160 'preservekeys' => true 161 ]); 162 } 163 164 // interfaces 165 $interfaces = API::HostInterface()->get([ 166 'output' => ['interfaceid', 'hostid', 'type'], 167 'hostids' => zbx_objectValues($dbHosts, 'hostid'), 168 'nopermissions' => true, 169 'preservekeys' => true 170 ]); 171 172 if ($update) { 173 $updateDiscoveredValidator = new CUpdateDiscoveredValidator([ 174 'allowed' => ['itemid', 'status'], 175 'messageAllowedField' => _('Cannot update "%2$s" for a discovered item "%1$s".') 176 ]); 177 foreach ($items as $item) { 178 // check permissions 179 if (!isset($dbItems[$item['itemid']])) { 180 self::exception(ZBX_API_ERROR_PARAMETERS, 181 _('No permissions to referred object or it does not exist!')); 182 } 183 184 $dbItem = $dbItems[$item['itemid']]; 185 186 $itemName = isset($item['name']) ? $item['name'] : $dbItem['name']; 187 188 // discovered fields, except status, cannot be updated 189 $updateDiscoveredValidator->setObjectName($itemName); 190 $this->checkPartialValidator($item, $updateDiscoveredValidator, $dbItem); 191 } 192 193 $items = $this->extendObjects($this->tableName(), $items, ['name', 'flags']); 194 } 195 196 $item_key_parser = new CItemKey(); 197 198 foreach ($items as $inum => &$item) { 199 $item = $this->clearValues($item); 200 201 $fullItem = $items[$inum]; 202 203 if (!check_db_fields($itemDbFields, $item)) { 204 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 205 } 206 207 if ($update) { 208 check_db_fields($dbItems[$item['itemid']], $fullItem); 209 210 $this->checkNoParameters( 211 $item, 212 ['templateid', 'state'], 213 _('Cannot update "%1$s" for item "%2$s".'), 214 $item['name'] 215 ); 216 217 // apply rules 218 foreach ($this->fieldRules as $field => $rules) { 219 if ((0 != $fullItem['templateid'] && isset($rules['template'])) || isset($rules['system'])) { 220 unset($item[$field]); 221 222 // For templated item and fields that should not be modified, use the value from DB. 223 if (array_key_exists($field, $dbItems[$item['itemid']]) 224 && array_key_exists($field, $fullItem)) { 225 $fullItem[$field] = $dbItems[$item['itemid']][$field]; 226 } 227 } 228 } 229 230 if (!isset($item['key_'])) { 231 $item['key_'] = $fullItem['key_']; 232 } 233 if (!isset($item['hostid'])) { 234 $item['hostid'] = $fullItem['hostid']; 235 } 236 237 // if a templated item is being assigned to an interface with a different type, ignore it 238 $itemInterfaceType = itemTypeInterface($dbItems[$item['itemid']]['type']); 239 if ($fullItem['templateid'] && isset($item['interfaceid']) && isset($interfaces[$item['interfaceid']]) 240 && $itemInterfaceType !== INTERFACE_TYPE_ANY && $interfaces[$item['interfaceid']]['type'] != $itemInterfaceType) { 241 242 unset($item['interfaceid']); 243 } 244 } 245 else { 246 if (!isset($dbHosts[$item['hostid']])) { 247 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 248 } 249 250 check_db_fields($itemDbFields, $fullItem); 251 252 $this->checkNoParameters( 253 $item, 254 ['templateid', 'state'], 255 _('Cannot set "%1$s" for item "%2$s".'), 256 $item['name'] 257 ); 258 } 259 260 $host = $dbHosts[$fullItem['hostid']]; 261 262 if ($fullItem['type'] == ITEM_TYPE_ZABBIX_ACTIVE) { 263 $item['delay_flex'] = ''; 264 $fullItem['delay_flex'] = ''; 265 } 266 if ($fullItem['value_type'] == ITEM_VALUE_TYPE_STR) { 267 $item['delta'] = 0; 268 } 269 if ($fullItem['value_type'] != ITEM_VALUE_TYPE_UINT64) { 270 $item['data_type'] = 0; 271 } 272 273 // For non-numeric types, whichever value was entered in trends field, is overwritten to zero. 274 if ($fullItem['value_type'] == ITEM_VALUE_TYPE_STR || $fullItem['value_type'] == ITEM_VALUE_TYPE_LOG 275 || $fullItem['value_type'] == ITEM_VALUE_TYPE_TEXT) { 276 $item['trends'] = 0; 277 } 278 279 // check if the item requires an interface 280 $itemInterfaceType = itemTypeInterface($fullItem['type']); 281 if ($itemInterfaceType !== false && $host['status'] != HOST_STATUS_TEMPLATE) { 282 if (!$fullItem['interfaceid']) { 283 self::exception(ZBX_API_ERROR_PARAMETERS, _('No interface found.')); 284 } 285 elseif (!isset($interfaces[$fullItem['interfaceid']]) || bccomp($interfaces[$fullItem['interfaceid']]['hostid'], $fullItem['hostid']) != 0) { 286 self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses host interface from non-parent host.')); 287 } 288 elseif ($itemInterfaceType !== INTERFACE_TYPE_ANY && $interfaces[$fullItem['interfaceid']]['type'] != $itemInterfaceType) { 289 self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses incorrect interface type.')); 290 } 291 } 292 // no interface required, just set it to null 293 else { 294 $item['interfaceid'] = 0; 295 } 296 297 // item key 298 if ($fullItem['type'] == ITEM_TYPE_DB_MONITOR) { 299 if (!isset($fullItem['flags']) || $fullItem['flags'] != ZBX_FLAG_DISCOVERY_RULE) { 300 if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR) == 0) { 301 self::exception(ZBX_API_ERROR_PARAMETERS, 302 _('Check the key, please. Default example was passed.') 303 ); 304 } 305 } 306 elseif ($fullItem['flags'] == ZBX_FLAG_DISCOVERY_RULE) { 307 if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR_DISCOVERY) == 0) { 308 self::exception(ZBX_API_ERROR_PARAMETERS, 309 _('Check the key, please. Default example was passed.') 310 ); 311 } 312 } 313 } 314 elseif (($fullItem['type'] == ITEM_TYPE_SSH && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_SSH) == 0) 315 || ($fullItem['type'] == ITEM_TYPE_TELNET && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_TELNET) == 0) 316 || ($fullItem['type'] == ITEM_TYPE_JMX && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_JMX) == 0)) { 317 self::exception(ZBX_API_ERROR_PARAMETERS, _('Check the key, please. Default example was passed.')); 318 } 319 320 // key 321 if ($item_key_parser->parse($fullItem['key_']) != CParser::PARSE_SUCCESS) { 322 self::exception(ZBX_API_ERROR_PARAMETERS, 323 _params($this->getErrorMsg(self::ERROR_INVALID_KEY), [ 324 $fullItem['key_'], $fullItem['name'], $host['name'], $item_key_parser->getError() 325 ]) 326 ); 327 } 328 329 // parameters 330 if ($fullItem['type'] == ITEM_TYPE_AGGREGATE) { 331 $params_num = $item_key_parser->getParamsNum(); 332 333 if (!str_in_array($item_key_parser->getKey(), ['grpmax', 'grpmin', 'grpsum', 'grpavg']) 334 || $params_num > 4 || $params_num < 3 335 || ($params_num == 3 && $item_key_parser->getParam(2) !== 'last') 336 || !str_in_array($item_key_parser->getParam(2), ['last', 'min', 'max', 'avg', 'sum', 'count'])) { 337 self::exception(ZBX_API_ERROR_PARAMETERS, 338 _s('Key "%1$s" does not match <grpmax|grpmin|grpsum|grpavg>["Host group(s)", "Item key",'. 339 ' "<last|min|max|avg|sum|count>", "parameter"].', $item_key_parser->getKey())); 340 } 341 } 342 343 // type of information 344 if ($fullItem['type'] == ITEM_TYPE_AGGREGATE && $fullItem['value_type'] != ITEM_VALUE_TYPE_UINT64 345 && $fullItem['value_type'] != ITEM_VALUE_TYPE_FLOAT) { 346 self::exception(ZBX_API_ERROR_PARAMETERS, 347 _('Type of information must be "Numeric (unsigned)" or "Numeric (float)" for aggregate items.')); 348 } 349 350 // update interval 351 if ($fullItem['type'] != ITEM_TYPE_TRAPPER && $fullItem['type'] != ITEM_TYPE_SNMPTRAP) { 352 // delay must be between 0 and 86400, if delay is 0, delay_flex interval must be set. 353 if ($fullItem['delay'] < 0 || $fullItem['delay'] > SEC_PER_DAY 354 || ($fullItem['delay'] == 0 && $fullItem['delay_flex'] === '')) { 355 self::exception(ZBX_API_ERROR_PARAMETERS, 356 _('Item will not be refreshed. Please enter a correct update interval.') 357 ); 358 } 359 360 // Don't parse empty strings, they will not be valid. 361 if ($fullItem['delay_flex'] !== '') { 362 // Validate item delay_flex string. First check syntax with parser, then validate time ranges. 363 $item_delay_flex_parser = new CItemDelayFlexParser($fullItem['delay_flex']); 364 365 if ($item_delay_flex_parser->isValid()) { 366 $delay_flex_validator = new CItemDelayFlexValidator(); 367 368 if ($delay_flex_validator->validate($item_delay_flex_parser->getIntervals())) { 369 // Some valid intervals exist at this point. 370 $flexible_intervals = $item_delay_flex_parser->getFlexibleIntervals(); 371 372 // If there are no flexible intervals, skip the next check calculation. 373 if (!$flexible_intervals) { 374 continue; 375 } 376 377 $nextCheck = calculateItemNextCheck(0, $fullItem['delay'], 378 $item_delay_flex_parser->getFlexibleIntervals($flexible_intervals), 379 time() 380 ); 381 382 if ($nextCheck == ZBX_JAN_2038) { 383 self::exception(ZBX_API_ERROR_PARAMETERS, 384 _('Item will not be refreshed. Please enter a correct update interval.') 385 ); 386 } 387 } 388 else { 389 self::exception(ZBX_API_ERROR_PARAMETERS, $delay_flex_validator->getError()); 390 } 391 } 392 else { 393 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid interval "%1$s": %2$s.', 394 $fullItem['delay_flex'], 395 $item_delay_flex_parser->getError()) 396 ); 397 } 398 } 399 } 400 401 // ssh, telnet 402 if ($fullItem['type'] == ITEM_TYPE_SSH || $fullItem['type'] == ITEM_TYPE_TELNET) { 403 if (zbx_empty($fullItem['username'])) { 404 self::exception(ZBX_API_ERROR_PARAMETERS, _('No authentication user name specified.')); 405 } 406 407 if ($fullItem['type'] == ITEM_TYPE_SSH && $fullItem['authtype'] == ITEM_AUTHTYPE_PUBLICKEY) { 408 if (zbx_empty($fullItem['publickey'])) { 409 self::exception(ZBX_API_ERROR_PARAMETERS, _('No public key file specified.')); 410 } 411 if (zbx_empty($fullItem['privatekey'])) { 412 self::exception(ZBX_API_ERROR_PARAMETERS, _('No private key file specified.')); 413 } 414 } 415 } 416 417 // snmp trap 418 if ($fullItem['type'] == ITEM_TYPE_SNMPTRAP 419 && $fullItem['key_'] !== 'snmptrap.fallback' && $item_key_parser->getKey() !== 'snmptrap') { 420 self::exception(ZBX_API_ERROR_PARAMETERS, _('SNMP trap key is invalid.')); 421 } 422 423 // snmp oid 424 if ((in_array($fullItem['type'], [ITEM_TYPE_SNMPV1, ITEM_TYPE_SNMPV2C, ITEM_TYPE_SNMPV3])) 425 && zbx_empty($fullItem['snmp_oid'])) { 426 self::exception(ZBX_API_ERROR_PARAMETERS, _('No SNMP OID specified.')); 427 } 428 429 // snmp community 430 if (in_array($fullItem['type'], [ITEM_TYPE_SNMPV1, ITEM_TYPE_SNMPV2C]) 431 && zbx_empty($fullItem['snmp_community'])) { 432 self::exception(ZBX_API_ERROR_PARAMETERS, _('No SNMP community specified.')); 433 } 434 435 // snmp port 436 if (isset($fullItem['port']) && !zbx_empty($fullItem['port']) && !validatePortNumberOrMacro($fullItem['port'])) { 437 self::exception(ZBX_API_ERROR_PARAMETERS, 438 _s('Item "%1$s:%2$s" has invalid port: "%3$s".', $fullItem['name'], $fullItem['key_'], $fullItem['port'])); 439 } 440 441 if (isset($fullItem['snmpv3_securitylevel']) && $fullItem['snmpv3_securitylevel'] != ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV) { 442 // snmpv3 authprotocol 443 if (str_in_array($fullItem['snmpv3_securitylevel'], [ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV, ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV])) { 444 if (isset($fullItem['snmpv3_authprotocol']) && (zbx_empty($fullItem['snmpv3_authprotocol']) 445 || !str_in_array($fullItem['snmpv3_authprotocol'], 446 [ITEM_AUTHPROTOCOL_MD5, ITEM_AUTHPROTOCOL_SHA]))) { 447 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect authentication protocol for item "%1$s".', $fullItem['name'])); 448 } 449 } 450 451 // snmpv3 privprotocol 452 if ($fullItem['snmpv3_securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV) { 453 if (isset($fullItem['snmpv3_privprotocol']) && (zbx_empty($fullItem['snmpv3_privprotocol']) 454 || !str_in_array($fullItem['snmpv3_privprotocol'], 455 [ITEM_PRIVPROTOCOL_DES, ITEM_PRIVPROTOCOL_AES]))) { 456 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect privacy protocol for item "%1$s".', $fullItem['name'])); 457 } 458 } 459 } 460 461 if (isset($item['applications']) && $item['applications']) { 462 /* 463 * 'flags' is available for update and item prototypes. 464 * Don't allow discovered or any other application types for item prototypes in 'applications' option. 465 */ 466 if (array_key_exists('flags', $fullItem) && $fullItem['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) { 467 foreach ($host['applications'] as $num => $application) { 468 if ($application['flags'] != ZBX_FLAG_DISCOVERY_NORMAL) { 469 unset($host['applications'][$num]); 470 } 471 } 472 } 473 474 // check that the given applications belong to the item's host 475 $dbApplicationIds = zbx_objectValues($host['applications'], 'applicationid'); 476 foreach ($item['applications'] as $appId) { 477 if (!in_array($appId, $dbApplicationIds)) { 478 $error = _s('Application with ID "%1$s" is not available on "%2$s".', $appId, $host['name']); 479 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 480 } 481 } 482 } 483 484 $this->checkSpecificFields($fullItem); 485 } 486 unset($item); 487 488 $this->checkExistingItems($items); 489 } 490 491 protected function checkSpecificFields(array $item) { 492 return true; 493 } 494 495 protected function clearValues(array $item) { 496 if (isset($item['port']) && $item['port'] != '') { 497 $item['port'] = ltrim($item['port'], '0'); 498 if ($item['port'] == '') { 499 $item['port'] = 0; 500 } 501 } 502 503 if (isset($item['lifetime']) && $item['lifetime'] != '') { 504 $item['lifetime'] = ltrim($item['lifetime'], '0'); 505 if ($item['lifetime'] == '') { 506 $item['lifetime'] = 0; 507 } 508 } 509 510 return $item; 511 } 512 513 protected function errorInheritFlags($flag, $key, $host) { 514 switch ($flag) { 515 case ZBX_FLAG_DISCOVERY_NORMAL: 516 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item.', $key, $host)); 517 break; 518 case ZBX_FLAG_DISCOVERY_RULE: 519 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as a discovery rule.', $key, $host)); 520 break; 521 case ZBX_FLAG_DISCOVERY_PROTOTYPE: 522 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item prototype.', $key, $host)); 523 break; 524 case ZBX_FLAG_DISCOVERY_CREATED: 525 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)); 526 break; 527 default: 528 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as unknown item element.', $key, $host)); 529 } 530 } 531 532 /** 533 * Returns the interface that best matches the given item. 534 * 535 * @param array $itemType An item 536 * @param array $interfaces An array of interfaces to choose from 537 * 538 * @return array|boolean The best matching interface; 539 * an empty array of no matching interface was found; 540 * false, if the item does not need an interface 541 */ 542 public static function findInterfaceForItem(array $item, array $interfaces) { 543 $typeInterface = []; 544 foreach ($interfaces as $interface) { 545 if ($interface['main'] == 1) { 546 $typeInterface[$interface['type']] = $interface; 547 } 548 } 549 550 // find item interface type 551 $type = itemTypeInterface($item['type']); 552 553 $matchingInterface = []; 554 555 // the item can use any interface 556 if ($type == INTERFACE_TYPE_ANY) { 557 $interfaceTypes = [ 558 INTERFACE_TYPE_AGENT, 559 INTERFACE_TYPE_SNMP, 560 INTERFACE_TYPE_JMX, 561 INTERFACE_TYPE_IPMI 562 ]; 563 foreach ($interfaceTypes as $itype) { 564 if (isset($typeInterface[$itype])) { 565 $matchingInterface = $typeInterface[$itype]; 566 break; 567 } 568 } 569 } 570 // the item uses a specific type of interface 571 elseif ($type !== false) { 572 $matchingInterface = (isset($typeInterface[$type])) ? $typeInterface[$type] : []; 573 } 574 // the item does not need an interface 575 else { 576 $matchingInterface = false; 577 } 578 579 return $matchingInterface; 580 } 581 582 public function isReadable($ids) { 583 if (!is_array($ids)) { 584 return false; 585 } 586 if (empty($ids)) { 587 return true; 588 } 589 590 $ids = array_unique($ids); 591 592 $count = $this->get([ 593 'itemids' => $ids, 594 'countOutput' => true 595 ]); 596 597 return (count($ids) == $count); 598 } 599 600 public function isWritable($ids) { 601 if (!is_array($ids)) { 602 return false; 603 } 604 if (empty($ids)) { 605 return true; 606 } 607 608 $ids = array_unique($ids); 609 610 $count = $this->get([ 611 'itemids' => $ids, 612 'editable' => true, 613 'countOutput' => true 614 ]); 615 616 return (count($ids) == $count); 617 } 618 619 /** 620 * Checks whether the given items are referenced by any graphs and tries to 621 * unset these references, if they are no longer used. 622 * 623 * @throws APIException if at least one of the item can't be deleted 624 * 625 * @param array $itemids An array of item IDs 626 */ 627 protected function checkGraphReference(array $itemids) { 628 $this->checkUseInGraphAxis($itemids, true); 629 $this->checkUseInGraphAxis($itemids); 630 } 631 632 /** 633 * Checks if any of the given items are used as min/max Y values in a graph. 634 * 635 * if there are graphs, that have an y*_itemid column set, but the 636 * y*_type column is not set to GRAPH_YAXIS_TYPE_ITEM_VALUE, the y*_itemid 637 * column will be set to NULL. 638 * 639 * If the $checkMax parameter is set to true, the items will be checked against 640 * max Y values, otherwise, they will be checked against min Y values. 641 * 642 * @throws APIException if any of the given items are used as min/max Y values in a graph. 643 * 644 * @param array $itemids An array of items IDs 645 * @param bool $check_max 646 */ 647 protected function checkUseInGraphAxis(array $itemids, $check_max = false) { 648 $field_name_itemid = $check_max ? 'ymax_itemid' : 'ymin_itemid'; 649 $field_name_type = $check_max ? 'ymax_type' : 'ymin_type'; 650 $error = $check_max 651 ? 'Could not delete these items because some of them are used as MAX values for graphs.' 652 : 'Could not delete these items because some of them are used as MIN values for graphs.'; 653 654 $result = DBselect( 655 'SELECT g.graphid,g.'.$field_name_type. 656 ' FROM graphs g'. 657 ' WHERE '.dbConditionInt('g.'.$field_name_itemid, $itemids) 658 ); 659 660 $update_graphs = []; 661 662 while ($row = DBfetch($result)) { 663 // check if Y type is actually set to GRAPH_YAXIS_TYPE_ITEM_VALUE 664 if ($row[$field_name_type] == GRAPH_YAXIS_TYPE_ITEM_VALUE) { 665 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 666 } 667 else { 668 $update_graphs[] = [ 669 'values' => [$field_name_itemid => 0], 670 'where' => ['graphid' => $row['graphid']] 671 ]; 672 } 673 } 674 unset($graph); 675 676 // if there are graphs, that have an y*_itemid column set, but the 677 // y*_type column is not set to GRAPH_YAXIS_TYPE_ITEM_VALUE, set y*_itemid to NULL. 678 // Otherwise we won't be able to delete them. 679 if ($update_graphs) { 680 DB::update('graphs', $update_graphs); 681 } 682 } 683 684 /** 685 * Updates the children of the item on the given hosts and propagates the inheritance to the child hosts. 686 * 687 * @abstract 688 * 689 * @param array $items an array of items to inherit 690 * @param array|null $hostids an array of hosts to inherit to; if set to null, the children will be updated on all 691 * child hosts 692 * 693 * @return bool 694 */ 695 abstract protected function inherit(array $items, array $hostids = null); 696 697 /** 698 * Prepares and returns an array of child items, inherited from items $itemsToInherit on the given hosts. 699 * 700 * @param array $itemsToInherit 701 * @param array|null $hostIds 702 * 703 * @return array an array of unsaved child items 704 */ 705 protected function prepareInheritedItems(array $itemsToInherit, array $hostIds = null) { 706 // fetch all child hosts 707 $chdHosts = API::Host()->get([ 708 'output' => ['hostid', 'host', 'status'], 709 'selectParentTemplates' => ['templateid'], 710 'selectInterfaces' => API_OUTPUT_EXTEND, 711 'templateids' => zbx_objectValues($itemsToInherit, 'hostid'), 712 'hostids' => $hostIds, 713 'preservekeys' => true, 714 'nopermissions' => true, 715 'templated_hosts' => true 716 ]); 717 if (empty($chdHosts)) { 718 return []; 719 } 720 721 $newItems = []; 722 foreach ($chdHosts as $hostId => $host) { 723 $templateids = zbx_toHash($host['parentTemplates'], 'templateid'); 724 725 // skip items not from parent templates of current host 726 $parentItems = []; 727 foreach ($itemsToInherit as $inum => $parentItem) { 728 if (isset($templateids[$parentItem['hostid']])) { 729 $parentItems[$inum] = $parentItem; 730 } 731 } 732 733 // check existing items to decide insert or update 734 $exItems = API::Item()->get([ 735 'output' => ['itemid', 'type', 'key_', 'flags', 'templateid'], 736 'hostids' => $hostId, 737 'preservekeys' => true, 738 'nopermissions' => true, 739 'filter' => ['flags' => null] 740 ]); 741 742 $exItemsKeys = zbx_toHash($exItems, 'key_'); 743 $exItemsTpl = zbx_toHash($exItems, 'templateid'); 744 745 $itemids_with_application_prototypes = []; 746 747 foreach ($parentItems as $parentItem) { 748 if (isset($parentItem['applicationPrototypes']) && is_array($parentItem['applicationPrototypes']) 749 && !array_key_exists('ruleid', $parentItem)) { 750 $itemids_with_application_prototypes[$parentItem['itemid']] = true; 751 } 752 } 753 754 if ($itemids_with_application_prototypes) { 755 $discovery_rules = DBfetchArray(DBselect( 756 'SELECT id.itemid,id.parent_itemid'. 757 ' FROM item_discovery id'. 758 ' WHERE '.dbConditionInt('id.itemid', array_keys($itemids_with_application_prototypes)) 759 )); 760 $discovery_rules = zbx_toHash($discovery_rules, 'itemid'); 761 } 762 763 foreach ($parentItems as $parentItem) { 764 $exItem = null; 765 766 // check if an item of a different type with the same key exists 767 if (isset($exItemsKeys[$parentItem['key_']])) { 768 $exItem = $exItemsKeys[$parentItem['key_']]; 769 if ($exItem['flags'] != $parentItem['flags']) { 770 $this->errorInheritFlags($exItem['flags'], $exItem['key_'], $host['host']); 771 } 772 } 773 774 // update by templateid 775 if (isset($exItemsTpl[$parentItem['itemid']])) { 776 $exItem = $exItemsTpl[$parentItem['itemid']]; 777 778 if (isset($exItemsKeys[$parentItem['key_']]) 779 && !idcmp($exItemsKeys[$parentItem['key_']]['templateid'], $parentItem['itemid'])) { 780 self::exception( 781 ZBX_API_ERROR_PARAMETERS, 782 _params($this->getErrorMsg(self::ERROR_EXISTS), [$parentItem['key_'], $host['host']]) 783 ); 784 } 785 } 786 787 // update by key 788 if (isset($exItemsKeys[$parentItem['key_']])) { 789 $exItem = $exItemsKeys[$parentItem['key_']]; 790 791 if ($exItem['templateid'] > 0 && !idcmp($exItem['templateid'], $parentItem['itemid'])) { 792 793 self::exception( 794 ZBX_API_ERROR_PARAMETERS, 795 _params($this->getErrorMsg(self::ERROR_EXISTS_TEMPLATE), [ 796 $parentItem['key_'], 797 $host['host'] 798 ]) 799 ); 800 } 801 } 802 803 if ($host['status'] == HOST_STATUS_TEMPLATE || !isset($parentItem['type'])) { 804 unset($parentItem['interfaceid']); 805 } 806 elseif ((isset($parentItem['type']) && isset($exItem) && $parentItem['type'] != $exItem['type']) || !isset($exItem)) { 807 $interface = self::findInterfaceForItem($parentItem, $host['interfaces']); 808 809 if (!empty($interface)) { 810 $parentItem['interfaceid'] = $interface['interfaceid']; 811 } 812 elseif ($interface !== false) { 813 self::exception( 814 ZBX_API_ERROR_PARAMETERS, 815 _params($this->getErrorMsg(self::ERROR_NO_INTERFACE), [ 816 $host['host'], 817 $parentItem['key_'] 818 ]) 819 ); 820 } 821 } 822 else { 823 unset($parentItem['interfaceid']); 824 } 825 826 // copying item 827 $newItem = $parentItem; 828 $newItem['hostid'] = $host['hostid']; 829 $newItem['templateid'] = $parentItem['itemid']; 830 831 // setting item application 832 if (isset($parentItem['applications'])) { 833 $newItem['applications'] = get_same_applications_for_host($parentItem['applications'], $host['hostid']); 834 } 835 836 if ($parentItem['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE 837 && array_key_exists('applicationPrototypes', $parentItem)) { 838 839 // Get discovery rule ID for current item prototype, if it is not yet set. 840 if (array_key_exists('ruleid', $parentItem)) { 841 $discovery_ruleid = $parentItem['ruleid']; 842 } 843 else { 844 $discovery_ruleid = $discovery_rules[$parentItem['itemid']]['parent_itemid']; 845 } 846 847 $newItem['applicationPrototypes'] = []; 848 849 $db_application_prototypes = DBfetchArray(DBselect( 850 'SELECT ap.application_prototypeid,ap.name'. 851 ' FROM application_prototype ap'. 852 ' WHERE ap.itemid='.zbx_dbstr($discovery_ruleid). 853 ' AND '.dbConditionString('ap.name', 854 zbx_objectValues($parentItem['applicationPrototypes'], 'name') 855 ) 856 )); 857 858 $db_application_prototypes = zbx_toHash($db_application_prototypes, 'name'); 859 860 foreach ($parentItem['applicationPrototypes'] as $application_prototype) { 861 $db_application_prototype = $db_application_prototypes[$application_prototype['name']]; 862 863 $newItem['applicationPrototypes'][] = [ 864 'name' => $application_prototype['name'], 865 'templateid' => $db_application_prototype['application_prototypeid'] 866 ]; 867 } 868 } 869 870 if ($exItem) { 871 $newItem['itemid'] = $exItem['itemid']; 872 } 873 else { 874 unset($newItem['itemid']); 875 } 876 $newItems[] = $newItem; 877 } 878 } 879 880 return $newItems; 881 } 882 883 /** 884 * Check if any item from list already exists. 885 * If items have item ids it will check for existing item with different itemid. 886 * 887 * @throw APIException 888 * 889 * @param array $items 890 */ 891 protected function checkExistingItems(array $items) { 892 $itemKeysByHostId = []; 893 $itemIds = []; 894 foreach ($items as $item) { 895 if (!isset($itemKeysByHostId[$item['hostid']])) { 896 $itemKeysByHostId[$item['hostid']] = []; 897 } 898 $itemKeysByHostId[$item['hostid']][] = $item['key_']; 899 900 if (isset($item['itemid'])) { 901 $itemIds[] = $item['itemid']; 902 } 903 } 904 905 $sqlWhere = []; 906 foreach ($itemKeysByHostId as $hostId => $keys) { 907 $sqlWhere[] = '(i.hostid='.zbx_dbstr($hostId).' AND '.dbConditionString('i.key_', $keys).')'; 908 } 909 910 if ($sqlWhere) { 911 $sql = 'SELECT i.key_,h.host'. 912 ' FROM items i,hosts h'. 913 ' WHERE i.hostid=h.hostid AND ('.implode(' OR ', $sqlWhere).')'; 914 915 // if we update existing items we need to exclude them from result. 916 if ($itemIds) { 917 $sql .= ' AND '.dbConditionInt('i.itemid', $itemIds, true); 918 } 919 $dbItems = DBselect($sql, 1); 920 while ($dbItem = DBfetch($dbItems)) { 921 self::exception(ZBX_API_ERROR_PARAMETERS, 922 _s('Item with key "%1$s" already exists on "%2$s".', $dbItem['key_'], $dbItem['host'])); 923 } 924 } 925 } 926 927 protected function addRelatedObjects(array $options, array $result) { 928 $result = parent::addRelatedObjects($options, $result); 929 930 // adding hosts 931 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 932 $relationMap = $this->createRelationMap($result, 'itemid', 'hostid'); 933 $hosts = API::Host()->get([ 934 'hostids' => $relationMap->getRelatedIds(), 935 'templated_hosts' => true, 936 'output' => $options['selectHosts'], 937 'nopermissions' => true, 938 'preservekeys' => true 939 ]); 940 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 941 } 942 943 return $result; 944 } 945} 946