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 items. 24 */ 25class CItem extends CItemGeneral { 26 27 protected $tableName = 'items'; 28 protected $tableAlias = 'i'; 29 protected $sortColumns = ['itemid', 'name', 'key_', 'delay', 'history', 'trends', 'type', 'status']; 30 31 public function __construct() { 32 parent::__construct(); 33 34 $this->errorMessages = array_merge($this->errorMessages, [ 35 self::ERROR_EXISTS_TEMPLATE => _('Item "%1$s" already exists on "%2$s", inherited from another template.'), 36 self::ERROR_EXISTS => _('Item "%1$s" already exists on "%2$s".'), 37 self::ERROR_INVALID_KEY => _('Invalid key "%1$s" for item "%2$s" on "%3$s": %4$s.') 38 ]); 39 } 40 41 /** 42 * Get items data. 43 * 44 * @param array $options 45 * @param array $options['itemids'] 46 * @param array $options['hostids'] 47 * @param array $options['groupids'] 48 * @param array $options['triggerids'] 49 * @param array $options['applicationids'] 50 * @param bool $options['status'] 51 * @param bool $options['templated_items'] 52 * @param bool $options['editable'] 53 * @param bool $options['count'] 54 * @param string $options['pattern'] 55 * @param int $options['limit'] 56 * @param string $options['order'] 57 * 58 * @return array|int item data as array or false if error 59 */ 60 public function get($options = []) { 61 $result = []; 62 63 $sqlParts = [ 64 'select' => ['items' => 'i.itemid'], 65 'from' => ['items' => 'items i'], 66 'where' => ['webtype' => 'i.type<>'.ITEM_TYPE_HTTPTEST, 'flags' => 'i.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'], 67 'group' => [], 68 'order' => [], 69 'limit' => null 70 ]; 71 72 $defOptions = [ 73 'groupids' => null, 74 'templateids' => null, 75 'hostids' => null, 76 'proxyids' => null, 77 'itemids' => null, 78 'interfaceids' => null, 79 'graphids' => null, 80 'triggerids' => null, 81 'applicationids' => null, 82 'webitems' => null, 83 'inherited' => null, 84 'templated' => null, 85 'monitored' => null, 86 'editable' => false, 87 'nopermissions' => null, 88 'group' => null, 89 'host' => null, 90 'application' => null, 91 'with_triggers' => null, 92 // filter 93 'filter' => null, 94 'search' => null, 95 'searchByAny' => null, 96 'startSearch' => false, 97 'excludeSearch' => false, 98 'searchWildcardsEnabled' => null, 99 // output 100 'output' => API_OUTPUT_EXTEND, 101 'selectHosts' => null, 102 'selectInterfaces' => null, 103 'selectTriggers' => null, 104 'selectGraphs' => null, 105 'selectApplications' => null, 106 'selectDiscoveryRule' => null, 107 'selectItemDiscovery' => null, 108 'selectPreprocessing' => null, 109 'countOutput' => false, 110 'groupCount' => false, 111 'preservekeys' => false, 112 'sortfield' => '', 113 'sortorder' => '', 114 'limit' => null, 115 'limitSelects' => null 116 ]; 117 $options = zbx_array_merge($defOptions, $options); 118 119 // editable + permission check 120 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 121 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 122 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 123 124 $sqlParts['where'][] = 'EXISTS ('. 125 'SELECT NULL'. 126 ' FROM hosts_groups hgg'. 127 ' JOIN rights r'. 128 ' ON r.id=hgg.groupid'. 129 ' AND '.dbConditionInt('r.groupid', $userGroups). 130 ' WHERE i.hostid=hgg.hostid'. 131 ' GROUP BY hgg.hostid'. 132 ' HAVING MIN(r.permission)>'.PERM_DENY. 133 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 134 ')'; 135 } 136 137 // itemids 138 if (!is_null($options['itemids'])) { 139 zbx_value2array($options['itemids']); 140 141 $sqlParts['where']['itemid'] = dbConditionInt('i.itemid', $options['itemids']); 142 } 143 144 // templateids 145 if (!is_null($options['templateids'])) { 146 zbx_value2array($options['templateids']); 147 148 if (!is_null($options['hostids'])) { 149 zbx_value2array($options['hostids']); 150 $options['hostids'] = array_merge($options['hostids'], $options['templateids']); 151 } 152 else { 153 $options['hostids'] = $options['templateids']; 154 } 155 } 156 157 // hostids 158 if (!is_null($options['hostids'])) { 159 zbx_value2array($options['hostids']); 160 161 $sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['hostids']); 162 163 if ($options['groupCount']) { 164 $sqlParts['group']['i'] = 'i.hostid'; 165 } 166 } 167 168 // interfaceids 169 if (!is_null($options['interfaceids'])) { 170 zbx_value2array($options['interfaceids']); 171 172 $sqlParts['where']['interfaceid'] = dbConditionId('i.interfaceid', $options['interfaceids']); 173 174 if ($options['groupCount']) { 175 $sqlParts['group']['i'] = 'i.interfaceid'; 176 } 177 } 178 179 // groupids 180 if (!is_null($options['groupids'])) { 181 zbx_value2array($options['groupids']); 182 183 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 184 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 185 $sqlParts['where'][] = 'hg.hostid=i.hostid'; 186 187 if ($options['groupCount']) { 188 $sqlParts['group']['hg'] = 'hg.groupid'; 189 } 190 } 191 192 // proxyids 193 if (!is_null($options['proxyids'])) { 194 zbx_value2array($options['proxyids']); 195 196 $sqlParts['from']['hosts'] = 'hosts h'; 197 $sqlParts['where'][] = dbConditionId('h.proxy_hostid', $options['proxyids']); 198 $sqlParts['where'][] = 'h.hostid=i.hostid'; 199 200 if ($options['groupCount']) { 201 $sqlParts['group']['h'] = 'h.proxy_hostid'; 202 } 203 } 204 205 // triggerids 206 if (!is_null($options['triggerids'])) { 207 zbx_value2array($options['triggerids']); 208 209 $sqlParts['from']['functions'] = 'functions f'; 210 $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); 211 $sqlParts['where']['if'] = 'i.itemid=f.itemid'; 212 } 213 214 // applicationids 215 if (!is_null($options['applicationids'])) { 216 zbx_value2array($options['applicationids']); 217 218 $sqlParts['from']['items_applications'] = 'items_applications ia'; 219 $sqlParts['where'][] = dbConditionInt('ia.applicationid', $options['applicationids']); 220 $sqlParts['where']['ia'] = 'ia.itemid=i.itemid'; 221 } 222 223 // graphids 224 if (!is_null($options['graphids'])) { 225 zbx_value2array($options['graphids']); 226 227 $sqlParts['from']['graphs_items'] = 'graphs_items gi'; 228 $sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']); 229 $sqlParts['where']['igi'] = 'i.itemid=gi.itemid'; 230 } 231 232 // webitems 233 if (!is_null($options['webitems'])) { 234 unset($sqlParts['where']['webtype']); 235 } 236 237 // inherited 238 if (!is_null($options['inherited'])) { 239 if ($options['inherited']) { 240 $sqlParts['where'][] = 'i.templateid IS NOT NULL'; 241 } 242 else { 243 $sqlParts['where'][] = 'i.templateid IS NULL'; 244 } 245 } 246 247 // templated 248 if (!is_null($options['templated'])) { 249 $sqlParts['from']['hosts'] = 'hosts h'; 250 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 251 252 if ($options['templated']) { 253 $sqlParts['where'][] = 'h.status='.HOST_STATUS_TEMPLATE; 254 } 255 else { 256 $sqlParts['where'][] = 'h.status<>'.HOST_STATUS_TEMPLATE; 257 } 258 } 259 260 // monitored 261 if (!is_null($options['monitored'])) { 262 $sqlParts['from']['hosts'] = 'hosts h'; 263 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 264 265 if ($options['monitored']) { 266 $sqlParts['where'][] = 'h.status='.HOST_STATUS_MONITORED; 267 $sqlParts['where'][] = 'i.status='.ITEM_STATUS_ACTIVE; 268 } 269 else { 270 $sqlParts['where'][] = '(h.status<>'.HOST_STATUS_MONITORED.' OR i.status<>'.ITEM_STATUS_ACTIVE.')'; 271 } 272 } 273 274 // search 275 if (is_array($options['search'])) { 276 zbx_db_search('items i', $options, $sqlParts); 277 } 278 279 // filter 280 if (is_array($options['filter'])) { 281 if (array_key_exists('delay', $options['filter']) && $options['filter']['delay'] !== null) { 282 $sqlParts['where'][] = makeUpdateIntervalFilter('i.delay', $options['filter']['delay']); 283 unset($options['filter']['delay']); 284 } 285 286 if (array_key_exists('history', $options['filter']) && $options['filter']['history'] !== null) { 287 $options['filter']['history'] = getTimeUnitFilters($options['filter']['history']); 288 } 289 290 if (array_key_exists('trends', $options['filter']) && $options['filter']['trends'] !== null) { 291 $options['filter']['trends'] = getTimeUnitFilters($options['filter']['trends']); 292 } 293 294 $this->dbFilter('items i', $options, $sqlParts); 295 296 if (isset($options['filter']['host'])) { 297 zbx_value2array($options['filter']['host']); 298 299 $sqlParts['from']['hosts'] = 'hosts h'; 300 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 301 $sqlParts['where']['h'] = dbConditionString('h.host', $options['filter']['host'], false, true); 302 } 303 304 if (array_key_exists('flags', $options['filter']) && 305 (is_null($options['filter']['flags']) || !zbx_empty($options['filter']['flags']))) { 306 unset($sqlParts['where']['flags']); 307 } 308 } 309 310 // group 311 if (!is_null($options['group'])) { 312 $sqlParts['from']['hstgrp'] = 'hstgrp g'; 313 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 314 $sqlParts['where']['ghg'] = 'g.groupid=hg.groupid'; 315 $sqlParts['where']['hgi'] = 'hg.hostid=i.hostid'; 316 $sqlParts['where'][] = ' g.name='.zbx_dbstr($options['group']); 317 } 318 319 // host 320 if (!is_null($options['host'])) { 321 $sqlParts['from']['hosts'] = 'hosts h'; 322 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 323 $sqlParts['where'][] = ' h.host='.zbx_dbstr($options['host']); 324 } 325 326 // application 327 if (!is_null($options['application'])) { 328 $sqlParts['from']['applications'] = 'applications a'; 329 $sqlParts['from']['items_applications'] = 'items_applications ia'; 330 $sqlParts['where']['aia'] = 'a.applicationid = ia.applicationid'; 331 $sqlParts['where']['iai'] = 'ia.itemid=i.itemid'; 332 $sqlParts['where'][] = ' a.name='.zbx_dbstr($options['application']); 333 } 334 335 // with_triggers 336 if (!is_null($options['with_triggers'])) { 337 if ($options['with_triggers'] == 1) { 338 $sqlParts['where'][] = 'EXISTS ('. 339 'SELECT NULL'. 340 ' FROM functions ff,triggers t'. 341 ' WHERE i.itemid=ff.itemid'. 342 ' AND ff.triggerid=t.triggerid'. 343 ' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 344 ')'; 345 } 346 else { 347 $sqlParts['where'][] = 'NOT EXISTS ('. 348 'SELECT NULL'. 349 ' FROM functions ff,triggers t'. 350 ' WHERE i.itemid=ff.itemid'. 351 ' AND ff.triggerid=t.triggerid'. 352 ' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 353 ')'; 354 } 355 } 356 357 // limit 358 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 359 $sqlParts['limit'] = $options['limit']; 360 } 361 362 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 363 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 364 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 365 while ($item = DBfetch($res)) { 366 if ($options['countOutput']) { 367 if ($options['groupCount']) { 368 $result[] = $item; 369 } 370 else { 371 $result = $item['rowscount']; 372 } 373 } 374 else { 375 $result[$item['itemid']] = $item; 376 } 377 } 378 379 if ($options['countOutput']) { 380 return $result; 381 } 382 383 // add other related objects 384 if ($result) { 385 $result = $this->addRelatedObjects($options, $result); 386 $result = $this->unsetExtraFields($result, ['hostid', 'interfaceid', 'value_type'], $options['output']); 387 } 388 389 // removing keys (hash -> array) 390 if (!$options['preservekeys']) { 391 $result = zbx_cleanHashes($result); 392 } 393 394 // Decode ITEM_TYPE_HTTPAGENT encoded fields. 395 $json = new CJson(); 396 397 foreach ($result as &$item) { 398 if (array_key_exists('query_fields', $item)) { 399 $query_fields = ($item['query_fields'] !== '') ? $json->decode($item['query_fields'], true) : []; 400 $item['query_fields'] = $json->hasError() ? [] : $query_fields; 401 } 402 403 if (array_key_exists('headers', $item)) { 404 $item['headers'] = $this->headersStringToArray($item['headers']); 405 } 406 } 407 408 return $result; 409 } 410 411 /** 412 * Create item. 413 * 414 * @param $items 415 * 416 * @return array 417 */ 418 public function create($items) { 419 $items = zbx_toArray($items); 420 421 parent::checkInput($items); 422 self::validateInventoryLinks($items); 423 424 foreach ($items as &$item) { 425 $item['flags'] = ZBX_FLAG_DISCOVERY_NORMAL; 426 unset($item['itemid']); 427 } 428 unset($item); 429 430 $this->validateDependentItems($items); 431 432 $json = new CJson(); 433 434 foreach ($items as &$item) { 435 if ($item['type'] == ITEM_TYPE_HTTPAGENT) { 436 if (array_key_exists('query_fields', $item)) { 437 $item['query_fields'] = $item['query_fields'] ? $json->encode($item['query_fields']) : ''; 438 } 439 440 if (array_key_exists('headers', $item)) { 441 $item['headers'] = $this->headersArrayToString($item['headers']); 442 } 443 444 if (array_key_exists('request_method', $item) && $item['request_method'] == HTTPCHECK_REQUEST_HEAD 445 && !array_key_exists('retrieve_mode', $item)) { 446 $item['retrieve_mode'] = HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; 447 } 448 } 449 else { 450 $item['query_fields'] = ''; 451 $item['headers'] = ''; 452 } 453 } 454 unset($item); 455 456 $this->createReal($items); 457 $this->inherit($items); 458 459 return ['itemids' => zbx_objectValues($items, 'itemid')]; 460 } 461 462 /** 463 * Create host item. 464 * 465 * @param array $items 466 */ 467 protected function createReal(array &$items) { 468 foreach ($items as &$item) { 469 if ($item['type'] != ITEM_TYPE_DEPENDENT) { 470 $item['master_itemid'] = null; 471 } 472 } 473 unset($item); 474 475 $itemids = DB::insert('items', $items); 476 477 $itemApplications = []; 478 foreach ($items as $key => $item) { 479 $items[$key]['itemid'] = $itemids[$key]; 480 481 if (!isset($item['applications'])) { 482 continue; 483 } 484 485 foreach ($item['applications'] as $appid) { 486 if ($appid == 0) { 487 continue; 488 } 489 490 $itemApplications[] = [ 491 'applicationid' => $appid, 492 'itemid' => $items[$key]['itemid'] 493 ]; 494 } 495 } 496 497 if ($itemApplications) { 498 DB::insertBatch('items_applications', $itemApplications); 499 } 500 501 $this->createItemPreprocessing($items); 502 } 503 504 /** 505 * Update host items. 506 * 507 * @param array $items 508 */ 509 protected function updateReal(array $items) { 510 CArrayHelper::sort($items, ['itemid']); 511 512 $data = []; 513 foreach ($items as $item) { 514 unset($item['flags']); // flags cannot be changed 515 $data[] = ['values' => $item, 'where' => ['itemid' => $item['itemid']]]; 516 } 517 DB::update('items', $data); 518 519 $itemApplications = []; 520 $applicationids = []; 521 foreach ($items as $item) { 522 if (!isset($item['applications'])) { 523 continue; 524 } 525 $applicationids[] = $item['itemid']; 526 527 foreach ($item['applications'] as $appid) { 528 $itemApplications[] = [ 529 'applicationid' => $appid, 530 'itemid' => $item['itemid'] 531 ]; 532 } 533 } 534 535 if (!empty($applicationids)) { 536 DB::delete('items_applications', ['itemid' => $applicationids]); 537 DB::insertBatch('items_applications', $itemApplications); 538 } 539 540 $this->updateItemPreprocessing($items); 541 } 542 543 /** 544 * Update item. 545 * 546 * @param array $items 547 * 548 * @return boolean 549 */ 550 public function update($items) { 551 $items = zbx_toArray($items); 552 553 parent::checkInput($items, true); 554 self::validateInventoryLinks($items, true); 555 556 $db_items = $this->get([ 557 'output' => ['flags', 'type', 'master_itemid', 'authtype', 'allow_traps', 'retrieve_mode'], 558 'itemids' => zbx_objectValues($items, 'itemid'), 559 'editable' => true, 560 'preservekeys' => true 561 ]); 562 563 $items = $this->extendFromObjects(zbx_toHash($items, 'itemid'), $db_items, ['flags', 'type', 'authtype', 564 'master_itemid' 565 ]); 566 567 $this->validateDependentItems($items); 568 569 $defaults = DB::getDefaults('items'); 570 $clean = [ 571 ITEM_TYPE_HTTPAGENT => [ 572 'url' => '', 573 'query_fields' => '', 574 'timeout' => $defaults['timeout'], 575 'status_codes' => $defaults['status_codes'], 576 'follow_redirects' => $defaults['follow_redirects'], 577 'request_method' => $defaults['request_method'], 578 'allow_traps' => $defaults['allow_traps'], 579 'post_type' => $defaults['post_type'], 580 'http_proxy' => '', 581 'headers' => '', 582 'retrieve_mode' => $defaults['retrieve_mode'], 583 'output_format' => $defaults['output_format'], 584 'ssl_key_password' => '', 585 'verify_peer' => $defaults['verify_peer'], 586 'verify_host' => $defaults['verify_host'], 587 'ssl_cert_file' => '', 588 'ssl_key_file' => '', 589 'posts' => '' 590 ] 591 ]; 592 593 $json = new CJson(); 594 595 foreach ($items as &$item) { 596 $type_change = ($item['type'] != $db_items[$item['itemid']]['type']); 597 598 if ($item['type'] != ITEM_TYPE_DEPENDENT && $db_items[$item['itemid']]['master_itemid'] != 0) { 599 $item['master_itemid'] = 0; 600 } 601 602 if ($type_change && $db_items[$item['itemid']]['type'] == ITEM_TYPE_HTTPAGENT) { 603 $item = array_merge($item, $clean[ITEM_TYPE_HTTPAGENT]); 604 605 if ($item['type'] != ITEM_TYPE_SSH) { 606 $item['authtype'] = $defaults['authtype']; 607 $item['username'] = ''; 608 $item['password'] = ''; 609 } 610 611 if ($item['type'] != ITEM_TYPE_TRAPPER) { 612 $item['trapper_hosts'] = ''; 613 } 614 } 615 616 if ($item['type'] == ITEM_TYPE_HTTPAGENT) { 617 // Clean username and password when authtype is set to HTTPTEST_AUTH_NONE. 618 if ($item['authtype'] == HTTPTEST_AUTH_NONE) { 619 $item['username'] = ''; 620 $item['password'] = ''; 621 } 622 623 if (array_key_exists('allow_traps', $item) && $item['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF 624 && $item['allow_traps'] != $db_items[$item['itemid']]['allow_traps']) { 625 $item['trapper_hosts'] = ''; 626 } 627 628 if (array_key_exists('query_fields', $item) && is_array($item['query_fields'])) { 629 $item['query_fields'] = $item['query_fields'] ? $json->encode($item['query_fields']) : ''; 630 } 631 632 if (array_key_exists('headers', $item) && is_array($item['headers'])) { 633 $item['headers'] = $this->headersArrayToString($item['headers']); 634 } 635 636 if (array_key_exists('request_method', $item) && $item['request_method'] == HTTPCHECK_REQUEST_HEAD 637 && !array_key_exists('retrieve_mode', $item) 638 && $db_items[$item['itemid']]['retrieve_mode'] != HTTPTEST_STEP_RETRIEVE_MODE_HEADERS) { 639 $item['retrieve_mode'] = HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; 640 } 641 } 642 else { 643 $item['query_fields'] = ''; 644 $item['headers'] = ''; 645 } 646 } 647 unset($item); 648 649 $this->updateReal($items); 650 $this->inherit($items); 651 652 return ['itemids' => zbx_objectValues($items, 'itemid')]; 653 } 654 655 /** 656 * Delete items. 657 * 658 * @param array $itemids 659 * 660 * @return array 661 */ 662 public function delete(array $itemids) { 663 $this->validateDelete($itemids, $db_items); 664 665 CItemManager::delete($itemids); 666 667 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_ITEM, $db_items); 668 669 return ['itemids' => $itemids]; 670 } 671 672 /** 673 * Validates the input parameters for the delete() method. 674 * 675 * @param array $itemids [IN/OUT] 676 * @param array $db_items [OUT] 677 * 678 * @throws APIException if the input is invalid. 679 */ 680 private function validateDelete(array &$itemids, array &$db_items = null) { 681 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 682 if (!CApiInputValidator::validate($api_input_rules, $itemids, '/', $error)) { 683 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 684 } 685 686 $db_items = $this->get([ 687 'output' => ['itemid', 'name', 'templateid', 'flags'], 688 'itemids' => $itemids, 689 'editable' => true, 690 'preservekeys' => true 691 ]); 692 693 foreach ($itemids as $itemid) { 694 if (!array_key_exists($itemid, $db_items)) { 695 self::exception(ZBX_API_ERROR_PERMISSIONS, 696 _('No permissions to referred object or it does not exist!') 697 ); 698 } 699 700 $db_item = $db_items[$itemid]; 701 702 if ($db_item['templateid'] != 0) { 703 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete templated item.')); 704 } 705 } 706 } 707 708 public function syncTemplates($data) { 709 $data['templateids'] = zbx_toArray($data['templateids']); 710 $data['hostids'] = zbx_toArray($data['hostids']); 711 712 $output = []; 713 foreach ($this->fieldRules as $field_name => $rules) { 714 if (!array_key_exists('system', $rules) && !array_key_exists('host', $rules)) { 715 $output[] = $field_name; 716 } 717 } 718 719 $tpl_items = $this->get([ 720 'output' => $output, 721 'selectApplications' => ['applicationid'], 722 'selectPreprocessing' => ['type', 'params'], 723 'hostids' => $data['templateids'], 724 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL], 725 'preservekeys' => true 726 ]); 727 728 $json = new CJson(); 729 730 foreach ($tpl_items as &$tpl_item) { 731 $tpl_item['applications'] = zbx_objectValues($tpl_item['applications'], 'applicationid'); 732 733 if ($tpl_item['type'] == ITEM_TYPE_HTTPAGENT) { 734 if (array_key_exists('query_fields', $tpl_item) && is_array($tpl_item['query_fields'])) { 735 $tpl_item['query_fields'] = $tpl_item['query_fields'] 736 ? $json->encode($tpl_item['query_fields']) 737 : ''; 738 } 739 740 if (array_key_exists('headers', $tpl_item) && is_array($tpl_item['headers'])) { 741 $tpl_item['headers'] = $this->headersArrayToString($tpl_item['headers']); 742 } 743 } 744 else { 745 $tpl_item['query_fields'] = ''; 746 $tpl_item['headers'] = ''; 747 } 748 } 749 unset($tpl_item); 750 751 $this->inherit($tpl_items, $data['hostids']); 752 753 return true; 754 } 755 756 /** 757 * Check item specific fields: 758 * - validate history and trends using simple interval parser and user macro parser; 759 * - validate item preprocessing. 760 * 761 * @param array $item An array of single item data. 762 * @param string $method A string of "create" or "update" method. 763 * 764 * @throws APIException if the input is invalid. 765 */ 766 protected function checkSpecificFields(array $item, $method) { 767 if (array_key_exists('history', $item) 768 && !validateTimeUnit($item['history'], SEC_PER_HOUR, 25 * SEC_PER_YEAR, true, $error, 769 ['usermacros' => true])) { 770 self::exception(ZBX_API_ERROR_PARAMETERS, 771 _s('Incorrect value for field "%1$s": %2$s.', 'history', $error) 772 ); 773 } 774 775 if (array_key_exists('trends', $item) 776 && !validateTimeUnit($item['trends'], SEC_PER_DAY, 25 * SEC_PER_YEAR, true, $error, 777 ['usermacros' => true])) { 778 self::exception(ZBX_API_ERROR_PARAMETERS, 779 _s('Incorrect value for field "%1$s": %2$s.', 'trends', $error) 780 ); 781 } 782 783 $this->validateItemPreprocessing($item, $method); 784 } 785 786 /** 787 * Check, if items that are about to be inserted or updated violate the rule: 788 * only one item can be linked to a inventory filed. 789 * If everything is ok, function return true or throws Exception otherwise 790 * 791 * @static 792 * 793 * @param array $items 794 * @param bool $update whether this is update operation 795 * 796 * @return bool 797 */ 798 public static function validateInventoryLinks(array $items, $update = false) { 799 // inventory link field is not being updated, or being updated to 0, no need to validate anything then 800 foreach ($items as $i => $item) { 801 if (!isset($item['inventory_link']) || $item['inventory_link'] == 0) { 802 unset($items[$i]); 803 } 804 } 805 806 if (zbx_empty($items)) { 807 return true; 808 } 809 810 $possibleHostInventories = getHostInventories(); 811 if ($update) { 812 // for successful validation we need three fields for each item: inventory_link, hostid and key_ 813 // problem is, that when we are updating an item, we might not have them, because they are not changed 814 // so, we need to find out what is missing and use API to get the lacking info 815 $itemsWithNoHostId = []; 816 $itemsWithNoInventoryLink = []; 817 $itemsWithNoKeys = []; 818 foreach ($items as $item) { 819 if (!isset($item['inventory_link'])) { 820 $itemsWithNoInventoryLink[$item['itemid']] = $item['itemid']; 821 } 822 if (!isset($item['hostid'])) { 823 $itemsWithNoHostId[$item['itemid']] = $item['itemid']; 824 } 825 if (!isset($item['key_'])) { 826 $itemsWithNoKeys[$item['itemid']] = $item['itemid']; 827 } 828 } 829 $itemsToFind = array_merge($itemsWithNoHostId, $itemsWithNoInventoryLink, $itemsWithNoKeys); 830 831 // are there any items with lacking info? 832 if (!zbx_empty($itemsToFind)) { 833 $missingInfo = API::Item()->get([ 834 'output' => ['hostid', 'inventory_link', 'key_'], 835 'filter' => ['itemid' => $itemsToFind], 836 'nopermissions' => true 837 ]); 838 $missingInfo = zbx_toHash($missingInfo, 'itemid'); 839 840 // appending host ids, inventory_links and keys where they are needed 841 foreach ($items as $i => $item) { 842 if (isset($missingInfo[$item['itemid']])) { 843 if (!isset($items[$i]['hostid'])) { 844 $items[$i]['hostid'] = $missingInfo[$item['itemid']]['hostid']; 845 } 846 if (!isset($items[$i]['inventory_link'])) { 847 $items[$i]['inventory_link'] = $missingInfo[$item['itemid']]['inventory_link']; 848 } 849 if (!isset($items[$i]['key_'])) { 850 $items[$i]['key_'] = $missingInfo[$item['itemid']]['key_']; 851 } 852 } 853 } 854 } 855 } 856 857 $hostids = zbx_objectValues($items, 'hostid'); 858 859 // getting all inventory links on every affected host 860 $itemsOnHostsInfo = API::Item()->get([ 861 'output' => ['key_', 'inventory_link', 'hostid'], 862 'filter' => ['hostid' => $hostids], 863 'nopermissions' => true 864 ]); 865 866 // now, changing array to: 'hostid' => array('key_'=>'inventory_link') 867 $linksOnHostsCurr = []; 868 foreach ($itemsOnHostsInfo as $info) { 869 // 0 means no link - we are not interested in those ones 870 if ($info['inventory_link'] != 0) { 871 if (!isset($linksOnHostsCurr[$info['hostid']])) { 872 $linksOnHostsCurr[$info['hostid']] = [$info['key_'] => $info['inventory_link']]; 873 } 874 else{ 875 $linksOnHostsCurr[$info['hostid']][$info['key_']] = $info['inventory_link']; 876 } 877 } 878 } 879 880 $linksOnHostsFuture = []; 881 882 foreach ($items as $item) { 883 // checking if inventory_link value is a valid number 884 if ($update || $item['value_type'] != ITEM_VALUE_TYPE_LOG) { 885 // does inventory field with provided number exists? 886 if (!isset($possibleHostInventories[$item['inventory_link']])) { 887 $maxVar = max(array_keys($possibleHostInventories)); 888 self::exception( 889 ZBX_API_ERROR_PARAMETERS, 890 _s('Item "%1$s" cannot populate a missing host inventory field number "%2$d". Choices are: from 0 (do not populate) to %3$d.', $item['name'], $item['inventory_link'], $maxVar) 891 ); 892 } 893 } 894 895 if (!isset($linksOnHostsFuture[$item['hostid']])) { 896 $linksOnHostsFuture[$item['hostid']] = [$item['key_'] => $item['inventory_link']]; 897 } 898 else { 899 $linksOnHostsFuture[$item['hostid']][$item['key_']] = $item['inventory_link']; 900 } 901 } 902 903 foreach ($linksOnHostsFuture as $hostId => $linkFuture) { 904 if (isset($linksOnHostsCurr[$hostId])) { 905 $futureSituation = array_merge($linksOnHostsCurr[$hostId], $linksOnHostsFuture[$hostId]); 906 } 907 else { 908 $futureSituation = $linksOnHostsFuture[$hostId]; 909 } 910 $valuesCount = array_count_values($futureSituation); 911 912 // if we have a duplicate inventory links after merging - we are in trouble 913 if (max($valuesCount) > 1) { 914 // what inventory field caused this conflict? 915 $conflictedLink = array_keys($valuesCount, 2); 916 $conflictedLink = reset($conflictedLink); 917 918 // which of updated items populates this link? 919 $beingSavedItemName = ''; 920 foreach ($items as $item) { 921 if ($item['inventory_link'] == $conflictedLink) { 922 if (isset($item['name'])) { 923 $beingSavedItemName = $item['name']; 924 } 925 else { 926 $thisItem = API::Item()->get([ 927 'output' => ['name'], 928 'filter' => ['itemid' => $item['itemid']], 929 'nopermissions' => true 930 ]); 931 $beingSavedItemName = $thisItem[0]['name']; 932 } 933 break; 934 } 935 } 936 937 // name of the original item that already populates the field 938 $originalItem = API::Item()->get([ 939 'output' => ['name'], 940 'filter' => [ 941 'hostid' => $hostId, 942 'inventory_link' => $conflictedLink 943 ], 944 'nopermissions' => true 945 ]); 946 $originalItemName = $originalItem[0]['name']; 947 948 self::exception( 949 ZBX_API_ERROR_PARAMETERS, 950 _s( 951 'Two items ("%1$s" and "%2$s") cannot populate one host inventory field "%3$s", this would lead to a conflict.', 952 $beingSavedItemName, 953 $originalItemName, 954 $possibleHostInventories[$conflictedLink]['title'] 955 ) 956 ); 957 } 958 } 959 960 return true; 961 } 962 963 public function addRelatedObjects(array $options, array $result) { 964 $result = parent::addRelatedObjects($options, $result); 965 966 $itemids = array_keys($result); 967 968 // adding applications 969 if ($options['selectApplications'] !== null && $options['selectApplications'] != API_OUTPUT_COUNT) { 970 $relationMap = $this->createRelationMap($result, 'itemid', 'applicationid', 'items_applications'); 971 $applications = API::Application()->get([ 972 'output' => $options['selectApplications'], 973 'applicationids' => $relationMap->getRelatedIds(), 974 'preservekeys' => true 975 ]); 976 $result = $relationMap->mapMany($result, $applications, 'applications'); 977 } 978 979 // adding interfaces 980 if ($options['selectInterfaces'] !== null && $options['selectInterfaces'] != API_OUTPUT_COUNT) { 981 $relationMap = $this->createRelationMap($result, 'itemid', 'interfaceid'); 982 $interfaces = API::HostInterface()->get([ 983 'output' => $options['selectInterfaces'], 984 'interfaceids' => $relationMap->getRelatedIds(), 985 'nopermissions' => true, 986 'preservekeys' => true 987 ]); 988 $result = $relationMap->mapMany($result, $interfaces, 'interfaces'); 989 } 990 991 // adding triggers 992 if (!is_null($options['selectTriggers'])) { 993 if ($options['selectTriggers'] != API_OUTPUT_COUNT) { 994 $relationMap = $this->createRelationMap($result, 'itemid', 'triggerid', 'functions'); 995 $triggers = API::Trigger()->get([ 996 'output' => $options['selectTriggers'], 997 'triggerids' => $relationMap->getRelatedIds(), 998 'preservekeys' => true 999 ]); 1000 1001 if (!is_null($options['limitSelects'])) { 1002 order_result($triggers, 'description'); 1003 } 1004 $result = $relationMap->mapMany($result, $triggers, 'triggers', $options['limitSelects']); 1005 } 1006 else { 1007 $triggers = API::Trigger()->get([ 1008 'countOutput' => true, 1009 'groupCount' => true, 1010 'itemids' => $itemids 1011 ]); 1012 $triggers = zbx_toHash($triggers, 'itemid'); 1013 1014 foreach ($result as $itemid => $item) { 1015 $result[$itemid]['triggers'] = array_key_exists($itemid, $triggers) 1016 ? $triggers[$itemid]['rowscount'] 1017 : '0'; 1018 } 1019 } 1020 } 1021 1022 // adding graphs 1023 if (!is_null($options['selectGraphs'])) { 1024 if ($options['selectGraphs'] != API_OUTPUT_COUNT) { 1025 $relationMap = $this->createRelationMap($result, 'itemid', 'graphid', 'graphs_items'); 1026 $graphs = API::Graph()->get([ 1027 'output' => $options['selectGraphs'], 1028 'graphids' => $relationMap->getRelatedIds(), 1029 'preservekeys' => true 1030 ]); 1031 1032 if (!is_null($options['limitSelects'])) { 1033 order_result($graphs, 'name'); 1034 } 1035 $result = $relationMap->mapMany($result, $graphs, 'graphs', $options['limitSelects']); 1036 } 1037 else { 1038 $graphs = API::Graph()->get([ 1039 'countOutput' => true, 1040 'groupCount' => true, 1041 'itemids' => $itemids 1042 ]); 1043 $graphs = zbx_toHash($graphs, 'itemid'); 1044 1045 foreach ($result as $itemid => $item) { 1046 $result[$itemid]['graphs'] = array_key_exists($itemid, $graphs) 1047 ? $graphs[$itemid]['rowscount'] 1048 : '0'; 1049 } 1050 } 1051 } 1052 1053 // adding discoveryrule 1054 if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) { 1055 $relationMap = new CRelationMap(); 1056 // discovered items 1057 $dbRules = DBselect( 1058 'SELECT id1.itemid,id2.parent_itemid'. 1059 ' FROM item_discovery id1,item_discovery id2,items i'. 1060 ' WHERE '.dbConditionInt('id1.itemid', $itemids). 1061 ' AND id1.parent_itemid=id2.itemid'. 1062 ' AND i.itemid=id1.itemid'. 1063 ' AND i.flags='.ZBX_FLAG_DISCOVERY_CREATED 1064 ); 1065 while ($rule = DBfetch($dbRules)) { 1066 $relationMap->addRelation($rule['itemid'], $rule['parent_itemid']); 1067 } 1068 1069 // item prototypes 1070 // TODO: this should not be in the item API 1071 $dbRules = DBselect( 1072 'SELECT id.parent_itemid,id.itemid'. 1073 ' FROM item_discovery id,items i'. 1074 ' WHERE '.dbConditionInt('id.itemid', $itemids). 1075 ' AND i.itemid=id.itemid'. 1076 ' AND i.flags='.ZBX_FLAG_DISCOVERY_PROTOTYPE 1077 ); 1078 while ($rule = DBfetch($dbRules)) { 1079 $relationMap->addRelation($rule['itemid'], $rule['parent_itemid']); 1080 } 1081 1082 $discoveryRules = API::DiscoveryRule()->get([ 1083 'output' => $options['selectDiscoveryRule'], 1084 'itemids' => $relationMap->getRelatedIds(), 1085 'nopermissions' => true, 1086 'preservekeys' => true 1087 ]); 1088 $result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule'); 1089 } 1090 1091 // adding item discovery 1092 if ($options['selectItemDiscovery'] !== null) { 1093 $itemDiscoveries = API::getApiService()->select('item_discovery', [ 1094 'output' => $this->outputExtend($options['selectItemDiscovery'], ['itemdiscoveryid', 'itemid']), 1095 'filter' => ['itemid' => array_keys($result)], 1096 'preservekeys' => true 1097 ]); 1098 $relationMap = $this->createRelationMap($itemDiscoveries, 'itemid', 'itemdiscoveryid'); 1099 1100 $itemDiscoveries = $this->unsetExtraFields($itemDiscoveries, ['itemid', 'itemdiscoveryid'], 1101 $options['selectItemDiscovery'] 1102 ); 1103 $result = $relationMap->mapOne($result, $itemDiscoveries, 'itemDiscovery'); 1104 } 1105 1106 // adding history data 1107 $requestedOutput = []; 1108 if ($this->outputIsRequested('lastclock', $options['output'])) { 1109 $requestedOutput['lastclock'] = true; 1110 } 1111 if ($this->outputIsRequested('lastns', $options['output'])) { 1112 $requestedOutput['lastns'] = true; 1113 } 1114 if ($this->outputIsRequested('lastvalue', $options['output'])) { 1115 $requestedOutput['lastvalue'] = true; 1116 } 1117 if ($this->outputIsRequested('prevvalue', $options['output'])) { 1118 $requestedOutput['prevvalue'] = true; 1119 } 1120 if ($requestedOutput) { 1121 $history = Manager::History()->getLastValues($result, 2, ZBX_HISTORY_PERIOD); 1122 foreach ($result as &$item) { 1123 $lastHistory = isset($history[$item['itemid']][0]) ? $history[$item['itemid']][0] : null; 1124 $prevHistory = isset($history[$item['itemid']][1]) ? $history[$item['itemid']][1] : null; 1125 $no_value = in_array($item['value_type'], 1126 [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT]) ? '' : '0'; 1127 1128 if (isset($requestedOutput['lastclock'])) { 1129 $item['lastclock'] = $lastHistory ? $lastHistory['clock'] : '0'; 1130 } 1131 if (isset($requestedOutput['lastns'])) { 1132 $item['lastns'] = $lastHistory ? $lastHistory['ns'] : '0'; 1133 } 1134 if (isset($requestedOutput['lastvalue'])) { 1135 $item['lastvalue'] = $lastHistory ? $lastHistory['value'] : $no_value; 1136 } 1137 if (isset($requestedOutput['prevvalue'])) { 1138 $item['prevvalue'] = $prevHistory ? $prevHistory['value'] : $no_value; 1139 } 1140 } 1141 unset($item); 1142 } 1143 1144 if ($options['selectPreprocessing'] !== null && $options['selectPreprocessing'] != API_OUTPUT_COUNT) { 1145 $db_item_preproc = API::getApiService()->select('item_preproc', [ 1146 'output' => $this->outputExtend($options['selectPreprocessing'], ['itemid', 'step']), 1147 'filter' => ['itemid' => array_keys($result)] 1148 ]); 1149 1150 CArrayHelper::sort($db_item_preproc, ['step']); 1151 1152 foreach ($result as &$item) { 1153 $item['preprocessing'] = []; 1154 } 1155 unset($item); 1156 1157 foreach ($db_item_preproc as $step) { 1158 $itemid = $step['itemid']; 1159 unset($step['item_preprocid'], $step['itemid'], $step['step']); 1160 1161 if (array_key_exists($itemid, $result)) { 1162 $result[$itemid]['preprocessing'][] = $step; 1163 } 1164 } 1165 } 1166 1167 return $result; 1168 } 1169 1170 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 1171 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 1172 1173 if (!$options['countOutput']) { 1174 if ($options['selectHosts'] !== null) { 1175 $sqlParts = $this->addQuerySelect('i.hostid', $sqlParts); 1176 } 1177 1178 if ($options['selectInterfaces'] !== null) { 1179 $sqlParts = $this->addQuerySelect('i.interfaceid', $sqlParts); 1180 } 1181 1182 if ($this->outputIsRequested('lastclock', $options['output']) 1183 || $this->outputIsRequested('lastns', $options['output']) 1184 || $this->outputIsRequested('lastvalue', $options['output']) 1185 || $this->outputIsRequested('prevvalue', $options['output'])) { 1186 1187 $sqlParts = $this->addQuerySelect('i.value_type', $sqlParts); 1188 } 1189 } 1190 1191 return $sqlParts; 1192 } 1193} 1194