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 discovery rules. 24 */ 25class CDiscoveryRule extends CItemGeneral { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 30 'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 31 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 32 'copy' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN] 33 ]; 34 35 protected $tableName = 'items'; 36 protected $tableAlias = 'i'; 37 protected $sortColumns = ['itemid', 'name', 'key_', 'delay', 'type', 'status']; 38 39 /** 40 * Define a set of supported pre-processing rules. 41 * 42 * @var array 43 */ 44 const SUPPORTED_PREPROCESSING_TYPES = [ZBX_PREPROC_REGSUB, ZBX_PREPROC_JSONPATH, 45 ZBX_PREPROC_VALIDATE_NOT_REGEX, ZBX_PREPROC_ERROR_FIELD_JSON, ZBX_PREPROC_THROTTLE_TIMED_VALUE, 46 ZBX_PREPROC_SCRIPT, ZBX_PREPROC_PROMETHEUS_TO_JSON, ZBX_PREPROC_XPATH, ZBX_PREPROC_ERROR_FIELD_XML, 47 ZBX_PREPROC_CSV_TO_JSON, ZBX_PREPROC_STR_REPLACE, ZBX_PREPROC_XML_TO_JSON 48 ]; 49 50 /** 51 * Define a set of supported item types. 52 * 53 * @var array 54 */ 55 const SUPPORTED_ITEM_TYPES = [ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL, 56 ITEM_TYPE_ZABBIX_ACTIVE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_IPMI, ITEM_TYPE_SSH, 57 ITEM_TYPE_TELNET, ITEM_TYPE_JMX, ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT 58 ]; 59 60 public function __construct() { 61 parent::__construct(); 62 63 $this->errorMessages = array_merge($this->errorMessages, [ 64 self::ERROR_EXISTS_TEMPLATE => _('Discovery rule "%1$s" already exists on "%2$s", inherited from another template.'), 65 self::ERROR_EXISTS => _('Discovery rule "%1$s" already exists on "%2$s".'), 66 self::ERROR_INVALID_KEY => _('Invalid key "%1$s" for discovery rule "%2$s" on "%3$s": %4$s.') 67 ]); 68 } 69 70 /** 71 * Get DiscoveryRule data 72 */ 73 public function get($options = []) { 74 $result = []; 75 76 $sqlParts = [ 77 'select' => ['items' => 'i.itemid'], 78 'from' => ['items' => 'items i'], 79 'where' => ['i.flags='.ZBX_FLAG_DISCOVERY_RULE], 80 'group' => [], 81 'order' => [], 82 'limit' => null 83 ]; 84 85 $defOptions = [ 86 'groupids' => null, 87 'templateids' => null, 88 'hostids' => null, 89 'itemids' => null, 90 'interfaceids' => null, 91 'inherited' => null, 92 'templated' => null, 93 'monitored' => null, 94 'editable' => false, 95 'nopermissions' => null, 96 // filter 97 'filter' => null, 98 'search' => null, 99 'searchByAny' => null, 100 'startSearch' => false, 101 'excludeSearch' => false, 102 'searchWildcardsEnabled' => null, 103 // output 104 'output' => API_OUTPUT_EXTEND, 105 'selectHosts' => null, 106 'selectItems' => null, 107 'selectTriggers' => null, 108 'selectGraphs' => null, 109 'selectHostPrototypes' => null, 110 'selectFilter' => null, 111 'selectLLDMacroPaths' => null, 112 'selectPreprocessing' => null, 113 'selectOverrides' => null, 114 'countOutput' => false, 115 'groupCount' => false, 116 'preservekeys' => false, 117 'sortfield' => '', 118 'sortorder' => '', 119 'limit' => null, 120 'limitSelects' => null 121 ]; 122 $options = zbx_array_merge($defOptions, $options); 123 124 // editable + PERMISSION CHECK 125 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 126 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 127 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 128 129 $sqlParts['where'][] = 'EXISTS ('. 130 'SELECT NULL'. 131 ' FROM hosts_groups hgg'. 132 ' JOIN rights r'. 133 ' ON r.id=hgg.groupid'. 134 ' AND '.dbConditionInt('r.groupid', $userGroups). 135 ' WHERE i.hostid=hgg.hostid'. 136 ' GROUP BY hgg.hostid'. 137 ' HAVING MIN(r.permission)>'.PERM_DENY. 138 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 139 ')'; 140 } 141 142 // templateids 143 if (!is_null($options['templateids'])) { 144 zbx_value2array($options['templateids']); 145 146 if (!is_null($options['hostids'])) { 147 zbx_value2array($options['hostids']); 148 $options['hostids'] = array_merge($options['hostids'], $options['templateids']); 149 } 150 else { 151 $options['hostids'] = $options['templateids']; 152 } 153 } 154 155 // hostids 156 if (!is_null($options['hostids'])) { 157 zbx_value2array($options['hostids']); 158 159 $sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['hostids']); 160 161 if ($options['groupCount']) { 162 $sqlParts['group']['i'] = 'i.hostid'; 163 } 164 } 165 166 // itemids 167 if (!is_null($options['itemids'])) { 168 zbx_value2array($options['itemids']); 169 170 $sqlParts['where']['itemid'] = dbConditionInt('i.itemid', $options['itemids']); 171 } 172 173 // interfaceids 174 if (!is_null($options['interfaceids'])) { 175 zbx_value2array($options['interfaceids']); 176 177 $sqlParts['where']['interfaceid'] = dbConditionId('i.interfaceid', $options['interfaceids']); 178 179 if ($options['groupCount']) { 180 $sqlParts['group']['i'] = 'i.interfaceid'; 181 } 182 } 183 184 // groupids 185 if ($options['groupids'] !== null) { 186 zbx_value2array($options['groupids']); 187 188 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 189 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 190 $sqlParts['where'][] = 'hg.hostid=i.hostid'; 191 192 if ($options['groupCount']) { 193 $sqlParts['group']['hg'] = 'hg.groupid'; 194 } 195 } 196 197 // inherited 198 if (!is_null($options['inherited'])) { 199 if ($options['inherited']) { 200 $sqlParts['where'][] = 'i.templateid IS NOT NULL'; 201 } 202 else { 203 $sqlParts['where'][] = 'i.templateid IS NULL'; 204 } 205 } 206 207 // templated 208 if (!is_null($options['templated'])) { 209 $sqlParts['from']['hosts'] = 'hosts h'; 210 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 211 212 if ($options['templated']) { 213 $sqlParts['where'][] = 'h.status='.HOST_STATUS_TEMPLATE; 214 } 215 else { 216 $sqlParts['where'][] = 'h.status<>'.HOST_STATUS_TEMPLATE; 217 } 218 } 219 220 // monitored 221 if (!is_null($options['monitored'])) { 222 $sqlParts['from']['hosts'] = 'hosts h'; 223 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 224 225 if ($options['monitored']) { 226 $sqlParts['where'][] = 'h.status='.HOST_STATUS_MONITORED; 227 $sqlParts['where'][] = 'i.status='.ITEM_STATUS_ACTIVE; 228 } 229 else { 230 $sqlParts['where'][] = '(h.status<>'.HOST_STATUS_MONITORED.' OR i.status<>'.ITEM_STATUS_ACTIVE.')'; 231 } 232 } 233 234 // search 235 if (is_array($options['search'])) { 236 if (array_key_exists('error', $options['search']) && $options['search']['error'] !== null) { 237 zbx_db_search('item_rtdata ir', ['search' => ['error' => $options['search']['error']]] + $options, 238 $sqlParts 239 ); 240 } 241 242 zbx_db_search('items i', $options, $sqlParts); 243 } 244 245 // filter 246 if (is_array($options['filter'])) { 247 if (array_key_exists('delay', $options['filter']) && $options['filter']['delay'] !== null) { 248 $sqlParts['where'][] = makeUpdateIntervalFilter('i.delay', $options['filter']['delay']); 249 unset($options['filter']['delay']); 250 } 251 252 if (array_key_exists('lifetime', $options['filter']) && $options['filter']['lifetime'] !== null) { 253 $options['filter']['lifetime'] = getTimeUnitFilters($options['filter']['lifetime']); 254 } 255 256 if (array_key_exists('state', $options['filter']) && $options['filter']['state'] !== null) { 257 $this->dbFilter('item_rtdata ir', ['filter' => ['state' => $options['filter']['state']]] + $options, 258 $sqlParts 259 ); 260 } 261 262 $this->dbFilter('items i', $options, $sqlParts); 263 264 if (isset($options['filter']['host'])) { 265 zbx_value2array($options['filter']['host']); 266 267 $sqlParts['from']['hosts'] = 'hosts h'; 268 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 269 $sqlParts['where']['h'] = dbConditionString('h.host', $options['filter']['host']); 270 } 271 } 272 273 // limit 274 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 275 $sqlParts['limit'] = $options['limit']; 276 } 277 278 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 279 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 280 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 281 while ($item = DBfetch($res)) { 282 if (!$options['countOutput']) { 283 $result[$item['itemid']] = $item; 284 continue; 285 } 286 287 if ($options['groupCount']) { 288 $result[] = $item; 289 } 290 else { 291 $result = $item['rowscount']; 292 } 293 } 294 295 if ($options['countOutput']) { 296 return $result; 297 } 298 299 if ($result) { 300 if (self::dbDistinct($sqlParts)) { 301 $result = $this->addNclobFieldValues($options, $result); 302 } 303 304 $result = $this->addRelatedObjects($options, $result); 305 $result = $this->unsetExtraFields($result, ['hostid'], $options['output']); 306 307 foreach ($result as &$rule) { 308 // unset the fields that are returned in the filter 309 unset($rule['formula'], $rule['evaltype']); 310 311 if ($options['selectFilter'] !== null) { 312 $filter = $this->unsetExtraFields([$rule['filter']], 313 ['conditions', 'formula', 'evaltype'], 314 $options['selectFilter'] 315 ); 316 $filter = reset($filter); 317 if (isset($filter['conditions'])) { 318 foreach ($filter['conditions'] as &$condition) { 319 unset($condition['item_conditionid'], $condition['itemid']); 320 } 321 unset($condition); 322 } 323 324 $rule['filter'] = $filter; 325 } 326 } 327 unset($rule); 328 } 329 330 // Decode ITEM_TYPE_HTTPAGENT encoded fields. 331 foreach ($result as &$item) { 332 if (array_key_exists('query_fields', $item)) { 333 $query_fields = ($item['query_fields'] !== '') ? json_decode($item['query_fields'], true) : []; 334 $item['query_fields'] = json_last_error() ? [] : $query_fields; 335 } 336 337 if (array_key_exists('headers', $item)) { 338 $item['headers'] = $this->headersStringToArray($item['headers']); 339 } 340 341 // Option 'Convert to JSON' is not supported for discovery rule. 342 unset($item['output_format']); 343 } 344 unset($item); 345 346 if (!$options['preservekeys']) { 347 $result = zbx_cleanHashes($result); 348 } 349 350 return $result; 351 } 352 353 /** 354 * Add DiscoveryRule. 355 * 356 * @param array $items 357 * 358 * @return array 359 */ 360 public function create($items) { 361 $items = zbx_toArray($items); 362 $this->checkInput($items); 363 364 foreach ($items as &$item) { 365 if ($item['type'] == ITEM_TYPE_HTTPAGENT) { 366 if (array_key_exists('query_fields', $item)) { 367 $item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : ''; 368 } 369 370 if (array_key_exists('headers', $item)) { 371 $item['headers'] = $this->headersArrayToString($item['headers']); 372 } 373 374 if (array_key_exists('request_method', $item) && $item['request_method'] == HTTPCHECK_REQUEST_HEAD 375 && !array_key_exists('retrieve_mode', $item)) { 376 $item['retrieve_mode'] = HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; 377 } 378 } 379 else { 380 $item['query_fields'] = ''; 381 $item['headers'] = ''; 382 } 383 384 // Option 'Convert to JSON' is not supported for discovery rule. 385 unset($item['itemid'], $item['output_format']); 386 } 387 unset($item); 388 389 // Get only hosts not templates from items 390 $hosts = API::Host()->get([ 391 'output' => [], 392 'hostids' => zbx_objectValues($items, 'hostid'), 393 'preservekeys' => true 394 ]); 395 foreach ($items as &$item) { 396 if (array_key_exists($item['hostid'], $hosts)) { 397 $item['rtdata'] = true; 398 } 399 } 400 unset($item); 401 402 $this->validateCreateLLDMacroPaths($items); 403 $this->validateDependentItems($items); 404 $this->createReal($items); 405 $this->inherit($items); 406 407 return ['itemids' => zbx_objectValues($items, 'itemid')]; 408 } 409 410 /** 411 * Update DiscoveryRule. 412 * 413 * @param array $items 414 * 415 * @return array 416 */ 417 public function update($items) { 418 $items = zbx_toArray($items); 419 420 $db_items = $this->get([ 421 'output' => ['itemid', 'name', 'type', 'master_itemid', 'authtype', 'allow_traps', 'retrieve_mode'], 422 'selectFilter' => ['evaltype', 'formula', 'conditions'], 423 'itemids' => zbx_objectValues($items, 'itemid'), 424 'preservekeys' => true 425 ]); 426 427 $this->checkInput($items, true, $db_items); 428 $this->validateUpdateLLDMacroPaths($items); 429 430 $items = $this->extendFromObjects(zbx_toHash($items, 'itemid'), $db_items, ['flags', 'type', 'authtype', 431 'master_itemid' 432 ]); 433 $this->validateDependentItems($items); 434 435 $defaults = DB::getDefaults('items'); 436 $clean = [ 437 ITEM_TYPE_HTTPAGENT => [ 438 'url' => '', 439 'query_fields' => '', 440 'timeout' => $defaults['timeout'], 441 'status_codes' => $defaults['status_codes'], 442 'follow_redirects' => $defaults['follow_redirects'], 443 'request_method' => $defaults['request_method'], 444 'allow_traps' => $defaults['allow_traps'], 445 'post_type' => $defaults['post_type'], 446 'http_proxy' => '', 447 'headers' => '', 448 'retrieve_mode' => $defaults['retrieve_mode'], 449 'output_format' => $defaults['output_format'], 450 'ssl_key_password' => '', 451 'verify_peer' => $defaults['verify_peer'], 452 'verify_host' => $defaults['verify_host'], 453 'ssl_cert_file' => '', 454 'ssl_key_file' => '', 455 'posts' => '' 456 ] 457 ]; 458 459 // set the default values required for updating 460 foreach ($items as &$item) { 461 $type_change = (array_key_exists('type', $item) && $item['type'] != $db_items[$item['itemid']]['type']); 462 463 if (isset($item['filter'])) { 464 foreach ($item['filter']['conditions'] as &$condition) { 465 $condition += [ 466 'operator' => DB::getDefault('item_condition', 'operator') 467 ]; 468 } 469 unset($condition); 470 } 471 472 if ($type_change && $db_items[$item['itemid']]['type'] == ITEM_TYPE_HTTPAGENT) { 473 $item = array_merge($item, $clean[ITEM_TYPE_HTTPAGENT]); 474 475 if ($item['type'] != ITEM_TYPE_SSH) { 476 $item['authtype'] = $defaults['authtype']; 477 $item['username'] = ''; 478 $item['password'] = ''; 479 } 480 481 if ($item['type'] != ITEM_TYPE_TRAPPER) { 482 $item['trapper_hosts'] = ''; 483 } 484 } 485 486 if ($item['type'] == ITEM_TYPE_HTTPAGENT) { 487 // Clean username and password when authtype is set to HTTPTEST_AUTH_NONE. 488 if ($item['authtype'] == HTTPTEST_AUTH_NONE) { 489 $item['username'] = ''; 490 $item['password'] = ''; 491 } 492 493 if (array_key_exists('allow_traps', $item) && $item['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF 494 && $item['allow_traps'] != $db_items[$item['itemid']]['allow_traps']) { 495 $item['trapper_hosts'] = ''; 496 } 497 498 if (array_key_exists('query_fields', $item) && is_array($item['query_fields'])) { 499 $item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : ''; 500 } 501 502 if (array_key_exists('headers', $item) && is_array($item['headers'])) { 503 $item['headers'] = $this->headersArrayToString($item['headers']); 504 } 505 506 if (array_key_exists('request_method', $item) && $item['request_method'] == HTTPCHECK_REQUEST_HEAD 507 && !array_key_exists('retrieve_mode', $item) 508 && $db_items[$item['itemid']]['retrieve_mode'] != HTTPTEST_STEP_RETRIEVE_MODE_HEADERS) { 509 $item['retrieve_mode'] = HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; 510 } 511 } 512 else { 513 $item['query_fields'] = ''; 514 $item['headers'] = ''; 515 } 516 517 if ($type_change && $db_items[$item['itemid']]['type'] == ITEM_TYPE_SCRIPT) { 518 if ($item['type'] != ITEM_TYPE_SSH && $item['type'] != ITEM_TYPE_DB_MONITOR 519 && $item['type'] != ITEM_TYPE_TELNET && $item['type'] != ITEM_TYPE_CALCULATED) { 520 $item['params'] = ''; 521 } 522 523 if ($item['type'] != ITEM_TYPE_HTTPAGENT) { 524 $item['timeout'] = $defaults['timeout']; 525 } 526 } 527 528 // Option 'Convert to JSON' is not supported for discovery rule. 529 unset($item['output_format']); 530 } 531 unset($item); 532 533 // update 534 $this->updateReal($items); 535 $this->inherit($items); 536 537 return ['itemids' => zbx_objectValues($items, 'itemid')]; 538 } 539 540 /** 541 * Delete DiscoveryRules. 542 * 543 * @param array $ruleids 544 * 545 * @return array 546 */ 547 public function delete(array $ruleids) { 548 $this->validateDelete($ruleids); 549 550 CDiscoveryRuleManager::delete($ruleids); 551 552 return ['ruleids' => $ruleids]; 553 } 554 555 /** 556 * Validates the input parameters for the delete() method. 557 * 558 * @param array $ruleids [IN/OUT] 559 * 560 * @throws APIException if the input is invalid. 561 */ 562 private function validateDelete(array &$ruleids) { 563 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 564 if (!CApiInputValidator::validate($api_input_rules, $ruleids, '/', $error)) { 565 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 566 } 567 568 $db_rules = $this->get([ 569 'output' => ['templateid'], 570 'itemids' => $ruleids, 571 'editable' => true, 572 'preservekeys' => true 573 ]); 574 575 foreach ($ruleids as $ruleid) { 576 if (!array_key_exists($ruleid, $db_rules)) { 577 self::exception(ZBX_API_ERROR_PERMISSIONS, 578 _('No permissions to referred object or it does not exist!') 579 ); 580 } 581 582 $db_rule = $db_rules[$ruleid]; 583 584 if ($db_rule['templateid'] != 0) { 585 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete templated items.')); 586 } 587 } 588 } 589 590 /** 591 * Checks if the current user has access to the given hosts and templates. Assumes the "hostid" field is valid. 592 * 593 * @param array $hostids an array of host or template IDs 594 * 595 * @throws APIException if the user doesn't have write permissions for the given hosts. 596 */ 597 protected function checkHostPermissions(array $hostids) { 598 if ($hostids) { 599 $hostids = array_unique($hostids); 600 601 $count = API::Host()->get([ 602 'countOutput' => true, 603 'hostids' => $hostids, 604 'editable' => true 605 ]); 606 607 if ($count == count($hostids)) { 608 return; 609 } 610 611 $count += API::Template()->get([ 612 'countOutput' => true, 613 'templateids' => $hostids, 614 'editable' => true 615 ]); 616 617 if ($count != count($hostids)) { 618 self::exception(ZBX_API_ERROR_PERMISSIONS, 619 _('No permissions to referred object or it does not exist!') 620 ); 621 } 622 } 623 } 624 625 /** 626 * Copies the given discovery rules to the specified hosts. 627 * 628 * @throws APIException if no discovery rule IDs or host IDs are given or 629 * the user doesn't have the necessary permissions. 630 * 631 * @param array $data 632 * @param array $data['discoveryids'] An array of item ids to be cloned. 633 * @param array $data['hostids'] An array of host ids were the items should be cloned to. 634 * 635 * @return bool 636 */ 637 public function copy(array $data) { 638 // validate data 639 if (!isset($data['discoveryids']) || !$data['discoveryids']) { 640 self::exception(ZBX_API_ERROR_PARAMETERS, _('No discovery rule IDs given.')); 641 } 642 if (!isset($data['hostids']) || !$data['hostids']) { 643 self::exception(ZBX_API_ERROR_PARAMETERS, _('No host IDs given.')); 644 } 645 646 $this->checkHostPermissions($data['hostids']); 647 648 // check if the given discovery rules exist 649 $count = $this->get([ 650 'countOutput' => true, 651 'itemids' => $data['discoveryids'] 652 ]); 653 654 if ($count != count($data['discoveryids'])) { 655 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 656 } 657 658 // copy 659 foreach ($data['discoveryids'] as $discoveryid) { 660 foreach ($data['hostids'] as $hostid) { 661 $this->copyDiscoveryRule($discoveryid, $hostid); 662 } 663 } 664 665 return true; 666 } 667 668 public function syncTemplates($data) { 669 $data['templateids'] = zbx_toArray($data['templateids']); 670 $data['hostids'] = zbx_toArray($data['hostids']); 671 672 $output = []; 673 foreach ($this->fieldRules as $field_name => $rules) { 674 if (!array_key_exists('system', $rules) && !array_key_exists('host', $rules)) { 675 $output[] = $field_name; 676 } 677 } 678 679 $tpl_items = $this->get([ 680 'output' => $output, 681 'hostids' => $data['templateids'], 682 'selectFilter' => ['formula', 'evaltype', 'conditions'], 683 'selectLLDMacroPaths' => ['lld_macro', 'path'], 684 'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'], 685 'selectOverrides' => ['name', 'step', 'stop', 'filter', 'operations'], 686 'preservekeys' => true 687 ]); 688 689 foreach ($tpl_items as &$item) { 690 if ($item['type'] == ITEM_TYPE_HTTPAGENT) { 691 if (array_key_exists('query_fields', $item) && is_array($item['query_fields'])) { 692 $item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : ''; 693 } 694 695 if (array_key_exists('headers', $item) && is_array($item['headers'])) { 696 $item['headers'] = $this->headersArrayToString($item['headers']); 697 } 698 } 699 else { 700 $item['query_fields'] = ''; 701 $item['headers'] = ''; 702 } 703 704 // Option 'Convert to JSON' is not supported for discovery rule. 705 unset($item['output_format']); 706 } 707 unset($item); 708 709 $this->inherit($tpl_items, $data['hostids']); 710 711 return true; 712 } 713 714 /** 715 * Copies all of the triggers from the source discovery to the target discovery rule. 716 * 717 * @throws APIException if trigger saving fails 718 * 719 * @param array $srcDiscovery The source discovery rule to copy from 720 * @param array $srcHost The host the source discovery belongs to 721 * @param array $dstHost The host the target discovery belongs to 722 * 723 * @return array 724 */ 725 protected function copyTriggerPrototypes(array $srcDiscovery, array $srcHost, array $dstHost) { 726 $srcTriggers = API::TriggerPrototype()->get([ 727 'discoveryids' => $srcDiscovery['itemid'], 728 'output' => ['triggerid', 'expression', 'description', 'url', 'status', 'priority', 'comments', 729 'templateid', 'type', 'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag', 730 'opdata', 'discover', 'event_name' 731 ], 732 'selectHosts' => API_OUTPUT_EXTEND, 733 'selectItems' => ['itemid', 'type'], 734 'selectDiscoveryRule' => API_OUTPUT_EXTEND, 735 'selectFunctions' => API_OUTPUT_EXTEND, 736 'selectDependencies' => ['triggerid'], 737 'selectTags' => ['tag', 'value'], 738 'preservekeys' => true 739 ]); 740 741 foreach ($srcTriggers as $id => $trigger) { 742 // Skip trigger prototypes with web items and remove them from source. 743 if (httpItemExists($trigger['items'])) { 744 unset($srcTriggers[$id]); 745 } 746 } 747 748 if (!$srcTriggers) { 749 return []; 750 } 751 752 /* 753 * Copy the remaining trigger prototypes to a new source. These will contain IDs and original dependencies. 754 * The dependencies from $srcTriggers will be removed. 755 */ 756 $trigger_prototypes = $srcTriggers; 757 758 // Contains original trigger prototype dependency IDs. 759 $dep_triggerids = []; 760 761 /* 762 * Collect dependency trigger IDs and remove them from source. Otherwise these IDs do not pass 763 * validation, since they don't belong to destination discovery rule. 764 */ 765 $add_dependencies = false; 766 767 foreach ($srcTriggers as $id => &$trigger) { 768 if ($trigger['dependencies']) { 769 foreach ($trigger['dependencies'] as $dep_trigger) { 770 $dep_triggerids[] = $dep_trigger['triggerid']; 771 } 772 $add_dependencies = true; 773 } 774 unset($trigger['dependencies']); 775 } 776 unset($trigger); 777 778 // Save new trigger prototypes and without dependencies for now. 779 $dstTriggers = $srcTriggers; 780 $dstTriggers = CMacrosResolverHelper::resolveTriggerExpressions($dstTriggers, 781 ['sources' => ['expression', 'recovery_expression']] 782 ); 783 foreach ($dstTriggers as $id => &$trigger) { 784 unset($trigger['triggerid'], $trigger['templateid'], $trigger['hosts'], $trigger['functions'], 785 $trigger['items'], $trigger['discoveryRule'] 786 ); 787 788 // Update the destination expressions. 789 $trigger['expression'] = triggerExpressionReplaceHost($trigger['expression'], $srcHost['host'], 790 $dstHost['host'] 791 ); 792 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 793 $trigger['recovery_expression'] = triggerExpressionReplaceHost($trigger['recovery_expression'], 794 $srcHost['host'], $dstHost['host'] 795 ); 796 } 797 } 798 unset($trigger); 799 800 $result = API::TriggerPrototype()->create($dstTriggers); 801 if (!$result) { 802 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone trigger prototypes.')); 803 } 804 805 // Process dependencies, if at least one trigger prototype has a dependency. 806 if ($add_dependencies) { 807 $trigger_prototypeids = array_keys($trigger_prototypes); 808 809 foreach ($result['triggerids'] as $i => $triggerid) { 810 $new_trigger_prototypes[$trigger_prototypeids[$i]] = [ 811 'new_triggerid' => $triggerid, 812 'new_hostid' => $dstHost['hostid'], 813 'new_host' => $dstHost['host'], 814 'src_hostid' => $srcHost['hostid'], 815 'src_host' => $srcHost['host'] 816 ]; 817 } 818 819 /* 820 * Search for original dependent triggers and expressions to find corresponding triggers on destination host 821 * with same expression. 822 */ 823 $dep_triggers = API::Trigger()->get([ 824 'output' => ['description', 'expression'], 825 'selectHosts' => ['hostid'], 826 'triggerids' => $dep_triggerids, 827 'preservekeys' => true 828 ]); 829 $dep_triggers = CMacrosResolverHelper::resolveTriggerExpressions($dep_triggers); 830 831 // Map dependencies to the new trigger IDs and save. 832 foreach ($trigger_prototypes as &$trigger_prototype) { 833 // Get corresponding created trigger prototype ID. 834 $new_trigger_prototype = $new_trigger_prototypes[$trigger_prototype['triggerid']]; 835 836 if ($trigger_prototype['dependencies']) { 837 foreach ($trigger_prototype['dependencies'] as &$dependency) { 838 $dep_triggerid = $dependency['triggerid']; 839 840 /* 841 * We have added a dependent trigger prototype and we know corresponding trigger prototype ID 842 * for newly created trigger prototype. 843 */ 844 if (array_key_exists($dependency['triggerid'], $new_trigger_prototypes)) { 845 /* 846 * Dependency is within same host according to $srcHostId parameter or dep trigger has 847 * single host. 848 */ 849 if ($new_trigger_prototype['src_hostid'] == 850 $new_trigger_prototypes[$dep_triggerid]['src_hostid']) { 851 $dependency['triggerid'] = $new_trigger_prototypes[$dep_triggerid]['new_triggerid']; 852 } 853 } 854 elseif (in_array(['hostid' => $new_trigger_prototype['src_hostid']], 855 $dep_triggers[$dep_triggerid]['hosts'])) { 856 // Get all possible $depTrigger matching triggers by description. 857 $target_triggers = API::Trigger()->get([ 858 'output' => ['hosts', 'triggerid', 'expression'], 859 'hostids' => $new_trigger_prototype['new_hostid'], 860 'filter' => ['description' => $dep_triggers[$dep_triggerid]['description']], 861 'preservekeys' => true 862 ]); 863 $target_triggers = CMacrosResolverHelper::resolveTriggerExpressions($target_triggers); 864 865 // Compare exploded expressions for exact match. 866 $expr1 = $dep_triggers[$dep_triggerid]['expression']; 867 $dependency['triggerid'] = null; 868 869 foreach ($target_triggers as $target_trigger) { 870 $expr2 = triggerExpressionReplaceHost($target_trigger['expression'], 871 $new_trigger_prototype['new_host'], 872 $new_trigger_prototype['src_host'] 873 ); 874 875 if ($expr2 === $expr1) { 876 // Matching trigger has been found. 877 $dependency['triggerid'] = $target_trigger['triggerid']; 878 break; 879 } 880 } 881 882 // If matching trigger was not found, raise exception. 883 if ($dependency['triggerid'] === null) { 884 $expr2 = triggerExpressionReplaceHost($dep_triggers[$dep_triggerid]['expression'], 885 $new_trigger_prototype['src_host'], 886 $new_trigger_prototype['new_host'] 887 ); 888 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 889 'Cannot add dependency from trigger "%1$s:%2$s" to non existing trigger "%3$s:%4$s".', 890 $trigger_prototype['description'], 891 $trigger_prototype['expression'], 892 $dep_triggers[$dep_triggerid]['description'], 893 $expr2 894 )); 895 } 896 } 897 } 898 unset($dependency); 899 900 $trigger_prototype['triggerid'] = $new_trigger_prototype['new_triggerid']; 901 } 902 } 903 unset($trigger_prototype); 904 905 // If adding a dependency fails, the exception will be raised in TriggerPrototype API. 906 API::TriggerPrototype()->addDependencies($trigger_prototypes); 907 } 908 909 return $result; 910 } 911 912 protected function createReal(array &$items) { 913 $items_rtdata = []; 914 $create_items = []; 915 916 // create items without formulas, they will be updated when items and conditions are saved 917 foreach ($items as $key => $item) { 918 if (array_key_exists('filter', $item)) { 919 $item['evaltype'] = $item['filter']['evaltype']; 920 unset($item['filter']); 921 } 922 923 if (array_key_exists('rtdata', $item)) { 924 $items_rtdata[$key] = []; 925 unset($item['rtdata']); 926 } 927 928 $create_items[] = $item; 929 } 930 $create_items = DB::save('items', $create_items); 931 932 foreach ($items_rtdata as $key => &$value) { 933 $value['itemid'] = $create_items[$key]['itemid']; 934 } 935 unset($value); 936 937 DB::insert('item_rtdata', $items_rtdata, false); 938 939 $conditions = []; 940 $itemids = []; 941 942 foreach ($items as $key => &$item) { 943 $item['itemid'] = $create_items[$key]['itemid']; 944 $itemids[$key] = $item['itemid']; 945 946 // conditions 947 if (isset($item['filter'])) { 948 foreach ($item['filter']['conditions'] as $condition) { 949 $condition['itemid'] = $item['itemid']; 950 951 $conditions[] = $condition; 952 } 953 } 954 } 955 unset($item); 956 957 $conditions = DB::save('item_condition', $conditions); 958 959 $item_conditions = []; 960 961 foreach ($conditions as $condition) { 962 $item_conditions[$condition['itemid']][] = $condition; 963 } 964 965 $lld_macro_paths = []; 966 967 foreach ($items as $item) { 968 // update formulas 969 if (isset($item['filter']) && $item['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 970 $this->updateFormula($item['itemid'], $item['filter']['formula'], $item_conditions[$item['itemid']]); 971 } 972 973 // $item['lld_macro_paths'] expects to be filled with validated fields 'lld_macro' and 'path' and values. 974 if (array_key_exists('lld_macro_paths', $item)) { 975 foreach ($item['lld_macro_paths'] as $lld_macro_path) { 976 $lld_macro_paths[] = $lld_macro_path + ['itemid' => $item['itemid']]; 977 } 978 } 979 } 980 981 DB::insertBatch('lld_macro_path', $lld_macro_paths); 982 983 $this->createItemParameters($items, $itemids); 984 $this->createItemPreprocessing($items); 985 $this->createOverrides($items); 986 } 987 988 /** 989 * Creates overrides for low-level discovery rules. 990 * 991 * @param array $items Low-level discovery rules. 992 */ 993 protected function createOverrides(array $items) { 994 $overrides = []; 995 996 foreach ($items as $item) { 997 if (array_key_exists('overrides', $item)) { 998 foreach ($item['overrides'] as $override) { 999 // Formula will be added after conditions. 1000 $new_override = [ 1001 'itemid' => $item['itemid'], 1002 'name' => $override['name'], 1003 'step' => $override['step'], 1004 'stop' => array_key_exists('stop', $override) ? $override['stop'] : ZBX_LLD_OVERRIDE_STOP_NO 1005 ]; 1006 1007 $new_override['evaltype'] = array_key_exists('filter', $override) 1008 ? $override['filter']['evaltype'] 1009 : DB::getDefault('lld_override', 'evaltype'); 1010 1011 $overrides[] = $new_override; 1012 } 1013 } 1014 } 1015 1016 $overrideids = DB::insertBatch('lld_override', $overrides); 1017 1018 if ($overrideids) { 1019 $ovrd_conditions = []; 1020 $ovrd_idx = 0; 1021 $cnd_idx = 0; 1022 1023 foreach ($items as &$item) { 1024 if (array_key_exists('overrides', $item)) { 1025 foreach ($item['overrides'] as &$override) { 1026 $override['lld_overrideid'] = $overrideids[$ovrd_idx++]; 1027 1028 if (array_key_exists('filter', $override)) { 1029 foreach ($override['filter']['conditions'] as $condition) { 1030 $ovrd_conditions[] = [ 1031 'macro' => $condition['macro'], 1032 'value' => $condition['value'], 1033 'formulaid' => array_key_exists('formulaid', $condition) 1034 ? $condition['formulaid'] 1035 : '', 1036 'operator' => array_key_exists('operator', $condition) 1037 ? $condition['operator'] 1038 : DB::getDefault('lld_override_condition', 'operator'), 1039 'lld_overrideid' => $override['lld_overrideid'] 1040 ]; 1041 } 1042 } 1043 } 1044 unset($override); 1045 } 1046 } 1047 unset($item); 1048 1049 $conditionids = DB::insertBatch('lld_override_condition', $ovrd_conditions); 1050 1051 $ids = []; 1052 1053 if ($conditionids) { 1054 foreach ($items as &$item) { 1055 if (array_key_exists('overrides', $item)) { 1056 foreach ($item['overrides'] as &$override) { 1057 if (array_key_exists('filter', $override)) { 1058 foreach ($override['filter']['conditions'] as &$condition) { 1059 $condition['lld_override_conditionid'] = $conditionids[$cnd_idx++]; 1060 } 1061 unset($condition); 1062 } 1063 } 1064 unset($override); 1065 } 1066 } 1067 unset($item); 1068 1069 foreach ($items as $item) { 1070 if (array_key_exists('overrides', $item)) { 1071 foreach ($item['overrides'] as $override) { 1072 if (array_key_exists('filter', $override) 1073 && $override['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1074 $ids = []; 1075 foreach ($override['filter']['conditions'] as $condition) { 1076 $ids[$condition['formulaid']] = $condition['lld_override_conditionid']; 1077 } 1078 1079 $formula = CConditionHelper::replaceLetterIds($override['filter']['formula'], $ids); 1080 DB::updateByPk('lld_override', $override['lld_overrideid'], ['formula' => $formula]); 1081 } 1082 } 1083 } 1084 } 1085 } 1086 1087 $operations = []; 1088 foreach ($items as $item) { 1089 if (array_key_exists('overrides', $item)) { 1090 foreach ($item['overrides'] as $override) { 1091 if (array_key_exists('operations', $override)) { 1092 foreach ($override['operations'] as $operation) { 1093 $operations[] = [ 1094 'lld_overrideid' => $override['lld_overrideid'], 1095 'operationobject' => $operation['operationobject'], 1096 'operator' => array_key_exists('operator', $operation) 1097 ? $operation['operator'] 1098 : DB::getDefault('lld_override_operation', 'operator'), 1099 'value' => array_key_exists('value', $operation) ? $operation['value'] : '' 1100 ]; 1101 } 1102 } 1103 } 1104 } 1105 } 1106 1107 $operationids = DB::insertBatch('lld_override_operation', $operations); 1108 1109 $opr_idx = 0; 1110 $opstatus = []; 1111 $opdiscover = []; 1112 $opperiod = []; 1113 $ophistory = []; 1114 $optrends = []; 1115 $opseverity = []; 1116 $optag = []; 1117 $optemplate = []; 1118 $opinventory = []; 1119 1120 foreach ($items as $item) { 1121 if (array_key_exists('overrides', $item)) { 1122 foreach ($item['overrides'] as $override) { 1123 if (array_key_exists('operations', $override)) { 1124 foreach ($override['operations'] as $operation) { 1125 $operation['lld_override_operationid'] = $operationids[$opr_idx++]; 1126 1127 // Discover status applies to all operation object types. 1128 if (array_key_exists('opdiscover', $operation)) { 1129 $opdiscover[] = [ 1130 'lld_override_operationid' => $operation['lld_override_operationid'], 1131 'discover' => $operation['opdiscover']['discover'] 1132 ]; 1133 } 1134 1135 switch ($operation['operationobject']) { 1136 case OPERATION_OBJECT_ITEM_PROTOTYPE: 1137 if (array_key_exists('opstatus', $operation)) { 1138 $opstatus[] = [ 1139 'lld_override_operationid' => $operation['lld_override_operationid'], 1140 'status' => $operation['opstatus']['status'] 1141 ]; 1142 } 1143 1144 if (array_key_exists('opperiod', $operation)) { 1145 $opperiod[] = [ 1146 'lld_override_operationid' => $operation['lld_override_operationid'], 1147 'delay' => $operation['opperiod']['delay'] 1148 ]; 1149 } 1150 1151 if (array_key_exists('ophistory', $operation)) { 1152 $ophistory[] = [ 1153 'lld_override_operationid' => $operation['lld_override_operationid'], 1154 'history' => $operation['ophistory']['history'] 1155 ]; 1156 } 1157 1158 if (array_key_exists('optrends', $operation)) { 1159 $optrends[] = [ 1160 'lld_override_operationid' => $operation['lld_override_operationid'], 1161 'trends' => $operation['optrends']['trends'] 1162 ]; 1163 } 1164 1165 if (array_key_exists('optag', $operation)) { 1166 foreach ($operation['optag'] as $tag) { 1167 $optag[] = [ 1168 'lld_override_operationid' => 1169 $operation['lld_override_operationid'], 1170 'tag' => $tag['tag'], 1171 'value' => array_key_exists('value', $tag) ? $tag['value'] : '' 1172 ]; 1173 } 1174 } 1175 break; 1176 1177 case OPERATION_OBJECT_TRIGGER_PROTOTYPE: 1178 if (array_key_exists('opstatus', $operation)) { 1179 $opstatus[] = [ 1180 'lld_override_operationid' => $operation['lld_override_operationid'], 1181 'status' => $operation['opstatus']['status'] 1182 ]; 1183 } 1184 1185 if (array_key_exists('opseverity', $operation)) { 1186 $opseverity[] = [ 1187 'lld_override_operationid' => $operation['lld_override_operationid'], 1188 'severity' => $operation['opseverity']['severity'] 1189 ]; 1190 } 1191 1192 if (array_key_exists('optag', $operation)) { 1193 foreach ($operation['optag'] as $tag) { 1194 $optag[] = [ 1195 'lld_override_operationid' => 1196 $operation['lld_override_operationid'], 1197 'tag' => $tag['tag'], 1198 'value' => array_key_exists('value', $tag) ? $tag['value'] : '' 1199 ]; 1200 } 1201 } 1202 break; 1203 1204 case OPERATION_OBJECT_HOST_PROTOTYPE: 1205 if (array_key_exists('opstatus', $operation)) { 1206 $opstatus[] = [ 1207 'lld_override_operationid' => $operation['lld_override_operationid'], 1208 'status' => $operation['opstatus']['status'] 1209 ]; 1210 } 1211 1212 if (array_key_exists('optemplate', $operation)) { 1213 foreach ($operation['optemplate'] as $template) { 1214 $optemplate[] = [ 1215 'lld_override_operationid' => 1216 $operation['lld_override_operationid'], 1217 'templateid' => $template['templateid'] 1218 ]; 1219 } 1220 } 1221 1222 if (array_key_exists('optag', $operation)) { 1223 foreach ($operation['optag'] as $tag) { 1224 $optag[] = [ 1225 'lld_override_operationid' => 1226 $operation['lld_override_operationid'], 1227 'tag' => $tag['tag'], 1228 'value' => array_key_exists('value', $tag) ? $tag['value'] : '' 1229 ]; 1230 } 1231 } 1232 1233 if (array_key_exists('opinventory', $operation)) { 1234 $opinventory[] = [ 1235 'lld_override_operationid' => $operation['lld_override_operationid'], 1236 'inventory_mode' => $operation['opinventory']['inventory_mode'] 1237 ]; 1238 } 1239 break; 1240 } 1241 } 1242 } 1243 } 1244 } 1245 } 1246 1247 DB::insertBatch('lld_override_opstatus', $opstatus, false); 1248 DB::insertBatch('lld_override_opdiscover', $opdiscover, false); 1249 DB::insertBatch('lld_override_opperiod', $opperiod, false); 1250 DB::insertBatch('lld_override_ophistory', $ophistory, false); 1251 DB::insertBatch('lld_override_optrends', $optrends, false); 1252 DB::insertBatch('lld_override_opseverity', $opseverity, false); 1253 DB::insertBatch('lld_override_optag', $optag); 1254 DB::insertBatch('lld_override_optemplate', $optemplate); 1255 DB::insertBatch('lld_override_opinventory', $opinventory, false); 1256 } 1257 } 1258 1259 protected function updateReal(array $items) { 1260 CArrayHelper::sort($items, ['itemid']); 1261 1262 $ruleIds = zbx_objectValues($items, 'itemid'); 1263 1264 $data = []; 1265 foreach ($items as $item) { 1266 $values = $item; 1267 1268 if (isset($item['filter'])) { 1269 // clear the formula for non-custom expression rules 1270 if ($item['filter']['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) { 1271 $values['formula'] = ''; 1272 } 1273 1274 $values['evaltype'] = $item['filter']['evaltype']; 1275 unset($values['filter']); 1276 } 1277 1278 $data[] = ['values' => $values, 'where' => ['itemid' => $item['itemid']]]; 1279 } 1280 DB::update('items', $data); 1281 1282 $newRuleConditions = null; 1283 foreach ($items as $item) { 1284 // conditions 1285 if (isset($item['filter'])) { 1286 if ($newRuleConditions === null) { 1287 $newRuleConditions = []; 1288 } 1289 1290 $newRuleConditions[$item['itemid']] = []; 1291 foreach ($item['filter']['conditions'] as $condition) { 1292 $condition['itemid'] = $item['itemid']; 1293 1294 $newRuleConditions[$item['itemid']][] = $condition; 1295 } 1296 } 1297 } 1298 1299 // replace conditions 1300 $ruleConditions = []; 1301 if ($newRuleConditions !== null) { 1302 // fetch existing conditions 1303 $exConditions = DBfetchArray(DBselect( 1304 'SELECT item_conditionid,itemid,macro,value,operator'. 1305 ' FROM item_condition'. 1306 ' WHERE '.dbConditionInt('itemid', $ruleIds). 1307 ' ORDER BY item_conditionid' 1308 )); 1309 $exRuleConditions = []; 1310 foreach ($exConditions as $condition) { 1311 $exRuleConditions[$condition['itemid']][] = $condition; 1312 } 1313 1314 // replace and add the new IDs 1315 $conditions = DB::replaceByPosition('item_condition', $exRuleConditions, $newRuleConditions); 1316 foreach ($conditions as $condition) { 1317 $ruleConditions[$condition['itemid']][] = $condition; 1318 } 1319 } 1320 1321 $itemids = []; 1322 $lld_macro_paths = []; 1323 $db_lld_macro_paths = []; 1324 1325 foreach ($items as $item) { 1326 // update formulas 1327 if (isset($item['filter']) && $item['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1328 $this->updateFormula($item['itemid'], $item['filter']['formula'], $ruleConditions[$item['itemid']]); 1329 } 1330 1331 // "lld_macro_paths" could be empty or filled with fields "lld_macro", "path" or "lld_macro_pathid". 1332 if (array_key_exists('lld_macro_paths', $item)) { 1333 $itemids[$item['itemid']] = true; 1334 1335 if ($item['lld_macro_paths']) { 1336 foreach ($item['lld_macro_paths'] as $lld_macro_path) { 1337 $lld_macro_paths[] = $lld_macro_path + ['itemid' => $item['itemid']]; 1338 } 1339 } 1340 } 1341 } 1342 1343 // Gather all existing LLD macros from given discovery rules. 1344 if ($itemids) { 1345 $db_lld_macro_paths = DB::select('lld_macro_path', [ 1346 'output' => ['lld_macro_pathid', 'itemid', 'lld_macro', 'path'], 1347 'filter' => ['itemid' => array_keys($itemids)] 1348 ]); 1349 } 1350 1351 /* 1352 * DB::replaceByPosition() does not allow to change records one by one due to unique indexes on two table 1353 * columns. Problems arise when given records are the same as records in DB and they are sorted differently. 1354 * That's why checking differences between old and new records is done manually. 1355 */ 1356 1357 $lld_macro_paths_to_update = []; 1358 1359 foreach ($lld_macro_paths as $idx1 => $lld_macro_path) { 1360 foreach ($db_lld_macro_paths as $idx2 => $db_lld_macro_path) { 1361 if (array_key_exists('lld_macro_pathid', $lld_macro_path)) { 1362 // Update records by primary key. 1363 1364 // Find matching "lld_macro_pathid" and update fields accordingly. 1365 if (bccomp($lld_macro_path['lld_macro_pathid'], $db_lld_macro_path['lld_macro_pathid']) == 0) { 1366 $fields_to_update = []; 1367 1368 if (array_key_exists('lld_macro', $lld_macro_path) 1369 && $lld_macro_path['lld_macro'] === $db_lld_macro_path['lld_macro']) { 1370 // If same "lld_macro" is found in DB, update only "path" if necessary. 1371 1372 if (array_key_exists('path', $lld_macro_path) 1373 && $lld_macro_path['path'] !== $lld_macro_path['path']) { 1374 $fields_to_update['path'] = $lld_macro_path['path']; 1375 } 1376 } 1377 else { 1378 /* 1379 * Update all other fields that correspond to given "lld_macro_pathid". Except for primary 1380 * key "lld_macro_pathid" and "itemid". 1381 */ 1382 1383 foreach ($lld_macro_path as $field => $value) { 1384 if ($field !== 'itemid' && $field !== 'lld_macro_pathid') { 1385 $fields_to_update[$field] = $value; 1386 } 1387 } 1388 } 1389 1390 /* 1391 * If there are any changes made, update fields in DB. Otherwise skip updating and result in 1392 * success anyway. 1393 */ 1394 if ($fields_to_update) { 1395 $lld_macro_paths_to_update[] = $fields_to_update 1396 + ['lld_macro_pathid' => $lld_macro_path['lld_macro_pathid']]; 1397 } 1398 1399 /* 1400 * Remove processed LLD macros from the list. Macros left in $db_lld_macro_paths will be removed 1401 * afterwards. 1402 */ 1403 unset($db_lld_macro_paths[$idx2]); 1404 unset($lld_macro_paths[$idx1]); 1405 } 1406 // Incorrect "lld_macro_pathid" cannot be given due to validation done previously. 1407 } 1408 else { 1409 // Add or update fields by given "lld_macro". 1410 1411 if (bccomp($lld_macro_path['itemid'], $db_lld_macro_path['itemid']) == 0) { 1412 if ($lld_macro_path['lld_macro'] === $db_lld_macro_path['lld_macro']) { 1413 // If same "lld_macro" is given, add primary key and update only "path", if necessary. 1414 1415 if ($lld_macro_path['path'] !== $db_lld_macro_path['path']) { 1416 $lld_macro_paths_to_update[] = [ 1417 'lld_macro_pathid' => $db_lld_macro_path['lld_macro_pathid'], 1418 'path' => $lld_macro_path['path'] 1419 ]; 1420 } 1421 1422 /* 1423 * Remove processed LLD macros from the list. Macros left in $db_lld_macro_paths will 1424 * be removed afterwards. And macros left in $lld_macro_paths will be created. 1425 */ 1426 unset($db_lld_macro_paths[$idx2]); 1427 unset($lld_macro_paths[$idx1]); 1428 } 1429 } 1430 } 1431 } 1432 } 1433 1434 // After all data has been collected, proceed with record update in DB. 1435 $lld_macro_pathids_to_delete = zbx_objectValues($db_lld_macro_paths, 'lld_macro_pathid'); 1436 1437 if ($lld_macro_pathids_to_delete) { 1438 DB::delete('lld_macro_path', ['lld_macro_pathid' => $lld_macro_pathids_to_delete]); 1439 } 1440 1441 if ($lld_macro_paths_to_update) { 1442 $data = []; 1443 1444 foreach ($lld_macro_paths_to_update as $lld_macro_path) { 1445 $data[] = [ 1446 'values' => $lld_macro_path, 1447 'where' => [ 1448 'lld_macro_pathid' => $lld_macro_path['lld_macro_pathid'] 1449 ] 1450 ]; 1451 } 1452 1453 DB::update('lld_macro_path', $data); 1454 } 1455 1456 DB::insertBatch('lld_macro_path', $lld_macro_paths); 1457 1458 $this->updateItemParameters($items); 1459 $this->updateItemPreprocessing($items); 1460 1461 // Delete old overrides and replace with new ones if any. 1462 $ovrd_itemids = []; 1463 foreach ($items as $item) { 1464 if (array_key_exists('overrides', $item)) { 1465 $ovrd_itemids[$item['itemid']] = true; 1466 } 1467 } 1468 1469 if ($ovrd_itemids) { 1470 DBexecute('DELETE FROM lld_override WHERE '.dbConditionId('itemid', array_keys($ovrd_itemids))); 1471 } 1472 1473 $this->createOverrides($items); 1474 } 1475 1476 /** 1477 * Converts a formula with letters to a formula with IDs and updates it. 1478 * 1479 * @param string $itemId 1480 * @param string $evalFormula formula with letters 1481 * @param array $conditions 1482 */ 1483 protected function updateFormula($itemId, $evalFormula, array $conditions) { 1484 $ids = []; 1485 foreach ($conditions as $condition) { 1486 $ids[$condition['formulaid']] = $condition['item_conditionid']; 1487 } 1488 $formula = CConditionHelper::replaceLetterIds($evalFormula, $ids); 1489 1490 DB::updateByPk('items', $itemId, [ 1491 'formula' => $formula 1492 ]); 1493 } 1494 1495 /** 1496 * Check item data and set missing default values. 1497 * 1498 * @param array $items passed by reference 1499 * @param bool $update 1500 * @param array $dbItems 1501 */ 1502 protected function checkInput(array &$items, $update = false, array $dbItems = []) { 1503 // add the values that cannot be changed, but are required for further processing 1504 foreach ($items as &$item) { 1505 $item['flags'] = ZBX_FLAG_DISCOVERY_RULE; 1506 $item['value_type'] = ITEM_VALUE_TYPE_TEXT; 1507 1508 // unset fields that are updated using the 'filter' parameter 1509 unset($item['evaltype']); 1510 unset($item['formula']); 1511 } 1512 unset($item); 1513 1514 parent::checkInput($items, $update); 1515 1516 $validateItems = $items; 1517 if ($update) { 1518 $validateItems = $this->extendFromObjects(zbx_toHash($validateItems, 'itemid'), $dbItems, ['name']); 1519 } 1520 1521 // filter validator 1522 $filterValidator = new CSchemaValidator($this->getFilterSchema()); 1523 1524 // condition validation 1525 $conditionValidator = new CSchemaValidator($this->getFilterConditionSchema()); 1526 foreach ($validateItems as $item) { 1527 // validate custom formula and conditions 1528 if (isset($item['filter'])) { 1529 $filterValidator->setObjectName($item['name']); 1530 $this->checkValidator($item['filter'], $filterValidator); 1531 1532 foreach ($item['filter']['conditions'] as $condition) { 1533 $conditionValidator->setObjectName($item['name']); 1534 $this->checkValidator($condition, $conditionValidator); 1535 } 1536 } 1537 } 1538 1539 $this->validateOverrides($validateItems); 1540 } 1541 1542 /** 1543 * Validate low-level discovery rule overrides. 1544 * 1545 * @param array $items Low-level discovery rules. 1546 * 1547 * @throws APIException 1548 */ 1549 protected function validateOverrides(array $items): void { 1550 $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['name'], ['step']], 'fields' => [ 1551 'step' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '1:'.ZBX_MAX_INT32], 1552 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_override', 'name')], 1553 'stop' => ['type' => API_INT32, 'in' => implode(',', [ZBX_LLD_OVERRIDE_STOP_NO, ZBX_LLD_OVERRIDE_STOP_YES]), 'default' => ZBX_LLD_OVERRIDE_STOP_NO], 1554 'filter' => ['type' => API_OBJECT, 'fields' => [ 1555 'evaltype' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [CONDITION_EVAL_TYPE_AND_OR, CONDITION_EVAL_TYPE_AND, CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_EXPRESSION])], 1556 'formula' => ['type' => API_STRING_UTF8], 1557 'conditions' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'fields' => [ 1558 'macro' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_override_condition', 'macro')], 1559 'operator' => ['type' => API_INT32, 'in' => implode(',', [CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP, CONDITION_OPERATOR_EXISTS, CONDITION_OPERATOR_NOT_EXISTS]), 'default' => DB::getDefault('lld_override_condition', 'operator')], 1560 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('lld_override_condition', 'value')], 1561 'formulaid' => ['type' => API_STRING_UTF8] 1562 ]] 1563 ]], 1564 'operations' => ['type' => API_OBJECTS, 'fields' => [ 1565 'operationobject' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE, OPERATION_OBJECT_TRIGGER_PROTOTYPE, OPERATION_OBJECT_GRAPH_PROTOTYPE, OPERATION_OBJECT_HOST_PROTOTYPE])], 1566 'operator' => ['type' => API_INT32, 'in' => implode(',', [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE, CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP]), 'default' => DB::getDefault('lld_override_operation', 'operator')], 1567 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('lld_override_operation', 'value')], 1568 'opstatus' => ['type' => API_OBJECT, 'fields' => [ 1569 'status' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PROTOTYPE_STATUS_ENABLED, ZBX_PROTOTYPE_STATUS_DISABLED])] 1570 ]], 1571 'opdiscover' => ['type' => API_OBJECT, 'fields' => [ 1572 'discover' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PROTOTYPE_DISCOVER, ZBX_PROTOTYPE_NO_DISCOVER])] 1573 ]], 1574 'opperiod' => ['type' => API_OBJECT, 'fields' => [ 1575 'delay' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_override_opperiod', 'delay')] 1576 ]], 1577 'ophistory' => ['type' => API_OBJECT, 'fields' => [ 1578 'history' => ['type' => API_TIME_UNIT, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('lld_override_ophistory', 'history')] 1579 ]], 1580 'optrends' => ['type' => API_OBJECT, 'fields' => [ 1581 'trends' => ['type' => API_TIME_UNIT, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('lld_override_optrends', 'trends')] 1582 ]], 1583 'opseverity' => ['type' => API_OBJECT, 'fields' => [ 1584 'severity' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1))] 1585 ]], 1586 'optag' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['tag', 'value']], 'fields' => [ 1587 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_override_optag', 'tag')], 1588 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('lld_override_optag', 'value'), 'default' => DB::getDefault('lld_override_optag', 'value')] 1589 ]], 1590 'optemplate' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'fields' => [ 1591 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] 1592 ]], 1593 'opinventory' => ['type' => API_OBJECT, 'fields' => [ 1594 'inventory_mode' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC])] 1595 ]] 1596 ]] 1597 ]]; 1598 1599 // Schema for filter is already validated in API validator. Create the formula validator for filter. 1600 $condition_validator = new CConditionValidator([ 1601 'messageMissingFormula' => _('Formula missing for override "%1$s".'), 1602 'messageInvalidFormula' => _('Incorrect custom expression "%2$s" for override "%1$s": %3$s.'), 1603 'messageMissingCondition' => 1604 _('Condition "%2$s" used in formula "%3$s" for override "%1$s" is not defined.'), 1605 'messageUnusedCondition' => _('Condition "%2$s" is not used in formula "%3$s" for override "%1$s".') 1606 ]); 1607 1608 $update_interval_parser = new CUpdateIntervalParser([ 1609 'usermacros' => true, 1610 'lldmacros' => true 1611 ]); 1612 1613 $lld_idx = 0; 1614 foreach ($items as $item) { 1615 if (array_key_exists('overrides', $item)) { 1616 $path = '/'.(++$lld_idx).'/overrides'; 1617 1618 if (!CApiInputValidator::validate($api_input_rules, $item['overrides'], $path, $error)) { 1619 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1620 } 1621 1622 foreach ($item['overrides'] as $ovrd_idx => $override) { 1623 if (array_key_exists('filter', $override)) { 1624 $condition_validator->setObjectName($override['name']); 1625 1626 // Validate the formula and check if they are in the conditions. 1627 if (!$condition_validator->validate($override['filter'])) { 1628 self::exception(ZBX_API_ERROR_PARAMETERS, $condition_validator->getError()); 1629 } 1630 1631 // Validate that conditions have correct macros and 'formulaid' for custom expressions. 1632 if (array_key_exists('conditions', $override['filter'])) { 1633 foreach ($override['filter']['conditions'] as $cnd_idx => $condition) { 1634 // API validator only checks if 'macro' field exists and is not empty. It must be macro. 1635 if (!preg_match('/^'.ZBX_PREG_EXPRESSION_LLD_MACROS.'$/', $condition['macro'])) { 1636 self::exception(ZBX_API_ERROR_PARAMETERS, 1637 _s('Incorrect filter condition macro for override "%1$s".', $override['name']) 1638 ); 1639 } 1640 1641 if ($override['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1642 /* 1643 * Check only if 'formulaid' exists. It cannot be empty or incorrect, but that is 1644 * already validated by previously set conditionValidator. 1645 */ 1646 if (!array_key_exists('formulaid', $condition)) { 1647 $cond_path = $path.'/'.($ovrd_idx + 1).'/filter/conditions/'.($cnd_idx + 1); 1648 self::exception(ZBX_API_ERROR_PARAMETERS, 1649 _s('Invalid parameter "%1$s": %2$s.', $path, 1650 _s('the parameter "%1$s" is missing', 'formulaid') 1651 ) 1652 ); 1653 } 1654 } 1655 } 1656 } 1657 } 1658 1659 // Check integrity of 'overrideobject' and its fields. 1660 if (array_key_exists('operations', $override)) { 1661 foreach ($override['operations'] as $opr_idx => $operation) { 1662 $opr_path = $path.'/'.($ovrd_idx + 1).'/operations/'.($opr_idx + 1); 1663 1664 switch ($operation['operationobject']) { 1665 case OPERATION_OBJECT_ITEM_PROTOTYPE: 1666 foreach (['opseverity', 'optemplate', 'opinventory'] as $field) { 1667 if (array_key_exists($field, $operation)) { 1668 self::exception(ZBX_API_ERROR_PARAMETERS, 1669 _s('Invalid parameter "%1$s": %2$s.', $opr_path, 1670 _s('unexpected parameter "%1$s"', $field) 1671 ) 1672 ); 1673 } 1674 } 1675 1676 if (!array_key_exists('opstatus', $operation) 1677 && !array_key_exists('opperiod', $operation) 1678 && !array_key_exists('ophistory', $operation) 1679 && !array_key_exists('optrends', $operation) 1680 && !array_key_exists('optag', $operation) 1681 && !array_key_exists('opdiscover', $operation)) { 1682 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 1683 $opr_path, _s('value must be one of %1$s', 1684 'opstatus, opdiscover, opperiod, ophistory, optrends, optag' 1685 ) 1686 )); 1687 } 1688 1689 if (array_key_exists('opperiod', $operation) 1690 && !validateDelay($update_interval_parser, 'delay', 1691 $operation['opperiod']['delay'], $error)) { 1692 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1693 } 1694 break; 1695 1696 case OPERATION_OBJECT_TRIGGER_PROTOTYPE: 1697 foreach (['opperiod', 'ophistory', 'optrends', 'optemplate', 'opinventory'] as 1698 $field) { 1699 if (array_key_exists($field, $operation)) { 1700 self::exception(ZBX_API_ERROR_PARAMETERS, 1701 _s('Invalid parameter "%1$s": %2$s.', $opr_path, 1702 _s('unexpected parameter "%1$s"', $field) 1703 ) 1704 ); 1705 } 1706 } 1707 1708 if (!array_key_exists('opstatus', $operation) 1709 && !array_key_exists('opseverity', $operation) 1710 && !array_key_exists('optag', $operation) 1711 && !array_key_exists('opdiscover', $operation)) { 1712 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 1713 $opr_path, 1714 _s('value must be one of %1$s', 'opstatus, opdiscover, opseverity, optag') 1715 )); 1716 } 1717 break; 1718 1719 case OPERATION_OBJECT_GRAPH_PROTOTYPE: 1720 foreach (['opstatus', 'opperiod', 'ophistory', 'optrends', 'opseverity', 'optag', 1721 'optemplate', 'opinventory'] as $field) { 1722 if (array_key_exists($field, $operation)) { 1723 self::exception(ZBX_API_ERROR_PARAMETERS, 1724 _s('Invalid parameter "%1$s": %2$s.', $opr_path, 1725 _s('unexpected parameter "%1$s"', $field) 1726 ) 1727 ); 1728 } 1729 } 1730 1731 if (!array_key_exists('opdiscover', $operation)) { 1732 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 1733 $opr_path, 1734 _s('value must be one of %1$s', 'opdiscover') 1735 )); 1736 } 1737 break; 1738 1739 case OPERATION_OBJECT_HOST_PROTOTYPE: 1740 foreach (['opperiod', 'ophistory', 'optrends', 'opseverity'] as $field) { 1741 if (array_key_exists($field, $operation)) { 1742 self::exception(ZBX_API_ERROR_PARAMETERS, 1743 _s('Invalid parameter "%1$s": %2$s.', $opr_path, 1744 _s('unexpected parameter "%1$s"', $field) 1745 ) 1746 ); 1747 } 1748 } 1749 1750 if (!array_key_exists('opstatus', $operation) 1751 && !array_key_exists('optemplate', $operation) 1752 && !array_key_exists('optag', $operation) 1753 && !array_key_exists('opinventory', $operation) 1754 && !array_key_exists('opdiscover', $operation)) { 1755 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 1756 $opr_path, _s('value must be one of %1$s', 1757 'opstatus, opdiscover, optemplate, optag, opinventory' 1758 ) 1759 )); 1760 } 1761 1762 if (array_key_exists('optemplate', $operation)) { 1763 $templates_cnt = API::Template()->get([ 1764 'countOutput' => true, 1765 'templateids' => zbx_objectValues($operation['optemplate'], 'templateid') 1766 ]); 1767 1768 if (count($operation['optemplate']) != $templates_cnt) { 1769 self::exception(ZBX_API_ERROR_PERMISSIONS, 1770 _('No permissions to referred object or it does not exist!') 1771 ); 1772 } 1773 } 1774 break; 1775 } 1776 } 1777 } 1778 } 1779 } 1780 } 1781 } 1782 1783 /** 1784 * Returns the parameters for creating a discovery rule filter validator. 1785 * 1786 * @return array 1787 */ 1788 protected function getFilterSchema() { 1789 return [ 1790 'validators' => [ 1791 'evaltype' => new CLimitedSetValidator([ 1792 'values' => [ 1793 CONDITION_EVAL_TYPE_OR, 1794 CONDITION_EVAL_TYPE_AND, 1795 CONDITION_EVAL_TYPE_AND_OR, 1796 CONDITION_EVAL_TYPE_EXPRESSION 1797 ], 1798 'messageInvalid' => _('Incorrect type of calculation for discovery rule "%1$s".') 1799 ]), 1800 'formula' => new CStringValidator([ 1801 'empty' => true 1802 ]), 1803 'conditions' => new CCollectionValidator([ 1804 'empty' => true, 1805 'messageInvalid' => _('Incorrect conditions for discovery rule "%1$s".') 1806 ]) 1807 ], 1808 'postValidators' => [ 1809 new CConditionValidator([ 1810 'messageMissingFormula' => _('Formula missing for discovery rule "%1$s".'), 1811 'messageInvalidFormula' => _('Incorrect custom expression "%2$s" for discovery rule "%1$s": %3$s.'), 1812 'messageMissingCondition' => _('Condition "%2$s" used in formula "%3$s" for discovery rule "%1$s" is not defined.'), 1813 'messageUnusedCondition' => _('Condition "%2$s" is not used in formula "%3$s" for discovery rule "%1$s".') 1814 ]) 1815 ], 1816 'required' => ['evaltype', 'conditions'], 1817 'messageRequired' => _('No "%2$s" given for the filter of discovery rule "%1$s".'), 1818 'messageUnsupported' => _('Unsupported parameter "%2$s" for the filter of discovery rule "%1$s".') 1819 ]; 1820 } 1821 1822 /** 1823 * Returns the parameters for creating a discovery rule filter condition validator. 1824 * 1825 * @return array 1826 */ 1827 protected function getFilterConditionSchema() { 1828 return [ 1829 'validators' => [ 1830 'macro' => new CStringValidator([ 1831 'regex' => '/^'.ZBX_PREG_EXPRESSION_LLD_MACROS.'$/', 1832 'messageEmpty' => _('Empty filter condition macro for discovery rule "%1$s".'), 1833 'messageRegex' => _('Incorrect filter condition macro for discovery rule "%1$s".') 1834 ]), 1835 'value' => new CStringValidator([ 1836 'empty' => true 1837 ]), 1838 'formulaid' => new CStringValidator([ 1839 'regex' => '/[A-Z]+/', 1840 'messageEmpty' => _('Empty filter condition formula ID for discovery rule "%1$s".'), 1841 'messageRegex' => _('Incorrect filter condition formula ID for discovery rule "%1$s".') 1842 ]), 1843 'operator' => new CLimitedSetValidator([ 1844 'values' => [CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP, CONDITION_OPERATOR_EXISTS, 1845 CONDITION_OPERATOR_NOT_EXISTS 1846 ], 1847 'messageInvalid' => _('Incorrect filter condition operator for discovery rule "%1$s".') 1848 ]) 1849 ], 1850 'required' => ['macro', 'value'], 1851 'messageRequired' => _('No "%2$s" given for a filter condition of discovery rule "%1$s".'), 1852 'messageUnsupported' => _('Unsupported parameter "%2$s" for a filter condition of discovery rule "%1$s".') 1853 ]; 1854 } 1855 1856 /** 1857 * Check discovery rule specific fields. 1858 * 1859 * @param array $item An array of single item data. 1860 * @param string $method A string of "create" or "update" method. 1861 * 1862 * @throws APIException if the input is invalid. 1863 */ 1864 protected function checkSpecificFields(array $item, $method) { 1865 if (array_key_exists('lifetime', $item) 1866 && !validateTimeUnit($item['lifetime'], SEC_PER_HOUR, 25 * SEC_PER_YEAR, true, $error, 1867 ['usermacros' => true])) { 1868 self::exception(ZBX_API_ERROR_PARAMETERS, 1869 _s('Incorrect value for field "%1$s": %2$s.', 'lifetime', $error) 1870 ); 1871 } 1872 } 1873 1874 /** 1875 * Checks if LLD macros contain duplicate names in "lld_macro". 1876 * 1877 * @param array $lld_macro_paths Array of items to validate. 1878 * @param string $lld_macro_paths[]['lld_macro'] LLD macro string (optional for update method). 1879 * @param array $macro_names Array where existing macro names are collected. 1880 * @param string $path Path to API object. 1881 * 1882 * @throws APIException if same discovery rules contains duplicate LLD macro names. 1883 */ 1884 protected function checkDuplicateLLDMacros(array $lld_macro_paths, $macro_names, $path) { 1885 foreach ($lld_macro_paths as $num => $lld_macro_path) { 1886 if (array_key_exists('lld_macro', $lld_macro_path)) { 1887 if (array_key_exists($lld_macro_path['lld_macro'], $macro_names)) { 1888 self::exception(ZBX_API_ERROR_PARAMETERS, 1889 _s('Invalid parameter "%1$s": %2$s.', $path.'/lld_macro_paths/'.($num + 1).'/lld_macro', 1890 _s('value "%1$s" already exists', $lld_macro_path['lld_macro']) 1891 ) 1892 ); 1893 } 1894 1895 $macro_names[$lld_macro_path['lld_macro']] = true; 1896 } 1897 } 1898 } 1899 1900 /** 1901 * Validates parameters in "lld_macro_paths" property for each item in create method. 1902 * 1903 * @param array $items Array of items to validate. 1904 * @param array $items[]['lld_macro_paths'] Array of LLD macro paths to validate for each 1905 * discovery rule (optional). 1906 * @param string $items[]['lld_macro_paths'][]['lld_macro'] LLD macro string. Required if "lld_macro_paths" exists. 1907 * @param string $items[]['lld_macro_paths'][]['path'] Path string. Validates as regular string. Required if 1908 * "lld_macro_paths" exists. 1909 * 1910 * @throws APIException if incorrect fields and values given. 1911 */ 1912 protected function validateCreateLLDMacroPaths(array $items) { 1913 $rules = [ 1914 'lld_macro_paths' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'fields' => [ 1915 'lld_macro' => ['type' => API_LLD_MACRO, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_macro_path', 'lld_macro')], 1916 'path' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_macro_path', 'path')] 1917 ]] 1918 ]; 1919 1920 foreach ($items as $key => $item) { 1921 if (array_key_exists('lld_macro_paths', $item)) { 1922 $item = array_intersect_key($item, $rules); 1923 $path = '/'.($key + 1); 1924 1925 if (!CApiInputValidator::validate(['type' => API_OBJECT, 'fields' => $rules], $item, $path, $error)) { 1926 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1927 } 1928 1929 $this->checkDuplicateLLDMacros($item['lld_macro_paths'], [], $path); 1930 } 1931 } 1932 } 1933 1934 /** 1935 * Validates parameters in "lld_macro_paths" property for each item in create method. 1936 * 1937 * @param array $items Array of items to validate. 1938 * @param array $items[]['lld_macro_paths'] Array of LLD macro paths to validate for each 1939 * discovery rule (optional). 1940 * @param string $items[]['lld_macro_paths'][]['lld_macro_pathid'] LLD macro path ID from DB (optional). 1941 * @param string $items[]['lld_macro_paths'][]['lld_macro'] LLD macro string. Required if "lld_macro_pathid" 1942 * does not exist. 1943 * @param string $items[]['lld_macro_paths'][]['path'] Path string. Validates as regular string. 1944 * Required if "lld_macro_pathid" and "lld_macro" 1945 * do not exist. 1946 * 1947 * @throws APIException if incorrect fields and values given. 1948 */ 1949 protected function validateUpdateLLDMacroPaths(array $items) { 1950 $rules = [ 1951 'lld_macro_paths' => ['type' => API_OBJECTS, 'fields' => [ 1952 'lld_macro_pathid' => ['type' => API_ID], 1953 'lld_macro' => ['type' => API_LLD_MACRO, 'length' => DB::getFieldLength('lld_macro_path', 'lld_macro')], 1954 'path' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_macro_path', 'path')] 1955 ]] 1956 ]; 1957 1958 $items = $this->extendObjects('items', $items, ['templateid']); 1959 1960 foreach ($items as $key => $item) { 1961 if (array_key_exists('lld_macro_paths', $item)) { 1962 $itemid = $item['itemid']; 1963 $templateid = $item['templateid']; 1964 1965 $item = array_intersect_key($item, $rules); 1966 $path = '/'.($key + 1); 1967 1968 if (!CApiInputValidator::validate(['type' => API_OBJECT, 'fields' => $rules], $item, $path, $error)) { 1969 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1970 } 1971 1972 if (array_key_exists('lld_macro_paths', $item)) { 1973 if ($templateid != 0) { 1974 self::exception(ZBX_API_ERROR_PARAMETERS, 1975 _s('Invalid parameter "%1$s": %2$s.', $path.'/lld_macro_paths', 1976 _('cannot update property for templated discovery rule') 1977 ) 1978 ); 1979 } 1980 1981 $lld_macro_pathids = []; 1982 1983 // Check that fields exists, are not empty, do not duplicate and collect IDs to compare with DB. 1984 foreach ($item['lld_macro_paths'] as $num => $lld_macro_path) { 1985 $subpath = $num + 1; 1986 1987 // API_NOT_EMPTY will not work, so we need at least one field to be present. 1988 if (!array_key_exists('lld_macro', $lld_macro_path) 1989 && !array_key_exists('path', $lld_macro_path) 1990 && !array_key_exists('lld_macro_pathid', $lld_macro_path)) { 1991 self::exception(ZBX_API_ERROR_PARAMETERS, 1992 _s('Invalid parameter "%1$s": %2$s.', $path.'/lld_macro_paths/'.$subpath, 1993 _('cannot be empty') 1994 ) 1995 ); 1996 } 1997 1998 // API 'uniq' => true will not work, because we validate API_ID not API_IDS. So make IDs unique. 1999 if (array_key_exists('lld_macro_pathid', $lld_macro_path)) { 2000 $lld_macro_pathids[$lld_macro_path['lld_macro_pathid']] = true; 2001 } 2002 else { 2003 /* 2004 * In case "lld_macro_pathid" does not exist, we need to treat it as a new LLD macro with 2005 * both fields present. 2006 */ 2007 if (array_key_exists('lld_macro', $lld_macro_path) 2008 && !array_key_exists('path', $lld_macro_path)) { 2009 self::exception(ZBX_API_ERROR_PARAMETERS, 2010 _s('Invalid parameter "%1$s": %2$s.', $path.'/lld_macro_paths/'.$subpath, 2011 _s('the parameter "%1$s" is missing', 'path') 2012 ) 2013 ); 2014 } 2015 elseif (array_key_exists('path', $lld_macro_path) 2016 && !array_key_exists('lld_macro', $lld_macro_path)) { 2017 self::exception(ZBX_API_ERROR_PARAMETERS, 2018 _s('Invalid parameter "%1$s": %2$s.', $path.'/lld_macro_paths/'.$subpath, 2019 _s('the parameter "%1$s" is missing', 'lld_macro') 2020 ) 2021 ); 2022 } 2023 } 2024 } 2025 2026 $this->checkDuplicateLLDMacros($item['lld_macro_paths'], [], $path); 2027 2028 /* 2029 * Validate "lld_macro_pathid" field. If "lld_macro_pathid" doesn't correspond to given "itemid" 2030 * or does not exist, throw an exception. 2031 */ 2032 if ($lld_macro_pathids) { 2033 $lld_macro_pathids = array_keys($lld_macro_pathids); 2034 2035 $db_lld_macro_paths = DBfetchArrayAssoc(DBselect( 2036 'SELECT lmp.lld_macro_pathid,lmp.lld_macro'. 2037 ' FROM lld_macro_path lmp'. 2038 ' WHERE lmp.itemid='.zbx_dbstr($itemid). 2039 ' AND '.dbConditionId('lmp.lld_macro_pathid', $lld_macro_pathids) 2040 ), 'lld_macro_pathid'); 2041 2042 if (count($db_lld_macro_paths) != count($lld_macro_pathids)) { 2043 self::exception(ZBX_API_ERROR_PERMISSIONS, 2044 _('No permissions to referred object or it does not exist!') 2045 ); 2046 } 2047 2048 $macro_names = []; 2049 2050 foreach ($item['lld_macro_paths'] as $num => $lld_macro_path) { 2051 if (array_key_exists('lld_macro_pathid', $lld_macro_path) 2052 && !array_key_exists('lld_macro', $lld_macro_path)) { 2053 $db_lld_macro_path = $db_lld_macro_paths[$lld_macro_path['lld_macro_pathid']]; 2054 $macro_names[$db_lld_macro_path['lld_macro']] = true; 2055 } 2056 } 2057 2058 $this->checkDuplicateLLDMacros($item['lld_macro_paths'], $macro_names, $path); 2059 } 2060 } 2061 } 2062 } 2063 } 2064 2065 /** 2066 * Copies the given discovery rule to the specified host. 2067 * 2068 * @throws APIException if the discovery rule interfaces could not be mapped 2069 * to the new host interfaces. 2070 * 2071 * @param string $discoveryid The ID of the discovery rule to be copied 2072 * @param string $hostid Destination host id 2073 * 2074 * @return bool 2075 */ 2076 protected function copyDiscoveryRule($discoveryid, $hostid) { 2077 // fetch discovery to clone 2078 $srcDiscovery = $this->get([ 2079 'itemids' => $discoveryid, 2080 'output' => ['itemid', 'type', 'snmp_oid', 'hostid', 'name', 'key_', 'delay', 'history', 'trends', 'status', 2081 'value_type', 'trapper_hosts', 'units', 'lastlogsize', 'logtimefmt', 'valuemapid', 'params', 2082 'ipmi_sensor', 'authtype', 'username', 'password', 'publickey', 'privatekey', 'mtime', 'flags', 2083 'interfaceid', 'description', 'inventory_link', 'lifetime', 'jmx_endpoint', 'url', 'query_fields', 2084 'parameters', 'timeout', 'posts', 'status_codes', 'follow_redirects', 'post_type', 'http_proxy', 2085 'headers', 'retrieve_mode', 'request_method', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 2086 'verify_peer', 'verify_host', 'allow_traps', 'master_itemid' 2087 ], 2088 'selectFilter' => ['evaltype', 'formula', 'conditions'], 2089 'selectLLDMacroPaths' => ['lld_macro', 'path'], 2090 'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'], 2091 'selectOverrides' => ['name', 'step', 'stop', 'filter', 'operations'], 2092 'preservekeys' => true 2093 ]); 2094 $srcDiscovery = reset($srcDiscovery); 2095 2096 // fetch source and destination hosts 2097 $hosts = API::Host()->get([ 2098 'hostids' => [$srcDiscovery['hostid'], $hostid], 2099 'output' => ['hostid', 'host', 'name', 'status'], 2100 'selectInterfaces' => API_OUTPUT_EXTEND, 2101 'templated_hosts' => true, 2102 'preservekeys' => true 2103 ]); 2104 $srcHost = $hosts[$srcDiscovery['hostid']]; 2105 $dstHost = $hosts[$hostid]; 2106 2107 $dstDiscovery = $srcDiscovery; 2108 $dstDiscovery['hostid'] = $hostid; 2109 unset($dstDiscovery['itemid']); 2110 if ($dstDiscovery['filter']) { 2111 foreach ($dstDiscovery['filter']['conditions'] as &$condition) { 2112 unset($condition['itemid'], $condition['item_conditionid']); 2113 } 2114 unset($condition); 2115 } 2116 2117 if (!$dstDiscovery['lld_macro_paths']) { 2118 unset($dstDiscovery['lld_macro_paths']); 2119 } 2120 2121 if ($dstDiscovery['overrides']) { 2122 foreach ($dstDiscovery['overrides'] as &$override) { 2123 if (array_key_exists('filter', $override)) { 2124 if (!$override['filter']['conditions']) { 2125 unset($override['filter']); 2126 } 2127 unset($override['filter']['eval_formula']); 2128 } 2129 } 2130 unset($override); 2131 } 2132 else { 2133 unset($dstDiscovery['overrides']); 2134 } 2135 2136 // if this is a plain host, map discovery interfaces 2137 if ($srcHost['status'] != HOST_STATUS_TEMPLATE) { 2138 // find a matching interface 2139 $interface = self::findInterfaceForItem($dstDiscovery['type'], $dstHost['interfaces']); 2140 if ($interface) { 2141 $dstDiscovery['interfaceid'] = $interface['interfaceid']; 2142 } 2143 // no matching interface found, throw an error 2144 elseif ($interface !== false) { 2145 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 2146 'Cannot find host interface on "%1$s" for item key "%2$s".', 2147 $dstHost['name'], 2148 $dstDiscovery['key_'] 2149 )); 2150 } 2151 } 2152 2153 // Master item should exists for LLD rule with type dependent item. 2154 if ($srcDiscovery['type'] == ITEM_TYPE_DEPENDENT) { 2155 $master_items = DBfetchArray(DBselect( 2156 'SELECT i1.itemid'. 2157 ' FROM items i1,items i2'. 2158 ' WHERE i1.key_=i2.key_'. 2159 ' AND i1.hostid='.zbx_dbstr($dstDiscovery['hostid']). 2160 ' AND i2.itemid='.zbx_dbstr($srcDiscovery['master_itemid']) 2161 )); 2162 2163 if (!$master_items) { 2164 self::exception(ZBX_API_ERROR_PERMISSIONS, 2165 _s('Discovery rule "%1$s" cannot be copied without its master item.', $srcDiscovery['name']) 2166 ); 2167 } 2168 2169 $dstDiscovery['master_itemid'] = $master_items[0]['itemid']; 2170 } 2171 2172 // save new discovery 2173 $newDiscovery = $this->create([$dstDiscovery]); 2174 $dstDiscovery['itemid'] = $newDiscovery['itemids'][0]; 2175 2176 // copy prototypes 2177 $new_prototypeids = $this->copyItemPrototypes($srcDiscovery, $dstDiscovery, $dstHost); 2178 2179 // if there were prototypes defined, clone everything else 2180 if ($new_prototypeids) { 2181 // fetch new prototypes 2182 $dstDiscovery['items'] = API::ItemPrototype()->get([ 2183 'output' => ['itemid', 'key_'], 2184 'itemids' => $new_prototypeids, 2185 'preservekeys' => true 2186 ]); 2187 2188 // copy graphs 2189 $this->copyGraphPrototypes($srcDiscovery, $dstDiscovery); 2190 2191 // copy triggers 2192 $this->copyTriggerPrototypes($srcDiscovery, $srcHost, $dstHost); 2193 } 2194 2195 // copy host prototypes 2196 $this->copyHostPrototypes($discoveryid, $dstDiscovery); 2197 2198 return true; 2199 } 2200 2201 /** 2202 * Copies all of the item prototypes from the source discovery to the target 2203 * discovery rule. Return array of created item prototype ids. 2204 * 2205 * @throws APIException if prototype saving fails 2206 * 2207 * @param array $srcDiscovery The source discovery rule to copy from 2208 * @param array $dstDiscovery The target discovery rule to copy to 2209 * @param array $dstHost The target host to copy the deiscovery rule to 2210 * 2211 * @return array 2212 */ 2213 protected function copyItemPrototypes(array $srcDiscovery, array $dstDiscovery, array $dstHost) { 2214 $item_prototypes = API::ItemPrototype()->get([ 2215 'output' => ['itemid', 'type', 'snmp_oid', 'name', 'key_', 'delay', 'history', 'trends', 'status', 2216 'value_type', 'trapper_hosts', 'units', 'logtimefmt', 'valuemapid', 'params', 'ipmi_sensor', 'authtype', 2217 'username', 'password', 'publickey', 'privatekey', 'interfaceid', 'port', 'description', 'jmx_endpoint', 2218 'master_itemid', 'templateid', 'url', 'query_fields', 'timeout', 'posts', 'status_codes', 2219 'follow_redirects', 'post_type', 'http_proxy', 'headers', 'retrieve_mode', 'request_method', 2220 'output_format', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'verify_peer', 'verify_host', 2221 'allow_traps', 'discover', 'parameters' 2222 ], 2223 'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'], 2224 'selectTags' => ['tag', 'value'], 2225 'selectValueMap' => ['name'], 2226 'discoveryids' => $srcDiscovery['itemid'], 2227 'preservekeys' => true 2228 ]); 2229 $new_itemids = []; 2230 $itemkey_to_id = []; 2231 $create_items = []; 2232 $src_valuemap_names = []; 2233 $valuemap_map = []; 2234 2235 foreach ($item_prototypes as $item_prototype) { 2236 if ($item_prototype['valuemap']) { 2237 $src_valuemap_names[] = $item_prototype['valuemap']['name']; 2238 } 2239 } 2240 2241 if ($src_valuemap_names) { 2242 $valuemap_map = array_column(API::ValueMap()->get([ 2243 'output' => ['valuemapid', 'name'], 2244 'hostids' => $dstHost['hostid'], 2245 'filter' => ['name' => $src_valuemap_names] 2246 ]), 'valuemapid', 'name'); 2247 } 2248 2249 if ($item_prototypes) { 2250 $create_order = []; 2251 $src_itemid_to_key = []; 2252 $unresolved_master_itemids = []; 2253 2254 // Gather all master item IDs and check if master item IDs already belong to item prototypes. 2255 foreach ($item_prototypes as $itemid => $item_prototype) { 2256 if ($item_prototype['type'] == ITEM_TYPE_DEPENDENT 2257 && !array_key_exists($item_prototype['master_itemid'], $item_prototypes)) { 2258 $unresolved_master_itemids[$item_prototype['master_itemid']] = true; 2259 } 2260 } 2261 2262 $items = []; 2263 2264 // It's possible that master items are non-prototype items. 2265 if ($unresolved_master_itemids) { 2266 $items = API::Item()->get([ 2267 'output' => ['itemid'], 2268 'itemids' => array_keys($unresolved_master_itemids), 2269 'webitems' => true, 2270 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL], 2271 'preservekeys' => true 2272 ]); 2273 2274 foreach ($items as $item) { 2275 if (array_key_exists($item['itemid'], $unresolved_master_itemids)) { 2276 unset($unresolved_master_itemids[$item['itemid']]); 2277 } 2278 } 2279 2280 // If still there are IDs left, there's nothing more we can do. 2281 if ($unresolved_master_itemids) { 2282 reset($unresolved_master_itemids); 2283 self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', 2284 'master_itemid', _s('Item "%1$s" does not exist or you have no access to this item', 2285 key($unresolved_master_itemids) 2286 ))); 2287 } 2288 } 2289 2290 foreach ($item_prototypes as $itemid => $item_prototype) { 2291 $dependency_level = 0; 2292 $master_item_prototype = $item_prototype; 2293 $src_itemid_to_key[$itemid] = $item_prototype['key_']; 2294 2295 while ($master_item_prototype['type'] == ITEM_TYPE_DEPENDENT) { 2296 if (array_key_exists($master_item_prototype['master_itemid'], $item_prototypes)) { 2297 $master_item_prototype = $item_prototypes[$master_item_prototype['master_itemid']]; 2298 ++$dependency_level; 2299 } 2300 else { 2301 break; 2302 } 2303 } 2304 2305 $create_order[$itemid] = $dependency_level; 2306 } 2307 asort($create_order); 2308 2309 $current_dependency = reset($create_order); 2310 2311 foreach ($create_order as $key => $dependency_level) { 2312 if ($current_dependency != $dependency_level && $create_items) { 2313 $current_dependency = $dependency_level; 2314 $created_itemids = API::ItemPrototype()->create($create_items); 2315 2316 if (!$created_itemids) { 2317 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone item prototypes.')); 2318 } 2319 2320 $created_itemids = $created_itemids['itemids']; 2321 $new_itemids = array_merge($new_itemids, $created_itemids); 2322 2323 foreach ($create_items as $index => $created_item) { 2324 $itemkey_to_id[$created_item['key_']] = $created_itemids[$index]; 2325 } 2326 2327 $create_items = []; 2328 } 2329 2330 $item_prototype = $item_prototypes[$key]; 2331 $item_prototype['ruleid'] = $dstDiscovery['itemid']; 2332 $item_prototype['hostid'] = $dstDiscovery['hostid']; 2333 2334 if ($item_prototype['valuemapid'] != 0) { 2335 $item_prototype['valuemapid'] = array_key_exists($item_prototype['valuemap']['name'], $valuemap_map) 2336 ? $valuemap_map[$item_prototype['valuemap']['name']] 2337 : 0; 2338 } 2339 2340 // map prototype interfaces 2341 if ($dstHost['status'] != HOST_STATUS_TEMPLATE) { 2342 // find a matching interface 2343 $interface = self::findInterfaceForItem($item_prototype['type'], $dstHost['interfaces']); 2344 if ($interface) { 2345 $item_prototype['interfaceid'] = $interface['interfaceid']; 2346 } 2347 // no matching interface found, throw an error 2348 elseif ($interface !== false) { 2349 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 2350 'Cannot find host interface on "%1$s" for item key "%2$s".', 2351 $dstHost['name'], 2352 $item_prototype['key_'] 2353 )); 2354 } 2355 } 2356 2357 if (!$item_prototype['preprocessing']) { 2358 unset($item_prototype['preprocessing']); 2359 } 2360 2361 if ($item_prototype['type'] == ITEM_TYPE_DEPENDENT) { 2362 $master_itemid = $item_prototype['master_itemid']; 2363 2364 if (array_key_exists($master_itemid, $src_itemid_to_key)) { 2365 $src_item_key = $src_itemid_to_key[$master_itemid]; 2366 $item_prototype['master_itemid'] = $itemkey_to_id[$src_item_key]; 2367 } 2368 else { 2369 // It's a non-prototype item, so look for it on destination host. 2370 $dst_item = get_same_item_for_host($items[$master_itemid], $dstHost['hostid']); 2371 2372 if (!$dst_item) { 2373 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone item prototypes.')); 2374 } 2375 2376 $item_prototype['master_itemid'] = $dst_item['itemid']; 2377 } 2378 } 2379 else { 2380 unset($item_prototype['master_itemid']); 2381 } 2382 2383 unset($item_prototype['templateid']); 2384 $create_items[] = $item_prototype; 2385 } 2386 2387 if ($create_items) { 2388 $created_itemids = API::ItemPrototype()->create($create_items); 2389 2390 if (!$created_itemids) { 2391 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone item prototypes.')); 2392 } 2393 2394 $new_itemids = array_merge($new_itemids, $created_itemids['itemids']); 2395 } 2396 } 2397 2398 return $new_itemids; 2399 } 2400 2401 /** 2402 * Copies all of the graphs from the source discovery to the target discovery rule. 2403 * 2404 * @throws APIException if graph saving fails 2405 * 2406 * @param array $srcDiscovery The source discovery rule to copy from 2407 * @param array $dstDiscovery The target discovery rule to copy to 2408 * 2409 * @return array 2410 */ 2411 protected function copyGraphPrototypes(array $srcDiscovery, array $dstDiscovery) { 2412 // fetch source graphs 2413 $srcGraphs = API::GraphPrototype()->get([ 2414 'output' => ['graphid', 'name', 'width', 'height', 'yaxismin', 'yaxismax', 'show_work_period', 2415 'show_triggers', 'graphtype', 'show_legend', 'show_3d', 'percent_left', 'percent_right', 2416 'ymin_type', 'ymax_type', 'ymin_itemid', 'ymax_itemid', 'discover' 2417 ], 2418 'selectGraphItems' => ['itemid', 'drawtype', 'sortorder', 'color', 'yaxisside', 'calc_fnc', 'type'], 2419 'selectHosts' => ['hostid'], 2420 'discoveryids' => $srcDiscovery['itemid'], 2421 'preservekeys' => true 2422 ]); 2423 2424 if (!$srcGraphs) { 2425 return []; 2426 } 2427 2428 $srcItemIds = []; 2429 foreach ($srcGraphs as $key => $graph) { 2430 // skip graphs with items from multiple hosts 2431 if (count($graph['hosts']) > 1) { 2432 unset($srcGraphs[$key]); 2433 continue; 2434 } 2435 2436 // skip graphs with http items 2437 if (httpItemExists($graph['gitems'])) { 2438 unset($srcGraphs[$key]); 2439 continue; 2440 } 2441 2442 // save all used item ids to map them to the new items 2443 foreach ($graph['gitems'] as $item) { 2444 $srcItemIds[$item['itemid']] = $item['itemid']; 2445 } 2446 if ($graph['ymin_itemid']) { 2447 $srcItemIds[$graph['ymin_itemid']] = $graph['ymin_itemid']; 2448 } 2449 if ($graph['ymax_itemid']) { 2450 $srcItemIds[$graph['ymax_itemid']] = $graph['ymax_itemid']; 2451 } 2452 } 2453 2454 // fetch source items 2455 $items = API::Item()->get([ 2456 'output' => ['itemid', 'key_'], 2457 'webitems' => true, 2458 'itemids' => $srcItemIds, 2459 'filter' => ['flags' => null], 2460 'preservekeys' => true 2461 ]); 2462 2463 $srcItems = []; 2464 $itemKeys = []; 2465 foreach ($items as $item) { 2466 $srcItems[$item['itemid']] = $item; 2467 $itemKeys[$item['key_']] = $item['key_']; 2468 } 2469 2470 // fetch newly cloned items 2471 $newItems = API::Item()->get([ 2472 'output' => ['itemid', 'key_'], 2473 'webitems' => true, 2474 'hostids' => $dstDiscovery['hostid'], 2475 'filter' => [ 2476 'key_' => $itemKeys, 2477 'flags' => null 2478 ], 2479 'preservekeys' => true 2480 ]); 2481 2482 $items = array_merge($dstDiscovery['items'], $newItems); 2483 $dstItems = []; 2484 foreach ($items as $item) { 2485 $dstItems[$item['key_']] = $item; 2486 } 2487 2488 $dstGraphs = $srcGraphs; 2489 foreach ($dstGraphs as &$graph) { 2490 unset($graph['graphid']); 2491 2492 foreach ($graph['gitems'] as &$gitem) { 2493 // replace the old item with the new one with the same key 2494 $item = $srcItems[$gitem['itemid']]; 2495 $gitem['itemid'] = $dstItems[$item['key_']]['itemid']; 2496 } 2497 unset($gitem); 2498 2499 // replace the old axis items with the new one with the same key 2500 if ($graph['ymin_itemid']) { 2501 $yMinSrcItem = $srcItems[$graph['ymin_itemid']]; 2502 $graph['ymin_itemid'] = $dstItems[$yMinSrcItem['key_']]['itemid']; 2503 } 2504 if ($graph['ymax_itemid']) { 2505 $yMaxSrcItem = $srcItems[$graph['ymax_itemid']]; 2506 $graph['ymax_itemid'] = $dstItems[$yMaxSrcItem['key_']]['itemid']; 2507 } 2508 } 2509 unset($graph); 2510 2511 // save graphs 2512 $rs = API::GraphPrototype()->create($dstGraphs); 2513 if (!$rs) { 2514 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone graph prototypes.')); 2515 } 2516 2517 return $rs; 2518 } 2519 2520 /** 2521 * Copies all of the host prototypes from the source discovery to the target 2522 * discovery rule. 2523 * 2524 * @throws APIException if prototype saving fails. 2525 * 2526 * @param int $srcid The source discovery rule id to copy from. 2527 * @param array $dstDiscovery The target discovery rule to copy to. 2528 * 2529 * @return array 2530 */ 2531 protected function copyHostPrototypes($srcid, array $dstDiscovery) { 2532 $prototypes = API::HostPrototype()->get([ 2533 'discoveryids' => $srcid, 2534 'output' => ['host', 'name', 'status', 'inventory_mode', 'discover', 'custom_interfaces'], 2535 'selectGroupLinks' => ['groupid'], 2536 'selectGroupPrototypes' => ['name'], 2537 'selectInterfaces' => ['type', 'useip', 'ip', 'dns', 'port', 'main', 'details'], 2538 'selectTemplates' => ['templateid'], 2539 'selectTags' => ['tag', 'value'], 2540 'selectMacros' => ['macro', 'type', 'value', 'description'], 2541 'preservekeys' => true 2542 ]); 2543 2544 $rs = []; 2545 if ($prototypes) { 2546 foreach ($prototypes as &$prototype) { 2547 $prototype['ruleid'] = $dstDiscovery['itemid']; 2548 unset($prototype['hostid'], $prototype['inventory']['hostid']); 2549 2550 foreach ($prototype['groupLinks'] as &$groupLinks) { 2551 unset($groupLinks['group_prototypeid']); 2552 } 2553 unset($groupLinks); 2554 2555 foreach ($prototype['groupPrototypes'] as &$groupPrototype) { 2556 unset($groupPrototype['group_prototypeid']); 2557 } 2558 unset($groupPrototype); 2559 2560 foreach ($prototype['macros'] as &$macro) { 2561 $macro['type'] = ($macro['type'] == ZBX_MACRO_TYPE_SECRET) ? ZBX_MACRO_TYPE_TEXT : $macro['type']; 2562 $macro += ['value' => '']; 2563 } 2564 unset($macro); 2565 } 2566 unset($prototype); 2567 2568 $rs = API::HostPrototype()->create($prototypes); 2569 if (!$rs) { 2570 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone host prototypes.')); 2571 } 2572 } 2573 return $rs; 2574 } 2575 2576 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 2577 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 2578 2579 if ((!$options['countOutput'] && ($this->outputIsRequested('state', $options['output']) 2580 || $this->outputIsRequested('error', $options['output']))) 2581 || (is_array($options['search']) && array_key_exists('error', $options['search'])) 2582 || (is_array($options['filter']) && array_key_exists('state', $options['filter']))) { 2583 $sqlParts['left_join'][] = ['alias' => 'ir', 'table' => 'item_rtdata', 'using' => 'itemid']; 2584 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 2585 } 2586 2587 if (!$options['countOutput']) { 2588 if ($this->outputIsRequested('state', $options['output'])) { 2589 $sqlParts = $this->addQuerySelect('ir.state', $sqlParts); 2590 } 2591 if ($this->outputIsRequested('error', $options['output'])) { 2592 /* 2593 * SQL func COALESCE use for template items because they don't have record 2594 * in item_rtdata table and DBFetch convert null to '0' 2595 */ 2596 $sqlParts = $this->addQuerySelect(dbConditionCoalesce('ir.error', '', 'error'), $sqlParts); 2597 } 2598 2599 // add filter fields 2600 if ($this->outputIsRequested('formula', $options['selectFilter']) 2601 || $this->outputIsRequested('eval_formula', $options['selectFilter']) 2602 || $this->outputIsRequested('conditions', $options['selectFilter'])) { 2603 2604 $sqlParts = $this->addQuerySelect('i.formula', $sqlParts); 2605 $sqlParts = $this->addQuerySelect('i.evaltype', $sqlParts); 2606 } 2607 if ($this->outputIsRequested('evaltype', $options['selectFilter'])) { 2608 $sqlParts = $this->addQuerySelect('i.evaltype', $sqlParts); 2609 } 2610 2611 if ($options['selectHosts'] !== null) { 2612 $sqlParts = $this->addQuerySelect('i.hostid', $sqlParts); 2613 } 2614 } 2615 2616 return $sqlParts; 2617 } 2618 2619 protected function addRelatedObjects(array $options, array $result) { 2620 $result = parent::addRelatedObjects($options, $result); 2621 2622 $itemIds = array_keys($result); 2623 2624 // adding items 2625 if (!is_null($options['selectItems'])) { 2626 if ($options['selectItems'] != API_OUTPUT_COUNT) { 2627 $items = []; 2628 $relationMap = $this->createRelationMap($result, 'parent_itemid', 'itemid', 'item_discovery'); 2629 $related_ids = $relationMap->getRelatedIds(); 2630 2631 if ($related_ids) { 2632 $items = API::ItemPrototype()->get([ 2633 'output' => $options['selectItems'], 2634 'itemids' => $related_ids, 2635 'nopermissions' => true, 2636 'preservekeys' => true 2637 ]); 2638 } 2639 2640 $result = $relationMap->mapMany($result, $items, 'items', $options['limitSelects']); 2641 } 2642 else { 2643 $items = API::ItemPrototype()->get([ 2644 'discoveryids' => $itemIds, 2645 'nopermissions' => true, 2646 'countOutput' => true, 2647 'groupCount' => true 2648 ]); 2649 2650 $items = zbx_toHash($items, 'parent_itemid'); 2651 foreach ($result as $itemid => $item) { 2652 $result[$itemid]['items'] = array_key_exists($itemid, $items) ? $items[$itemid]['rowscount'] : '0'; 2653 } 2654 } 2655 } 2656 2657 // adding triggers 2658 if (!is_null($options['selectTriggers'])) { 2659 if ($options['selectTriggers'] != API_OUTPUT_COUNT) { 2660 $triggers = []; 2661 $relationMap = new CRelationMap(); 2662 $res = DBselect( 2663 'SELECT id.parent_itemid,f.triggerid'. 2664 ' FROM item_discovery id,items i,functions f'. 2665 ' WHERE '.dbConditionInt('id.parent_itemid', $itemIds). 2666 ' AND id.itemid=i.itemid'. 2667 ' AND i.itemid=f.itemid' 2668 ); 2669 while ($relation = DBfetch($res)) { 2670 $relationMap->addRelation($relation['parent_itemid'], $relation['triggerid']); 2671 } 2672 2673 $related_ids = $relationMap->getRelatedIds(); 2674 2675 if ($related_ids) { 2676 $triggers = API::TriggerPrototype()->get([ 2677 'output' => $options['selectTriggers'], 2678 'triggerids' => $related_ids, 2679 'preservekeys' => true 2680 ]); 2681 } 2682 2683 $result = $relationMap->mapMany($result, $triggers, 'triggers', $options['limitSelects']); 2684 } 2685 else { 2686 $triggers = API::TriggerPrototype()->get([ 2687 'discoveryids' => $itemIds, 2688 'countOutput' => true, 2689 'groupCount' => true 2690 ]); 2691 2692 $triggers = zbx_toHash($triggers, 'parent_itemid'); 2693 foreach ($result as $itemid => $item) { 2694 $result[$itemid]['triggers'] = array_key_exists($itemid, $triggers) 2695 ? $triggers[$itemid]['rowscount'] 2696 : '0'; 2697 } 2698 } 2699 } 2700 2701 // adding graphs 2702 if (!is_null($options['selectGraphs'])) { 2703 if ($options['selectGraphs'] != API_OUTPUT_COUNT) { 2704 $graphs = []; 2705 $relationMap = new CRelationMap(); 2706 $res = DBselect( 2707 'SELECT id.parent_itemid,gi.graphid'. 2708 ' FROM item_discovery id,items i,graphs_items gi'. 2709 ' WHERE '.dbConditionInt('id.parent_itemid', $itemIds). 2710 ' AND id.itemid=i.itemid'. 2711 ' AND i.itemid=gi.itemid' 2712 ); 2713 while ($relation = DBfetch($res)) { 2714 $relationMap->addRelation($relation['parent_itemid'], $relation['graphid']); 2715 } 2716 2717 $related_ids = $relationMap->getRelatedIds(); 2718 2719 if ($related_ids) { 2720 $graphs = API::GraphPrototype()->get([ 2721 'output' => $options['selectGraphs'], 2722 'graphids' => $related_ids, 2723 'preservekeys' => true 2724 ]); 2725 } 2726 2727 $result = $relationMap->mapMany($result, $graphs, 'graphs', $options['limitSelects']); 2728 } 2729 else { 2730 $graphs = API::GraphPrototype()->get([ 2731 'discoveryids' => $itemIds, 2732 'countOutput' => true, 2733 'groupCount' => true 2734 ]); 2735 2736 $graphs = zbx_toHash($graphs, 'parent_itemid'); 2737 foreach ($result as $itemid => $item) { 2738 $result[$itemid]['graphs'] = array_key_exists($itemid, $graphs) 2739 ? $graphs[$itemid]['rowscount'] 2740 : '0'; 2741 } 2742 } 2743 } 2744 2745 // adding hosts 2746 if ($options['selectHostPrototypes'] !== null) { 2747 if ($options['selectHostPrototypes'] != API_OUTPUT_COUNT) { 2748 $hostPrototypes = []; 2749 $relationMap = $this->createRelationMap($result, 'parent_itemid', 'hostid', 'host_discovery'); 2750 $related_ids = $relationMap->getRelatedIds(); 2751 2752 if ($related_ids) { 2753 $hostPrototypes = API::HostPrototype()->get([ 2754 'output' => $options['selectHostPrototypes'], 2755 'hostids' => $related_ids, 2756 'nopermissions' => true, 2757 'preservekeys' => true 2758 ]); 2759 } 2760 2761 $result = $relationMap->mapMany($result, $hostPrototypes, 'hostPrototypes', $options['limitSelects']); 2762 } 2763 else { 2764 $hostPrototypes = API::HostPrototype()->get([ 2765 'discoveryids' => $itemIds, 2766 'nopermissions' => true, 2767 'countOutput' => true, 2768 'groupCount' => true 2769 ]); 2770 $hostPrototypes = zbx_toHash($hostPrototypes, 'parent_itemid'); 2771 2772 foreach ($result as $itemid => $item) { 2773 $result[$itemid]['hostPrototypes'] = array_key_exists($itemid, $hostPrototypes) 2774 ? $hostPrototypes[$itemid]['rowscount'] 2775 : '0'; 2776 } 2777 } 2778 } 2779 2780 if ($options['selectFilter'] !== null) { 2781 $formulaRequested = $this->outputIsRequested('formula', $options['selectFilter']); 2782 $evalFormulaRequested = $this->outputIsRequested('eval_formula', $options['selectFilter']); 2783 $conditionsRequested = $this->outputIsRequested('conditions', $options['selectFilter']); 2784 2785 $filters = []; 2786 foreach ($result as $rule) { 2787 $filters[$rule['itemid']] = [ 2788 'evaltype' => $rule['evaltype'], 2789 'formula' => isset($rule['formula']) ? $rule['formula'] : '' 2790 ]; 2791 } 2792 2793 // adding conditions 2794 if ($formulaRequested || $evalFormulaRequested || $conditionsRequested) { 2795 $conditions = DB::select('item_condition', [ 2796 'output' => ['item_conditionid', 'macro', 'value', 'itemid', 'operator'], 2797 'filter' => ['itemid' => $itemIds], 2798 'preservekeys' => true, 2799 'sortfield' => ['item_conditionid'] 2800 ]); 2801 $relationMap = $this->createRelationMap($conditions, 'itemid', 'item_conditionid'); 2802 2803 $filters = $relationMap->mapMany($filters, $conditions, 'conditions'); 2804 2805 foreach ($filters as &$filter) { 2806 // in case of a custom expression - use the given formula 2807 if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 2808 $formula = $filter['formula']; 2809 } 2810 // in other cases - generate the formula automatically 2811 else { 2812 // sort the conditions by macro before generating the formula 2813 $conditions = zbx_toHash($filter['conditions'], 'item_conditionid'); 2814 $conditions = order_macros($conditions, 'macro'); 2815 2816 $formulaConditions = []; 2817 foreach ($conditions as $condition) { 2818 $formulaConditions[$condition['item_conditionid']] = $condition['macro']; 2819 } 2820 $formula = CConditionHelper::getFormula($formulaConditions, $filter['evaltype']); 2821 } 2822 2823 // generate formulaids from the effective formula 2824 $formulaIds = CConditionHelper::getFormulaIds($formula); 2825 foreach ($filter['conditions'] as &$condition) { 2826 $condition['formulaid'] = $formulaIds[$condition['item_conditionid']]; 2827 } 2828 unset($condition); 2829 2830 // generated a letter based formula only for rules with custom expressions 2831 if ($formulaRequested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 2832 $filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds); 2833 } 2834 2835 if ($evalFormulaRequested) { 2836 $filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds); 2837 } 2838 } 2839 unset($filter); 2840 } 2841 2842 // add filters to the result 2843 foreach ($result as &$rule) { 2844 $rule['filter'] = $filters[$rule['itemid']]; 2845 } 2846 unset($rule); 2847 } 2848 2849 // Add LLD macro paths. 2850 if ($options['selectLLDMacroPaths'] !== null && $options['selectLLDMacroPaths'] != API_OUTPUT_COUNT) { 2851 $lld_macro_paths = API::getApiService()->select('lld_macro_path', [ 2852 'output' => $this->outputExtend($options['selectLLDMacroPaths'], ['itemid', 'lld_macro_pathid']), 2853 'filter' => ['itemid' => $itemIds] 2854 ]); 2855 2856 foreach ($result as &$lld_macro_path) { 2857 $lld_macro_path['lld_macro_paths'] = []; 2858 } 2859 unset($lld_macro_path); 2860 2861 foreach ($lld_macro_paths as $lld_macro_path) { 2862 $itemid = $lld_macro_path['itemid']; 2863 2864 if (!$this->outputIsRequested('lld_macro_pathid', $options['selectLLDMacroPaths'])) { 2865 unset($lld_macro_path['lld_macro_pathid']); 2866 } 2867 unset($lld_macro_path['itemid']); 2868 2869 $result[$itemid]['lld_macro_paths'][] = $lld_macro_path; 2870 } 2871 } 2872 2873 // add overrides 2874 if ($options['selectOverrides'] !== null && $options['selectOverrides'] != API_OUTPUT_COUNT) { 2875 $ovrd_fields = ['itemid', 'lld_overrideid']; 2876 $filter_requested = $this->outputIsRequested('filter', $options['selectOverrides']); 2877 $operations_requested = $this->outputIsRequested('operations', $options['selectOverrides']); 2878 2879 if ($filter_requested) { 2880 $ovrd_fields = array_merge($ovrd_fields, ['formula', 'evaltype']); 2881 } 2882 2883 $overrides = API::getApiService()->select('lld_override', [ 2884 'output' => $this->outputExtend($options['selectOverrides'], $ovrd_fields), 2885 'filter' => ['itemid' => $itemIds], 2886 'preservekeys' => true 2887 ]); 2888 2889 if ($filter_requested && $overrides) { 2890 $conditions = DB::select('lld_override_condition', [ 2891 'output' => ['lld_override_conditionid', 'macro', 'value', 'lld_overrideid', 'operator'], 2892 'filter' => ['lld_overrideid' => array_keys($overrides)], 2893 'sortfield' => ['lld_override_conditionid'], 2894 'preservekeys' => true 2895 ]); 2896 2897 $relation_map = $this->createRelationMap($conditions, 'lld_overrideid', 'lld_override_conditionid'); 2898 2899 foreach ($overrides as &$override) { 2900 $override['filter'] = [ 2901 'evaltype' => $override['evaltype'], 2902 'formula' => $override['formula'] 2903 ]; 2904 unset($override['evaltype'], $override['formula']); 2905 } 2906 unset($override); 2907 2908 $overrides = $relation_map->mapMany($overrides, $conditions, 'conditions'); 2909 2910 foreach ($overrides as &$override) { 2911 $override['filter'] += ['conditions' => $override['conditions']]; 2912 unset($override['conditions']); 2913 2914 if ($override['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 2915 $formula = $override['filter']['formula']; 2916 } 2917 else { 2918 $conditions = zbx_toHash($override['filter']['conditions'], 'lld_override_conditionid'); 2919 $conditions = order_macros($conditions, 'macro'); 2920 $formula_conditions = []; 2921 2922 foreach ($conditions as $condition) { 2923 $formula_conditions[$condition['lld_override_conditionid']] = $condition['macro']; 2924 } 2925 2926 $formula = CConditionHelper::getFormula($formula_conditions, $override['filter']['evaltype']); 2927 } 2928 2929 $formulaids = CConditionHelper::getFormulaIds($formula); 2930 2931 foreach ($override['filter']['conditions'] as &$condition) { 2932 $condition['formulaid'] = $formulaids[$condition['lld_override_conditionid']]; 2933 unset($condition['lld_override_conditionid'], $condition['lld_overrideid']); 2934 } 2935 unset($condition); 2936 2937 if ($override['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 2938 $override['filter']['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids); 2939 $override['filter']['eval_formula'] = $override['filter']['formula']; 2940 } 2941 else { 2942 $override['filter']['eval_formula'] = CConditionHelper::replaceNumericIds($formula, 2943 $formulaids 2944 ); 2945 } 2946 } 2947 unset($override); 2948 } 2949 2950 if ($operations_requested && $overrides) { 2951 $operations = DB::select('lld_override_operation', [ 2952 'output' => ['lld_override_operationid', 'lld_overrideid', 'operationobject', 'operator', 'value'], 2953 'filter' => ['lld_overrideid' => array_keys($overrides)], 2954 'sortfield' => ['lld_override_operationid'], 2955 'preservekeys' => true 2956 ]); 2957 2958 if ($operations) { 2959 $opdiscover = DB::select('lld_override_opdiscover', [ 2960 'output' => ['lld_override_operationid', 'discover'], 2961 'filter' => ['lld_override_operationid' => array_keys($operations)] 2962 ]); 2963 2964 $item_prototype_objectids = []; 2965 $trigger_prototype_objectids = []; 2966 $host_prototype_objectids = []; 2967 2968 foreach ($operations as $operation) { 2969 switch ($operation['operationobject']) { 2970 case OPERATION_OBJECT_ITEM_PROTOTYPE: 2971 $item_prototype_objectids[$operation['lld_override_operationid']] = true; 2972 break; 2973 2974 case OPERATION_OBJECT_TRIGGER_PROTOTYPE: 2975 $trigger_prototype_objectids[$operation['lld_override_operationid']] = true; 2976 break; 2977 2978 case OPERATION_OBJECT_HOST_PROTOTYPE: 2979 $host_prototype_objectids[$operation['lld_override_operationid']] = true; 2980 break; 2981 } 2982 } 2983 2984 if ($item_prototype_objectids || $trigger_prototype_objectids || $host_prototype_objectids) { 2985 $opstatus = DB::select('lld_override_opstatus', [ 2986 'output' => ['lld_override_operationid', 'status'], 2987 'filter' => ['lld_override_operationid' => array_keys( 2988 $item_prototype_objectids + $trigger_prototype_objectids + $host_prototype_objectids 2989 )] 2990 ]); 2991 } 2992 2993 if ($item_prototype_objectids) { 2994 $ophistory = DB::select('lld_override_ophistory', [ 2995 'output' => ['lld_override_operationid', 'history'], 2996 'filter' => ['lld_override_operationid' => array_keys($item_prototype_objectids)] 2997 ]); 2998 $optrends = DB::select('lld_override_optrends', [ 2999 'output' => ['lld_override_operationid', 'trends'], 3000 'filter' => ['lld_override_operationid' => array_keys($item_prototype_objectids)] 3001 ]); 3002 $opperiod = DB::select('lld_override_opperiod', [ 3003 'output' => ['lld_override_operationid', 'delay'], 3004 'filter' => ['lld_override_operationid' => array_keys($item_prototype_objectids)] 3005 ]); 3006 } 3007 3008 if ($trigger_prototype_objectids) { 3009 $opseverity = DB::select('lld_override_opseverity', [ 3010 'output' => ['lld_override_operationid', 'severity'], 3011 'filter' => ['lld_override_operationid' => array_keys($trigger_prototype_objectids)] 3012 ]); 3013 } 3014 3015 if ($trigger_prototype_objectids || $host_prototype_objectids || $item_prototype_objectids) { 3016 $optag = DB::select('lld_override_optag', [ 3017 'output' => ['lld_override_operationid', 'tag', 'value'], 3018 'filter' => ['lld_override_operationid' => array_keys( 3019 $trigger_prototype_objectids + $host_prototype_objectids + $item_prototype_objectids 3020 )] 3021 ]); 3022 } 3023 3024 if ($host_prototype_objectids) { 3025 $optemplate = DB::select('lld_override_optemplate', [ 3026 'output' => ['lld_override_operationid', 'templateid'], 3027 'filter' => ['lld_override_operationid' => array_keys($host_prototype_objectids)] 3028 ]); 3029 $opinventory = DB::select('lld_override_opinventory', [ 3030 'output' => ['lld_override_operationid', 'inventory_mode'], 3031 'filter' => ['lld_override_operationid' => array_keys($host_prototype_objectids)] 3032 ]); 3033 } 3034 3035 foreach ($operations as &$operation) { 3036 $lld_override_operationid = $operation['lld_override_operationid']; 3037 3038 if ($item_prototype_objectids || $trigger_prototype_objectids || $host_prototype_objectids) { 3039 foreach ($opstatus as $row) { 3040 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3041 $operation['opstatus']['status'] = $row['status']; 3042 } 3043 } 3044 } 3045 3046 foreach ($opdiscover as $row) { 3047 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3048 $operation['opdiscover']['discover'] = $row['discover']; 3049 } 3050 } 3051 3052 if ($item_prototype_objectids) { 3053 foreach ($ophistory as $row) { 3054 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3055 $operation['ophistory']['history'] = $row['history']; 3056 } 3057 } 3058 3059 foreach ($optrends as $row) { 3060 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3061 $operation['optrends']['trends'] = $row['trends']; 3062 } 3063 } 3064 3065 foreach ($opperiod as $row) { 3066 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3067 $operation['opperiod']['delay'] = $row['delay']; 3068 } 3069 } 3070 } 3071 3072 if ($trigger_prototype_objectids) { 3073 foreach ($opseverity as $row) { 3074 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3075 $operation['opseverity']['severity'] = $row['severity']; 3076 } 3077 } 3078 } 3079 3080 if ($trigger_prototype_objectids || $host_prototype_objectids || $item_prototype_objectids) { 3081 foreach ($optag as $row) { 3082 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3083 $operation['optag'][] = ['tag' => $row['tag'], 'value' => $row['value']]; 3084 } 3085 } 3086 } 3087 3088 if ($host_prototype_objectids) { 3089 foreach ($optemplate as $row) { 3090 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3091 $operation['optemplate'][] = ['templateid' => $row['templateid']]; 3092 } 3093 } 3094 3095 foreach ($opinventory as $row) { 3096 if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) { 3097 $operation['opinventory']['inventory_mode'] = $row['inventory_mode']; 3098 } 3099 } 3100 } 3101 } 3102 unset($operation); 3103 } 3104 3105 $relation_map = $this->createRelationMap($operations, 'lld_overrideid', 'lld_override_operationid'); 3106 3107 $overrides = $relation_map->mapMany($overrides, $operations, 'operations'); 3108 } 3109 3110 foreach ($result as &$row) { 3111 $row['overrides'] = []; 3112 3113 foreach ($overrides as $override) { 3114 if (bccomp($override['itemid'], $row['itemid']) == 0) { 3115 unset($override['itemid'], $override['lld_overrideid']); 3116 3117 if ($operations_requested) { 3118 foreach ($override['operations'] as &$operation) { 3119 unset($operation['lld_override_operationid'], $operation['lld_overrideid']); 3120 } 3121 unset($operation); 3122 } 3123 3124 $row['overrides'][] = $override; 3125 } 3126 } 3127 } 3128 unset($row); 3129 } 3130 3131 return $result; 3132 } 3133} 3134