diff options
Diffstat (limited to 'ui/include/classes/api/services/CItemGeneral.php')
-rw-r--r-- | ui/include/classes/api/services/CItemGeneral.php | 4034 |
1 files changed, 1831 insertions, 2203 deletions
diff --git a/ui/include/classes/api/services/CItemGeneral.php b/ui/include/classes/api/services/CItemGeneral.php index c6dcc174301..6c5746e16ce 100644 --- a/ui/include/classes/api/services/CItemGeneral.php +++ b/ui/include/classes/api/services/CItemGeneral.php @@ -38,1905 +38,1354 @@ abstract class CItemGeneral extends CApiService { INTERFACE_TYPE_IPMI ]; - const ERROR_EXISTS_TEMPLATE = 'existsTemplate'; - const ERROR_EXISTS = 'exists'; - const ERROR_NO_INTERFACE = 'noInterface'; - const ERROR_INVALID_KEY = 'invalidKey'; - - protected $fieldRules; + /** + * A list of supported preprocessing types. + * + * @var array + */ + public const SUPPORTED_PREPROCESSING_TYPES = []; /** - * @abstract + * A list of preprocessing types that supports the "params" field. * - * @param array $options + * @var array + */ + protected const PREPROC_TYPES_WITH_PARAMS = []; + + /** + * A list of preprocessing types that supports the error handling. * - * @return array + * @var array */ - abstract public function get($options = []); + protected const PREPROC_TYPES_WITH_ERR_HANDLING = []; - public function __construct() { - parent::__construct(); - - // template - if templated item, value is taken from template item, cannot be changed on host - // system - values should not be updated - // host - value should be null for template items - $this->fieldRules = [ - 'type' => ['template' => 1], - 'snmp_oid' => ['template' => 1], - 'hostid' => [], - 'name' => ['template' => 1], - 'description' => [], - 'key_' => ['template' => 1], - 'master_itemid' => ['template' => 1], - 'delay' => [], - 'history' => [], - 'trends' => [], - 'status' => [], - 'discover' => [], - 'value_type' => ['template' => 1], - 'trapper_hosts' => [], - 'units' => ['template' => 1], - 'formula' => ['template' => 1], - 'error' => ['system' => 1], - 'lastlogsize' => ['system' => 1], - 'logtimefmt' => [], - 'templateid' => ['system' => 1], - 'valuemapid' => ['template' => 1], - 'params' => [], - 'ipmi_sensor' => ['template' => 1], - 'authtype' => [], - 'username' => [], - 'password' => [], - 'publickey' => [], - 'privatekey' => [], - 'mtime' => ['system' => 1], - 'flags' => [], - 'filter' => [], - 'interfaceid' => ['host' => 1], - 'inventory_link' => [], - 'lifetime' => [], - 'preprocessing' => ['template' => 1], - 'overrides' => ['template' => 1], - 'jmx_endpoint' => [], - 'url' => ['template' => 1], - 'timeout' => ['template' => 1], - 'query_fields' => ['template' => 1], - 'parameters' => ['template' => 1], - 'posts' => ['template' => 1], - 'status_codes' => ['template' => 1], - 'follow_redirects' => ['template' => 1], - 'post_type' => ['template' => 1], - 'http_proxy' => ['template' => 1], - 'headers' => ['template' => 1], - 'retrieve_mode' => ['template' => 1], - 'request_method' => ['template' => 1], - 'output_format' => ['template' => 1], - 'allow_traps' => [], - 'ssl_cert_file' => ['template' => 1], - 'ssl_key_file' => ['template' => 1], - 'ssl_key_password' => ['template' => 1], - 'verify_peer' => ['template' => 1], - 'verify_host' => ['template' => 1] - ]; + /** + * A list of supported item types. + * + * @var array + */ + protected const SUPPORTED_ITEM_TYPES = []; - $this->errorMessages = array_merge($this->errorMessages, [ - self::ERROR_NO_INTERFACE => _('Cannot find host interface on "%1$s" for item key "%2$s".') - ]); - } + /** + * A list of field names for each of value types. + * + * @var array + */ + protected const VALUE_TYPE_FIELD_NAMES = []; /** - * Check items data. + * Maximum number of inheritable items per iteration. * - * Any system field passed to the function will be unset. + * @var int + */ + protected const INHERIT_CHUNK_SIZE = 1000; + + /** + * @abstract * - * @throw APIException + * @param array $options * - * @param array $items passed by reference - * @param bool $update + * @return array */ - protected function checkInput(array &$items, $update = false) { - $api_input_rules = ['type' => API_OBJECT, 'fields' => [ - 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', static::SUPPORTED_ITEM_TYPES)] - ]]; - if ($update) { - unset($api_input_rules['fields']['type']['flags']); - } + abstract public function get($options = []); - foreach ($items as $num => $item) { - $data = array_intersect_key($item, $api_input_rules['fields']); - if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($num + 1), $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); - } - } + /** + * @param array $field_names + * @param array $items + * @param array|null $db_items + * + * @throws APIException + */ + protected static function validateByType(array $field_names, array &$items, array $db_items = null): void { + $checked_fields = array_fill_keys($field_names, ['type' => API_ANY]); - if ($update) { - $itemDbFields = ['itemid' => null]; + foreach ($items as $i => &$item) { + $api_input_rules = ['type' => API_OBJECT, 'fields' => $checked_fields]; + $db_item = ($db_items === null) ? null : $db_items[$item['itemid']]; + $item_type = CItemTypeFactory::getObject($item['type']); - $dbItemsFields = ['itemid', 'templateid']; - foreach ($this->fieldRules as $field => $rule) { - if (!isset($rule['system'])) { - $dbItemsFields[] = $field; - } + if ($db_item === null) { + $api_input_rules['fields'] += $item_type::getCreateValidationRules($item); } - - $dbItems = $this->get([ - 'output' => $dbItemsFields, - 'itemids' => zbx_objectValues($items, 'itemid'), - 'editable' => true, - 'preservekeys' => true - ]); - - $dbHosts = API::Host()->get([ - 'output' => ['hostid', 'status', 'name'], - 'hostids' => zbx_objectValues($dbItems, 'hostid'), - 'templated_hosts' => true, - 'editable' => true, - 'preservekeys' => true - ]); - } - else { - $itemDbFields = [ - 'name' => null, - 'key_' => null, - 'hostid' => null, - 'type' => null, - 'value_type' => null, - 'delay' => null - ]; - - $dbHosts = API::Host()->get([ - 'output' => ['hostid', 'status', 'name'], - 'hostids' => zbx_objectValues($items, 'hostid'), - 'templated_hosts' => true, - 'editable' => true, - 'preservekeys' => true - ]); - - $discovery_rules = []; - - if ($this instanceof CItemPrototype) { - $itemDbFields['ruleid'] = null; - $druleids = zbx_objectValues($items, 'ruleid'); - - if ($druleids) { - $discovery_rules = API::DiscoveryRule()->get([ - 'output' => ['hostid'], - 'itemids' => $druleids, - 'preservekeys' => true - ]); + elseif ($db_item['templateid'] != 0) { + if ($item['type'] == ITEM_TYPE_HTTPAGENT) { + $item += array_intersect_key($db_item, array_flip(['allow_traps'])); } - } - } - - // interfaces - $interfaces = API::HostInterface()->get([ - 'output' => ['interfaceid', 'hostid', 'type'], - 'hostids' => zbx_objectValues($dbHosts, 'hostid'), - 'nopermissions' => true, - 'preservekeys' => true - ]); - - if ($update) { - $updateDiscoveredValidator = new CUpdateDiscoveredValidator([ - 'allowed' => ['itemid', 'status'], - 'messageAllowedField' => _('Cannot update "%2$s" for a discovered item "%1$s".') - ]); - foreach ($items as &$item) { - // check permissions - if (!array_key_exists($item['itemid'], $dbItems)) { - self::exception(ZBX_API_ERROR_PERMISSIONS, - _('No permissions to referred object or it does not exist!') - ); + elseif ($item['type'] == ITEM_TYPE_SSH) { + $item += array_intersect_key($db_item, array_flip(['authtype'])); } - $dbItem = $dbItems[$item['itemid']]; - - if (array_key_exists('hostid', $item) && bccomp($dbItem['hostid'], $item['hostid']) != 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'hostid', _('cannot be changed')) - ); + if ($item['type'] === ITEM_TYPE_SSH && $item['authtype'] == ITEM_AUTHTYPE_PUBLICKEY + && $db_item['authtype'] != ITEM_AUTHTYPE_PUBLICKEY) { + $item += array_intersect_key($db_item, array_flip(['publickey', 'privatekey'])); } - $itemName = array_key_exists('name', $item) ? $item['name'] : $dbItem['name']; - - // discovered fields, except status, cannot be updated - $updateDiscoveredValidator->setObjectName($itemName); - $this->checkPartialValidator($item, $updateDiscoveredValidator, $dbItem); - - $item += [ - 'hostid' => $dbItem['hostid'], - 'type' => $dbItem['type'], - 'name' => $dbItem['name'], - 'key_' => $dbItem['key_'], - 'flags' => $dbItem['flags'] - ]; + $api_input_rules['fields'] += $item_type::getUpdateValidationRulesInherited($db_item); } - unset($item); - } - - $item_key_parser = new CItemKey(); - $ip_range_parser = new CIPRangeParser([ - 'v6' => ZBX_HAVE_IPV6, - 'ranges' => false, - 'usermacros' => true, - 'macros' => [ - '{HOST.HOST}', '{HOSTNAME}', '{HOST.NAME}', '{HOST.CONN}', '{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}' - ] - ]); - $update_interval_parser = new CUpdateIntervalParser([ - 'usermacros' => true, - 'lldmacros' => (get_class($this) === 'CItemPrototype') - ]); - - $index = 0; - foreach ($items as $inum => &$item) { - $item = $this->clearValues($item); - $index++; - - $fullItem = $items[$inum]; - - if (!check_db_fields($itemDbFields, $item)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); + elseif ($db_item['flags'] == ZBX_FLAG_DISCOVERY_CREATED) { + $api_input_rules['fields'] += $item_type::getUpdateValidationRulesDiscovered(); } - - if ($update) { - $type = array_key_exists('type', $item) ? $item['type'] : $dbItems[$item['itemid']]['type']; - - if ($type == ITEM_TYPE_HTTPAGENT) { - $this->validateHTTPCheck($fullItem, $dbItems[$item['itemid']]); + else { + if ($item['type'] == ITEM_TYPE_HTTPAGENT) { + $item += array_intersect_key($db_item, array_flip( + ['request_method', 'post_type', 'authtype', 'allow_traps'] + )); + } + elseif ($item['type'] == ITEM_TYPE_SSH) { + $item += array_intersect_key($db_item, array_flip(['authtype'])); } - check_db_fields($dbItems[$item['itemid']], $fullItem); + $interfaceid_types = [ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_IPMI, + ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_JMX, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_HTTPAGENT, + ITEM_TYPE_SNMP + ]; - $this->checkNoParameters( - $item, - ['templateid', 'state', 'lastlogsize', 'mtime', 'error'], - _('Cannot update "%1$s" for item "%2$s".'), - $item['name'] - ); + if (in_array($item['type'], $interfaceid_types)) { + $opt_interface_types = [ITEM_TYPE_SIMPLE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, + ITEM_TYPE_HTTPAGENT + ]; - // apply rules - foreach ($this->fieldRules as $field => $rules) { - if ($fullItem['type'] == ITEM_TYPE_SCRIPT) { - $rules['template'] = 1; + if (in_array($db_item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]) + && (!in_array($db_item['type'], $interfaceid_types) + || (in_array($item['type'], array_diff($interfaceid_types, $opt_interface_types)) + && in_array($db_item['type'], $opt_interface_types) + && $db_item['interfaceid'] == 0))) { + $item += array_intersect_key($db_item, array_flip(['interfaceid'])); } + } + + $username_types = [ITEM_TYPE_SIMPLE, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, + ITEM_TYPE_JMX, ITEM_TYPE_HTTPAGENT + ]; - if ((0 != $fullItem['templateid'] && isset($rules['template'])) || isset($rules['system'])) { - unset($item[$field]); + if (in_array($item['type'], [ITEM_TYPE_SSH, ITEM_TYPE_TELNET])) { + $opt_username_types = array_diff($username_types, [ITEM_TYPE_SSH, ITEM_TYPE_TELNET]); - // For templated item and fields that should not be modified, use the value from DB. - if (array_key_exists($field, $dbItems[$item['itemid']]) - && array_key_exists($field, $fullItem)) { - $fullItem[$field] = $dbItems[$item['itemid']][$field]; - } + if (!in_array($db_item['type'], $username_types) + || (in_array($db_item['type'], $opt_username_types) && $db_item['username'] === '')) { + $item += array_intersect_key($db_item, array_flip(['username'])); } } - if (!isset($item['key_'])) { - $item['key_'] = $fullItem['key_']; - } - if (!isset($item['hostid'])) { - $item['hostid'] = $fullItem['hostid']; - } + $params_types = [ITEM_TYPE_DB_MONITOR, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_CALCULATED, + ITEM_TYPE_SCRIPT + ]; - // If a templated item is being assigned to an interface with a different type, ignore it. - $itemInterfaceType = itemTypeInterface($dbItems[$item['itemid']]['type']); + if (in_array($item['type'], $params_types) && !in_array($db_item['type'], $params_types)) { + $item += array_intersect_key($db_item, array_flip(['params'])); + } - if ($itemInterfaceType !== INTERFACE_TYPE_ANY && $itemInterfaceType !== INTERFACE_TYPE_OPT - && $fullItem['templateid'] - && array_key_exists('interfaceid', $item) && array_key_exists($item['interfaceid'], $interfaces) - && $interfaces[$item['interfaceid']]['type'] != $itemInterfaceType) { + $delay_types = [ITEM_TYPE_ZABBIX, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL, ITEM_TYPE_ZABBIX_ACTIVE, + ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_IPMI, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, + ITEM_TYPE_CALCULATED, ITEM_TYPE_JMX, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT + ]; - unset($item['interfaceid']); - } - } - else { - if ($fullItem['type'] == ITEM_TYPE_HTTPAGENT) { - $this->validateHTTPCheck($fullItem, []); + if (in_array($item['type'], $delay_types)) { + if (!in_array($db_item['type'], $delay_types) + || ($db_item['type'] == ITEM_TYPE_ZABBIX_ACTIVE + && strncmp($db_item['key_'], 'mqtt.get', 8) === 0)) { + $item += array_intersect_key($db_item, array_flip(['delay'])); + } } - if (!isset($dbHosts[$item['hostid']])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); + if ($item['type'] == ITEM_TYPE_DEPENDENT && $db_item['type'] != ITEM_TYPE_DEPENDENT) { + $item += array_intersect_key($db_item, array_flip(['master_itemid'])); } - check_db_fields($itemDbFields, $fullItem); + if ($item['type'] == ITEM_TYPE_HTTPAGENT) { + $post_types = [ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML]; - $this->checkNoParameters( - $item, - ['templateid', 'state'], - _('Cannot set "%1$s" for item "%2$s".'), - $item['name'] - ); - - if ($this instanceof CItemPrototype && (!array_key_exists($fullItem['ruleid'], $discovery_rules) - || $discovery_rules[$fullItem['ruleid']]['hostid'] != $fullItem['hostid'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _('No permissions to referred object or it does not exist!') - ); + if (in_array($item['post_type'], $post_types) && !in_array($db_item['post_type'], $post_types)) { + $item += array_intersect_key($db_item, array_flip(['posts'])); + } } - } - - if ($fullItem['type'] == ITEM_TYPE_CALCULATED) { - $api_input_rules = ['type' => API_OBJECT, 'fields' => [ - 'params' => ['type' => API_CALC_FORMULA, 'flags' => $this instanceof CItemPrototype ? API_ALLOW_LLD_MACRO : 0, 'length' => DB::getFieldLength('items', 'params')], - 'value_type' => ['type' => API_INT32, 'in' => implode(',', [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT])] - ]]; - $data = array_intersect_key($item, $api_input_rules['fields']); - - if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($inum + 1), $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); + if ($item['type'] == ITEM_TYPE_IPMI + && ($db_item['type'] != ITEM_TYPE_IPMI + || ($item['key_'] !== $db_item['key_'] && $db_item['key_'] === 'ipmi.get'))) { + $item += array_intersect_key($db_item, array_flip(['ipmi_sensor'])); } - } - if ($fullItem['type'] == ITEM_TYPE_SCRIPT) { - if ($update) { - if ($dbItems[$item['itemid']]['type'] == $fullItem['type']) { - $flags = API_NOT_EMPTY; - } - else { - $flags = API_REQUIRED | API_NOT_EMPTY; - } - } - else { - $flags = API_REQUIRED | API_NOT_EMPTY; + if ($item['type'] == ITEM_TYPE_JMX && $db_item['type'] != ITEM_TYPE_JMX) { + $item += array_intersect_key($db_item, array_flip(['jmx_endpoint'])); } - $api_input_rules = ['type' => API_OBJECT, 'fields' => [ - 'params' => ['type' => API_STRING_UTF8, 'flags' => $flags, 'length' => DB::getFieldLength('items', 'params')], - 'timeout' => [ - 'type' => API_TIME_UNIT, 'flags' => ($this instanceof CItemPrototype) - ? $flags | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO - : $flags | API_ALLOW_USER_MACRO, - 'in' => '1:'.SEC_PER_MIN - ], - 'parameters' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['name']], 'fields' => [ - 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('item_parameter', 'name')], - 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('item_parameter', 'value')] - ]] - ]]; - - $data = array_intersect_key($item, $api_input_rules['fields']); + if ($item['type'] == ITEM_TYPE_SNMP && $db_item['type'] != ITEM_TYPE_SNMP) { + $item += array_intersect_key($db_item, array_flip(['snmp_oid'])); + } - if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($inum + 1), $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); + if ($item['type'] === ITEM_TYPE_SSH && $item['authtype'] == ITEM_AUTHTYPE_PUBLICKEY + && $db_item['authtype'] != ITEM_AUTHTYPE_PUBLICKEY) { + $item += array_intersect_key($db_item, array_flip(['publickey', 'privatekey'])); } + + $api_input_rules['fields'] += $item_type::getUpdateValidationRules($db_item); } - $host = $dbHosts[$fullItem['hostid']]; + $api_input_rules['fields'] += CItemType::getDefaultValidationRules(); - // Validate update interval. - if (!in_array($fullItem['type'], [ITEM_TYPE_TRAPPER, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT]) - && ($fullItem['type'] != ITEM_TYPE_ZABBIX_ACTIVE || strncmp($fullItem['key_'], 'mqtt.get', 8) !== 0) - && !validateDelay($update_interval_parser, 'delay', $fullItem['delay'], $error)) { + if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($i + 1), $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } - // For non-numeric types, whichever value was entered in trends field, is overwritten to zero. - if ($fullItem['value_type'] == ITEM_VALUE_TYPE_STR || $fullItem['value_type'] == ITEM_VALUE_TYPE_LOG - || $fullItem['value_type'] == ITEM_VALUE_TYPE_TEXT) { - $item['trends'] = '0'; - } - - // Check if the item requires an interface. - if ($host['status'] == HOST_STATUS_TEMPLATE) { - unset($item['interfaceid']); - } - else { - $item_interface_type = itemTypeInterface($fullItem['type']); + if ($item['type'] == ITEM_TYPE_JMX) { + if (array_key_exists('username', $item) || array_key_exists('password', $item) + || ($db_item !== null && $db_item['type'] != ITEM_TYPE_JMX)) { + $_item = array_intersect_key($item, array_flip(['username', 'password'])); - if ($item_interface_type !== false) { - if (!array_key_exists('interfaceid', $fullItem) || !$fullItem['interfaceid']) { - if ($item_interface_type != INTERFACE_TYPE_OPT) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('No interface found.')); - } + if ($db_item === null) { + $_item += array_fill_keys(['username', 'password'], ''); } - elseif (!array_key_exists($fullItem['interfaceid'], $interfaces) - || bccomp($interfaces[$fullItem['interfaceid']]['hostid'], $fullItem['hostid']) != 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses host interface from non-parent host.')); + else { + $_item += array_intersect_key($db_item, array_flip(['username', 'password'])); } - elseif ($item_interface_type !== INTERFACE_TYPE_ANY && $item_interface_type !== INTERFACE_TYPE_OPT - && $interfaces[$fullItem['interfaceid']]['type'] != $item_interface_type) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses incorrect interface type.')); + + if (($_item['username'] === '') !== ($_item['password'] === '')) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.($i + 1), + _('both username and password should be either present or empty') + )); } } - // No interface required, just set it to zero. - else { - $item['interfaceid'] = 0; - } } - // item key - if ($fullItem['type'] == ITEM_TYPE_DB_MONITOR) { - if (!isset($fullItem['flags']) || $fullItem['flags'] != ZBX_FLAG_DISCOVERY_RULE) { - if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR) == 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _('Check the key, please. Default example was passed.') - ); - } - } - elseif ($fullItem['flags'] == ZBX_FLAG_DISCOVERY_RULE) { - if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR_DISCOVERY) == 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _('Check the key, please. Default example was passed.') + if (array_key_exists('query_fields', $item)) { + foreach ($item['query_fields'] as $query_field) { + if (count($query_field) != 1 || key($query_field) === '') { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/query_fields', _('nonempty key and value pair expected')) ); } } - } - elseif (($fullItem['type'] == ITEM_TYPE_SSH && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_SSH) == 0) - || ($fullItem['type'] == ITEM_TYPE_TELNET && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_TELNET) == 0)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Check the key, please. Default example was passed.')); - } - - // key - if ($item_key_parser->parse($fullItem['key_']) != CParser::PARSE_SUCCESS) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _params($this->getErrorMsg(self::ERROR_INVALID_KEY), [ - $fullItem['key_'], $fullItem['name'], $host['name'], $item_key_parser->getError() - ]) - ); - } - if (($fullItem['type'] == ITEM_TYPE_TRAPPER || $fullItem['type'] == ITEM_TYPE_HTTPAGENT) - && array_key_exists('trapper_hosts', $fullItem) && $fullItem['trapper_hosts'] !== '' - && !$ip_range_parser->parse($fullItem['trapper_hosts'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'trapper_hosts', $ip_range_parser->getError()) - ); - } - - // jmx - if ($fullItem['type'] == ITEM_TYPE_JMX) { - if (!array_key_exists('jmx_endpoint', $fullItem) && !$update) { - $item['jmx_endpoint'] = ZBX_DEFAULT_JMX_ENDPOINT; - } - if (array_key_exists('jmx_endpoint', $fullItem) && $fullItem['jmx_endpoint'] === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'jmx_endpoint', _('cannot be empty')) - ); - } + $item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : ''; - if (($fullItem['username'] === '') !== ($fullItem['password'] === '')) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'username', - _('both username and password should be either present or empty')) - ); - } - } - else { - if (array_key_exists('jmx_endpoint', $item) && $item['jmx_endpoint'] !== '') { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'jmx_endpoint', _('should be empty')) - ); - } - elseif (array_key_exists('jmx_endpoint', $fullItem) && $fullItem['jmx_endpoint'] !== '') { - $item['jmx_endpoint'] = ''; + if (strlen($item['query_fields']) > DB::getFieldLength('items', 'query_fields')) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/query_fields', _('value is too long') + )); } } - // Dependent item. - if ($fullItem['type'] == ITEM_TYPE_DEPENDENT) { - if ($update) { - if (array_key_exists('master_itemid', $item) && !$item['master_itemid']) { - self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('cannot be empty') - )); - } - if ($dbItems[$fullItem['itemid']]['type'] != ITEM_TYPE_DEPENDENT - && !array_key_exists('master_itemid', $item)) { - self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('cannot be empty') + if (array_key_exists('headers', $item)) { + foreach ($item['headers'] as $name => $value) { + if (trim($name) === '' || !is_string($value) || $value === '') { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/headers', _('nonempty key and value pair expected') )); } } - elseif (!array_key_exists('master_itemid', $item) || !$item['master_itemid']) { - self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('cannot be empty') - )); - } - if (array_key_exists('master_itemid', $item) && !is_int($item['master_itemid']) - && !(is_string($item['master_itemid']) && ctype_digit($item['master_itemid']))) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value "%1$s" for "%2$s" field.', - $item['master_itemid'], 'master_itemid' - )); - } - } - else { - if (array_key_exists('master_itemid', $item) && $item['master_itemid']) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('should be empty') - )); - } - $item['master_itemid'] = 0; - } - // ssh, telnet - if ($fullItem['type'] == ITEM_TYPE_SSH || $fullItem['type'] == ITEM_TYPE_TELNET) { - if ($fullItem['username'] === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, _('No authentication user name specified.')); - } + $item['headers'] = self::headersArrayToString($item['headers']); - if ($fullItem['type'] == ITEM_TYPE_SSH && $fullItem['authtype'] == ITEM_AUTHTYPE_PUBLICKEY) { - if ($fullItem['publickey'] === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, _('No public key file specified.')); - } - if ($fullItem['privatekey'] === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, _('No private key file specified.')); - } + if (strlen($item['headers']) > DB::getFieldLength('items', 'headers')) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/headers', _('value is too long') + )); } } + } + unset($item); + } - // Prevent IPMI sensor field being empty if item key is not "ipmi.get". - if ($fullItem['type'] == ITEM_TYPE_IPMI && $fullItem['key_'] !== 'ipmi.get' - && (!array_key_exists('ipmi_sensor', $fullItem) || $fullItem['ipmi_sensor'] === '')) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'ipmi_sensor', _('cannot be empty') - )); - } - - // snmp trap - if ($fullItem['type'] == ITEM_TYPE_SNMPTRAP - && $fullItem['key_'] !== 'snmptrap.fallback' && $item_key_parser->getKey() !== 'snmptrap') { - self::exception(ZBX_API_ERROR_PARAMETERS, _('SNMP trap key is invalid.')); - } - - // snmp oid - if ($fullItem['type'] == ITEM_TYPE_SNMP - && (!array_key_exists('snmp_oid', $fullItem) || $fullItem['snmp_oid'] === '')) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('No SNMP OID specified.')); - } - - $this->checkSpecificFields($fullItem, $update ? 'update' : 'create'); + /** + * @param array $items + */ + protected static function validateUniqueness(array &$items): void { + $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['hostid', 'key_']], 'fields' => [ + 'hostid' => ['type' => API_ANY], + 'key_' => ['type' => API_ANY] + ]]; - $this->validateItemPreprocessing($fullItem); - $this->validateTags($item, '/'.$index); + if (!CApiInputValidator::validateUniqueness($api_input_rules, $items, '/', $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); } - unset($item); + } - $this->validateValueMaps($items); + /** + * @return array + */ + protected static function getTagsValidationRules(): array { + return ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ + 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('item_tag', 'tag')], + 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('item_tag', 'value')] + ]]; + } - $this->checkAndAddUuid($items, $dbHosts, $update); - $this->checkExistingItems($items); + /** + * @param int $flags + * + * @return array + */ + public static function getPreprocessingValidationRules(int $flags = 0x00): array { + return [ + 'type' => API_OBJECTS, + 'uniq_by_values' => [ + ['type' => [ZBX_PREPROC_DELTA_VALUE, ZBX_PREPROC_DELTA_SPEED]], + ['type' => [ZBX_PREPROC_THROTTLE_VALUE, ZBX_PREPROC_THROTTLE_TIMED_VALUE]], + ['type' => [ZBX_PREPROC_PROMETHEUS_PATTERN, ZBX_PREPROC_PROMETHEUS_TO_JSON]], + ['type' => [ZBX_PREPROC_VALIDATE_NOT_SUPPORTED]] + ], + 'fields' => [ + 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', static::SUPPORTED_PREPROCESSING_TYPES)], + 'params' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'type', 'in' => implode(',', static::PREPROC_TYPES_WITH_PARAMS)], 'type' => API_PREPROC_PARAMS, 'flags' => API_REQUIRED | API_ALLOW_USER_MACRO | ($flags & API_ALLOW_LLD_MACRO), 'preproc_type' => ['field' => 'type'], 'length' => DB::getFieldLength('item_preproc', 'params')], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('item_preproc', 'params')] + ]], + 'error_handler' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'type', 'in' => implode(',', array_diff(static::PREPROC_TYPES_WITH_ERR_HANDLING, [ZBX_PREPROC_VALIDATE_NOT_SUPPORTED]))], 'type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PREPROC_FAIL_DEFAULT, ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE, ZBX_PREPROC_FAIL_SET_ERROR])], + ['if' => ['field' => 'type', 'in' => ZBX_PREPROC_VALIDATE_NOT_SUPPORTED], 'type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE, ZBX_PREPROC_FAIL_SET_ERROR])], + ['else' => true, 'type' => API_INT32, 'in' => DB::getDefault('item_preproc', 'error_handler')] + ]], + 'error_handler_params' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => static function (array $data): bool { + return array_key_exists('error_handler', $data) && $data['error_handler'] == ZBX_PREPROC_FAIL_SET_VALUE; + }, 'type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('item_preproc', 'error_handler_params')], + ['if' => static function (array $data): bool { + return array_key_exists('error_handler', $data) && $data['error_handler'] == ZBX_PREPROC_FAIL_SET_ERROR; + }, 'type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('item_preproc', 'error_handler_params')], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('item_preproc', 'error_handler_params')] + ]] + ] + ]; } /** - * Check that only items on templates have UUID. Add UUID to all host prototypes on templates, - * if it doesn't exist. + * Check that host IDs of given items are valid. + * If host IDs are valid, $db_hosts and $db_templates parameters will be filled with found hosts and templates. * - * @param array $items_to_create - * @param array $db_hosts - * @param bool $is_update + * @param array $items + * @param array|null $db_hosts + * @param array|null $db_templates * * @throws APIException */ - protected function checkAndAddUuid(array &$items_to_create, array $db_hosts, bool $is_update): void { - if ($is_update) { - foreach ($items_to_create as $index => &$item) { - if (array_key_exists('uuid', $item)) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', '/' . ($index + 1), - _s('unexpected parameter "%1$s"', 'uuid') - ) - ); - } - } + protected static function checkHostsAndTemplates(array $items, array &$db_hosts = null, + array &$db_templates = null): void { + $hostids = array_unique(array_column($items, 'hostid')); + + $db_templates = API::Template()->get([ + 'output' => [], + 'templateids' => $hostids, + 'editable' => true, + 'preservekeys' => true + ]); - return; + $_hostids = array_diff($hostids, array_keys($db_templates)); + + $db_hosts = $_hostids + ? API::Host()->get([ + 'output' => ['status'], + 'hostids' => $_hostids, + 'editable' => true, + 'preservekeys' => true + ]) + : []; + + if (count($db_templates) + count($db_hosts) != count($hostids)) { + self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); } + } - foreach ($items_to_create as $index => &$item) { - if ($db_hosts[$item['hostid']]['status'] != HOST_STATUS_TEMPLATE && array_key_exists('uuid', $item)) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', '/' . ($index + 1), _s('unexpected parameter "%1$s"', 'uuid')) - ); + /** + * Add host_status property to given items in accordance of given hosts and templates statuses. + * + * @param array $items + * @param array $db_hosts + * @param array $db_templates + */ + protected static function addHostStatus(array &$items, array $db_hosts, array $db_templates): void { + foreach ($items as &$item) { + if (array_key_exists($item['hostid'], $db_templates)) { + $item['host_status'] = HOST_STATUS_TEMPLATE; } - - if ($db_hosts[$item['hostid']]['status'] == HOST_STATUS_TEMPLATE && !array_key_exists('uuid', $item)) { - $item['uuid'] = generateUuidV4(); + else { + $item['host_status'] = $db_hosts[$item['hostid']]['status']; } } unset($item); - - $db_uuid = DB::select('items', [ - 'output' => ['uuid'], - 'filter' => ['uuid' => array_column($items_to_create, 'uuid')], - 'limit' => 1 - ]); - - if ($db_uuid) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Entry with UUID "%1$s" already exists.', $db_uuid[0]['uuid']) - ); - } } /** - * Validates tags. + * Add flags property to given items with the given flags value. * - * @param array $item - * @param array $item['tags'] - * @param string $item['tags'][]['tag'] - * @param string $item['tags'][]['value'] - * - * @throws APIException if the input is invalid. + * @param array $items + * @param int $flags */ - protected function validateTags(array $item, string $path = '/') { - if (!array_key_exists('tags', $item)) { - return; - } - - $api_input_rules = ['type' => API_OBJECT, 'fields' => [ - 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ - 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('item_tag', 'tag')], - 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('item_tag', 'value')] - ]] - ]]; - - $item_tags = ['tags' => $item['tags']]; - if (!CApiInputValidator::validate($api_input_rules, $item_tags, $path, $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); + protected static function addFlags(array &$items, int $flags): void { + foreach ($items as &$item) { + $item['flags'] = $flags; } + unset($item); } /** - * Check item specific fields. Each API like Item, Itemprototype and Discovery rule may inherit different fields - * to validate. + * Check and add UUID to all item prototypes on templates, if it doesn't exist. * - * @param array $item An array of single item data. - * @param string $method A string of "create" or "update" method. + * @param array $items * - * @return bool + * @throws APIException */ - abstract protected function checkSpecificFields(array $item, $method); - - protected function clearValues(array $item) { - if (isset($item['port']) && $item['port'] != '') { - $item['port'] = ltrim($item['port'], '0'); - if ($item['port'] == '') { - $item['port'] = 0; + protected static function checkAndAddUuid(array &$items): void { + foreach ($items as &$item) { + if ($item['host_status'] == HOST_STATUS_TEMPLATE && !array_key_exists('uuid', $item)) { + $item['uuid'] = generateUuidV4(); } } + unset($item); + + $uuids = array_column($items, 'uuid'); - if (array_key_exists('type', $item) && - ($item['type'] == ITEM_TYPE_DEPENDENT || $item['type'] == ITEM_TYPE_TRAPPER - || ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE && array_key_exists('key_', $item) - && strncmp($item['key_'], 'mqtt.get', 8) === 0))) { - $item['delay'] = 0; + if (!$uuids) { + return; } - return $item; - } + $duplicates = DB::select('items', [ + 'output' => ['uuid'], + 'filter' => ['uuid' => $uuids], + 'limit' => 1 + ]); - protected function errorInheritFlags($flag, $key, $host) { - switch ($flag) { - case ZBX_FLAG_DISCOVERY_NORMAL: - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item.', $key, $host)); - break; - case ZBX_FLAG_DISCOVERY_RULE: - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as a discovery rule.', $key, $host)); - break; - case ZBX_FLAG_DISCOVERY_PROTOTYPE: - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item prototype.', $key, $host)); - break; - case ZBX_FLAG_DISCOVERY_CREATED: - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item created from item prototype.', $key, $host)); - break; - default: - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as unknown item element.', $key, $host)); + if ($duplicates) { + self::exception(ZBX_API_ERROR_PARAMETERS, + _s('Entry with UUID "%1$s" already exists.', $duplicates[0]['uuid']) + ); } } /** - * Return first main interface matched from list of preferred types, or NULL. - * - * @param array $interfaces An array of interfaces to choose from. + * @param array $items + * @param array|null $hostids * - * @return ?array + * @return array */ - public static function findInterfaceByPriority(array $interfaces): ?array { - $interface_by_type = []; + protected static function getTemplateLinks(array $items, ?array $hostids): array { + if ($hostids !== null) { + $db_hosts = DB::select('hosts', [ + 'output' => ['hostid', 'status'], + 'hostids' => $hostids, + 'preservekeys' => true + ]); - foreach ($interfaces as $interface) { - if ($interface['main'] == INTERFACE_PRIMARY) { - $interface_by_type[$interface['type']] = $interface; + $tpl_links = []; + + foreach ($items as $item) { + $tpl_links[$item['hostid']] = $db_hosts; } } + else { + $templateids = []; - foreach (self::INTERFACE_TYPES_BY_PRIORITY as $interface_type) { - if (array_key_exists($interface_type, $interface_by_type)) { - return $interface_by_type[$interface_type]; + foreach ($items as $item) { + if ($item['host_status'] == HOST_STATUS_TEMPLATE) { + $templateids[$item['hostid']] = true; + } } - } - return null; - } + if (!$templateids) { + return []; + } - /** - * Returns the interface that best matches the given item. - * - * @param array $item_type An item type - * @param array $interfaces An array of interfaces to choose from - * - * @return array|boolean The best matching interface; - * an empty array of no matching interface was found; - * false, if the item does not need an interface - */ - public static function findInterfaceForItem($item_type, array $interfaces) { - $type = itemTypeInterface($item_type); + $result = DBselect( + 'SELECT ht.templateid,ht.hostid,h.status'. + ' FROM hosts_templates ht,hosts h'. + ' WHERE ht.hostid=h.hostid'. + ' AND '.dbConditionId('ht.templateid', array_keys($templateids)) + ); - if ($type == INTERFACE_TYPE_OPT) { - return false; - } - elseif ($type == INTERFACE_TYPE_ANY) { - return self::findInterfaceByPriority($interfaces); - } - // the item uses a specific type of interface - elseif ($type !== false) { - $interface_by_type = []; + $tpl_links = []; - foreach ($interfaces as $interface) { - if ($interface['main'] == INTERFACE_PRIMARY) { - $interface_by_type[$interface['type']] = $interface; - } + while ($row = DBfetch($result)) { + $tpl_links[$row['templateid']][$row['hostid']] = [ + 'hostid' => $row['hostid'], + 'status' => $row['status'] + ]; } - - return array_key_exists($type, $interface_by_type) ? $interface_by_type[$type] : []; - } - // the item does not need an interface - else { - return false; } + + return $tpl_links; } /** - * Updates the children of the item on the given hosts and propagates the inheritance to the child hosts. + * Filter out inheritable items from the given items. * - * @param array $tpl_items An array of items to inherit. - * @param array|null $hostids An array of hosts to inherit to; if set to null, the items will be inherited to all - * linked hosts or templates. + * @param array $items + * @param array $db_items + * @param array $tpl_links */ - protected function inherit(array $tpl_items, array $hostids = null) { - $tpl_items = zbx_toHash($tpl_items, 'itemid'); - - // Inherit starting from common items and finishing up dependent. - while ($tpl_items) { - $_tpl_items = []; + protected static function filterObjectsToInherit(array &$items, array &$db_items, array $tpl_links): void { + foreach ($items as $i => $item) { + if (!array_key_exists($item['hostid'], $tpl_links)) { + unset($items[$i]); - foreach ($tpl_items as $tpl_item) { - if ($tpl_item['type'] != ITEM_TYPE_DEPENDENT - || !array_key_exists($tpl_item['master_itemid'], $tpl_items)) { - $_tpl_items[$tpl_item['itemid']] = $tpl_item; + if (array_key_exists($item['itemid'], $db_items)) { + unset($db_items[$item['itemid']]); } } - - foreach ($_tpl_items as $itemid => $_tpl_item) { - unset($tpl_items[$itemid]); - } - - $this->_inherit($_tpl_items, $hostids); } } /** - * Auxiliary method for item inheritance. See full description in inherit() method. + * Check that no items with repeating keys would be inherited to a single host or template. + * + * @param array $items + * @param array $db_items + * @param array $tpl_links + * + * @throws APIException */ - private function _inherit(array $tpl_items, array $hostids = null) { - // Prepare the child items. - $new_items = $this->prepareInheritedItems($tpl_items, $hostids); - if (!$new_items) { - return; - } + protected static function checkDoubleInheritedNames(array $items, array $db_items, array $tpl_links): void { + $item_indexes = []; - $ins_items = []; - $upd_items = []; - - foreach ($new_items as $new_item) { - if (array_key_exists('itemid', $new_item)) { - if ($this instanceof CItemPrototype) { - unset($new_item['ruleid']); - } - $upd_items[$new_item['itemid']] = $new_item; - } - else { - $ins_items[] = $new_item; + foreach ($items as $i => $item) { + if (array_key_exists($item['itemid'], $db_items) && $item['key_'] === $db_items[$item['itemid']]['key_']) { + continue; } - } - $this->validateDependentItems($new_items); + $item_indexes[$item['key_']][] = $i; + } - // Save the new items. - if ($ins_items) { - if ($this instanceof CItem) { - static::validateInventoryLinks($ins_items, false); + foreach ($item_indexes as $key => $indexes) { + if (count($indexes) == 1) { + continue; } - $this->createReal($ins_items); - } + $tpl_items = array_column(array_intersect_key($items, array_flip($indexes)), null, 'hostid'); + $templateids = array_keys($tpl_items); + $template_count = count($templateids) - 1; - if ($upd_items) { - if ($this instanceof CItem) { - static::validateInventoryLinks($upd_items, true); - } + for ($i = 0; $i < $template_count - 1; $i++) { + for ($j = $i + 1; $j < $template_count; $j++) { + $same_hosts = array_intersect_key($tpl_links[$templateids[$i]], $tpl_links[$templateids[$j]]); - $this->updateReal($upd_items); - } + if ($same_hosts) { + $same_host = reset($same_hosts); - $new_items = array_merge($upd_items, $ins_items); + $hosts = DB::select('hosts', [ + 'output' => ['hostid', 'host'], + 'hostids' => [$templateids[$i], $templateids[$j], $same_host['hostid']], + 'preservekeys' => true + ]); - // Inheriting items from the templates. - $db_items = DBselect( - 'SELECT i.itemid'. - ' FROM items i,hosts h'. - ' WHERE i.hostid=h.hostid'. - ' AND '.dbConditionInt('i.itemid', zbx_objectValues($new_items, 'itemid')). - ' AND '.dbConditionInt('h.status', [HOST_STATUS_TEMPLATE]) - ); + $target_is_host = in_array($same_host['status'], + [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED] + ); - $tpl_itemids = []; - while ($db_item = DBfetch($db_items)) { - $tpl_itemids[$db_item['itemid']] = true; - } + switch ($tpl_items[$templateids[$i]]['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + $error = $target_is_host + ? _('Cannot inherit items with key "%1$s" of both "%2$s" and "%3$s" templates, because the key must be unique on host "%4$s".') + : _('Cannot inherit items with key "%1$s" of both "%2$s" and "%3$s" templates, because the key must be unique on template "%4$s".'); + break; + + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + $error = $target_is_host + ? _('Cannot inherit item prototypes with key "%1$s" of both "%2$s" and "%3$s" templates, because the key must be unique on host "%4$s".') + : _('Cannot inherit item prototypes with key "%1$s" of both "%2$s" and "%3$s" templates, because the key must be unique on template "%4$s".'); + break; + + case ZBX_FLAG_DISCOVERY_RULE: + $error = $target_is_host + ? _('Cannot inherit LDD rules with key "%1$s" of both "%2$s" and "%3$s" templates, because the key must be unique on host "%4$s".') + : _('Cannot inherit LDD rules with key "%1$s" of both "%2$s" and "%3$s" templates, because the key must be unique on template "%4$s".'); + break; + } - foreach ($new_items as $index => $new_item) { - if (!array_key_exists($new_item['itemid'], $tpl_itemids)) { - unset($new_items[$index]); + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $key, + $hosts[$templateids[$i]]['host'], $hosts[$templateids[$j]]['host'], + $hosts[$same_host['hostid']]['host'] + )); + } + } } } - - $this->inherit($new_items); } /** - * Prepares and returns an array of child items, inherited from items $tpl_items on the given hosts. - * - * @param array $tpl_items - * @param string $tpl_items[<itemid>]['itemid'] - * @param string $tpl_items[<itemid>]['hostid'] - * @param string $tpl_items[<itemid>]['key_'] - * @param int $tpl_items[<itemid>]['type'] - * @param array $tpl_items[<itemid>]['preprocessing'] (optional) - * @param int $tpl_items[<itemid>]['preprocessing'][]['type'] - * @param string $tpl_items[<itemid>]['preprocessing'][]['params'] - * @param int $tpl_items[<itemid>]['flags'] - * @param string $tpl_items[<itemid>]['master_itemid'] (optional) - * @param mixed $tpl_items[<itemid>][<field_name>] (optional) - * @param array|null $hostids + * Get item chunks to inherit. * - * @return array an array of unsaved child items + * @param array $items + * @param array $tpl_links + * + * @return array */ - private function prepareInheritedItems(array $tpl_items, array $hostids = null) { - $itemids_by_templateid = []; - foreach ($tpl_items as $tpl_item) { - $itemids_by_templateid[$tpl_item['hostid']][] = $tpl_item['itemid']; - } - - // Fetch all child hosts. - $chd_hosts = API::Host()->get([ - 'output' => ['hostid', 'host', 'status'], - 'selectParentTemplates' => ['templateid'], - 'selectInterfaces' => ['interfaceid', 'main', 'type'], - 'templateids' => array_keys($itemids_by_templateid), - 'hostids' => $hostids, - 'preservekeys' => true, - 'nopermissions' => true, - 'templated_hosts' => true - ]); - if (!$chd_hosts) { - return []; - } + protected static function getInheritChunks(array $items, array $tpl_links): array { + $chunks = [ + [ + 'item_indexes' => [], + 'hosts' => [], + 'size' => 0 + ] + ]; + $last = 0; - $chd_items_tpl = []; - $chd_items_key = []; + foreach ($items as $i => $item) { + $hosts_chunks = array_chunk($tpl_links[$item['hostid']], self::INHERIT_CHUNK_SIZE, true); - // Preparing list of items by item templateid. - $sql = 'SELECT i.itemid,i.hostid,i.type,i.key_,i.flags,i.templateid'. - ' FROM items i'. - ' WHERE '.dbConditionInt('i.templateid', zbx_objectValues($tpl_items, 'itemid')); - if ($hostids !== null) { - $sql .= ' AND '.dbConditionInt('i.hostid', $hostids); - } - $db_items = DBselect($sql); + foreach ($hosts_chunks as $hosts) { + if ($chunks[$last]['size'] < self::INHERIT_CHUNK_SIZE) { + $_hosts = array_slice($hosts, 0, self::INHERIT_CHUNK_SIZE - $chunks[$last]['size'], true); - while ($db_item = DBfetch($db_items)) { - $hostid = $db_item['hostid']; - unset($db_item['hostid']); + $can_add_hosts = true; - $chd_items_tpl[$hostid][$db_item['templateid']] = $db_item; - } + foreach ($chunks[$last]['item_indexes'] as $_i) { + $new_hosts = array_diff_key($_hosts, $chunks[$last]['hosts']); - $hostids_by_key = []; + if (array_intersect_key($tpl_links[$items[$_i]['hostid']], $new_hosts)) { + $can_add_hosts = false; + break; + } + } - // Preparing list of items by item key. - foreach ($chd_hosts as $chd_host) { - $tpl_itemids = []; + if ($can_add_hosts) { + $chunks[$last]['item_indexes'][] = $i; + $chunks[$last]['hosts'] += $_hosts; + $chunks[$last]['size'] += count($_hosts); - foreach ($chd_host['parentTemplates'] as $parent_template) { - if (array_key_exists($parent_template['templateid'], $itemids_by_templateid)) { - $tpl_itemids = array_merge($tpl_itemids, $itemids_by_templateid[$parent_template['templateid']]); + $hosts = array_diff_key($hosts, $_hosts); + } } - } - foreach ($tpl_itemids as $tpl_itemid) { - if (!array_key_exists($chd_host['hostid'], $chd_items_tpl) - || !array_key_exists($tpl_itemid, $chd_items_tpl[$chd_host['hostid']])) { - $hostids_by_key[$tpl_items[$tpl_itemid]['key_']][] = $chd_host['hostid']; + if ($hosts) { + $chunks[++$last] = [ + 'item_indexes' => [$i], + 'hosts' => $hosts, + 'size' => count($hosts) + ]; } } } - foreach ($hostids_by_key as $key_ => $key_hostids) { - $sql_select = ($this instanceof CItemPrototype) ? ',id.parent_itemid AS ruleid' : ''; - // "LEFT JOIN" is needed to check flags on inherited and existing item, item prototype or lld rule. - // For example, when linking an item prototype with same key as in an item on target host or template. - $sql_join = ($this instanceof CItemPrototype) ? ' LEFT JOIN item_discovery id ON i.itemid=id.itemid' : ''; - $db_items = DBselect( - 'SELECT i.itemid,i.hostid,i.type,i.key_,i.flags,i.templateid'.$sql_select. - ' FROM items i'.$sql_join. - ' WHERE '.dbConditionInt('i.hostid', $key_hostids). - ' AND '.dbConditionString('i.key_', [$key_]) - ); - - while ($db_item = DBfetch($db_items)) { - $hostid = $db_item['hostid']; - unset($db_item['hostid']); + return $chunks; + } - $chd_items_key[$hostid][$db_item['key_']] = $db_item; - } - } + /** + * @param array $item + * @param array $upd_db_item + * + * @throws APIException + */ + protected static function showObjectMismatchError(array $item, array $upd_db_item): void { + $target_is_host = in_array($upd_db_item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]); - // List of the discovery rules. - if ($this instanceof CItemPrototype) { - // List of itemids without 'ruleid' property. - $tpl_itemids = []; - $tpl_ruleids = []; - foreach ($tpl_items as $tpl_item) { - if (!array_key_exists('ruleid', $tpl_item)) { - $tpl_itemids[] = $tpl_item['itemid']; - } - else { - $tpl_ruleids[$tpl_item['ruleid']] = true; - } - } + $hosts = DB::select('hosts', [ + 'output' => ['host'], + 'hostids' => [$item['hostid'], $upd_db_item['hostid']], + 'preservekeys' => true + ]); - if ($tpl_itemids) { - $db_rules = DBselect( - 'SELECT id.parent_itemid,id.itemid'. - ' FROM item_discovery id'. - ' WHERE '.dbConditionInt('id.itemid', $tpl_itemids) - ); + $error = ''; - while ($db_rule = DBfetch($db_rules)) { - $tpl_items[$db_rule['itemid']]['ruleid'] = $db_rule['parent_itemid']; - $tpl_ruleids[$db_rule['parent_itemid']] = true; + switch ($item['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + switch ($upd_db_item['flags']) { + case ZBX_FLAG_DISCOVERY_RULE: + $error = $target_is_host + ? _('Cannot inherit item with key "%1$s" of template "%2$s" to host "%3$s", because an LLD rule with the same key already exists.') + : _('Cannot inherit item with key "%1$s" of template "%2$s" to template "%3$s", because an LLD rule with the same key already exists.'); + break 2; + + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + $error = $target_is_host + ? _('Cannot inherit item with key "%1$s" of template "%2$s" to host "%3$s", because an item prototype with the same key already exists.') + : _('Cannot inherit item with key "%1$s" of template "%2$s" to template "%3$s", because an item prototype with the same key already exists.'); + break 2; + + case ZBX_FLAG_DISCOVERY_CREATED: + $error = $target_is_host + ? _('Cannot inherit item with key "%1$s" of template "%2$s" to host "%3$s", because a discovered item with the same key already exists.') + : _('Cannot inherit item with key "%1$s" of template "%2$s" to template "%3$s", because a discovered item with the same key already exists.'); + break 2; } - } - - $sql = 'SELECT i.hostid,i.templateid,i.itemid'. - ' FROM items i'. - ' WHERE '.dbConditionInt('i.templateid', array_keys($tpl_ruleids)); - if ($hostids !== null) { - $sql .= ' AND '.dbConditionInt('i.hostid', $hostids); - } - $db_rules = DBselect($sql); - // List of child lld ruleids by child hostid and parent lld ruleid. - $chd_ruleids = []; - while ($db_rule = DBfetch($db_rules)) { - $chd_ruleids[$db_rule['hostid']][$db_rule['templateid']] = $db_rule['itemid']; - } - } + case ZBX_FLAG_DISCOVERY_RULE: + switch ($upd_db_item['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + $error = $target_is_host + ? _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to host "%3$s", because a discovered item with the same key already exists.') + : _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to template "%3$s", because a discovered item with the same key already exists.'); + break 2; + + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + $error = $target_is_host + ? _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to host "%3$s", because an item prototype with the same key already exists.') + : _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to template "%3$s", because an item prototype with the same key already exists.'); + break 2; + + case ZBX_FLAG_DISCOVERY_CREATED: + $error = $target_is_host + ? _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to host "%3$s", because a discovered item with the same key already exists.') + : _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to template "%3$s", because a discovered item with the same key already exists.'); + break 2; + } - $new_items = []; - // List of the updated item keys by hostid. - $upd_hostids_by_key = []; + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + switch ($upd_db_item['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + $error = $target_is_host + ? _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to host "%3$s", because a discovered item with the same key already exists.') + : _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to template "%3$s", because a discovered item with the same key already exists.'); + break 2; - foreach ($chd_hosts as $chd_host) { - $tpl_itemids = []; + case ZBX_FLAG_DISCOVERY_RULE: + $error = $target_is_host + ? _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to host "%3$s", because an LLD rule with the same key already exists.') + : _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to template "%3$s", because an LLD rule with the same key already exists.'); + break 2; - foreach ($chd_host['parentTemplates'] as $parent_template) { - if (array_key_exists($parent_template['templateid'], $itemids_by_templateid)) { - $tpl_itemids = array_merge($tpl_itemids, $itemids_by_templateid[$parent_template['templateid']]); + case ZBX_FLAG_DISCOVERY_CREATED: + $error = $target_is_host + ? _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to host "%3$s", because a discovered item with the same key already exists.') + : _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to template "%3$s", because a discovered item with the same key already exists.'); + break 2; } - } - - foreach ($tpl_itemids as $tpl_itemid) { - $tpl_item = $tpl_items[$tpl_itemid]; - - $chd_item = null; + } - // Update by templateid. - if (array_key_exists($chd_host['hostid'], $chd_items_tpl) - && array_key_exists($tpl_item['itemid'], $chd_items_tpl[$chd_host['hostid']])) { - $chd_item = $chd_items_tpl[$chd_host['hostid']][$tpl_item['itemid']]; + if ($error) { + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $upd_db_item['key_'], + $hosts[$item['hostid']]['host'], $hosts[$upd_db_item['hostid']]['host'] + )); + } - if ($tpl_item['key_'] !== $chd_item['key_']) { - $upd_hostids_by_key[$tpl_item['key_']][] = $chd_host['hostid']; - } - } - // Update by key. - elseif (array_key_exists($chd_host['hostid'], $chd_items_key) - && array_key_exists($tpl_item['key_'], $chd_items_key[$chd_host['hostid']])) { - $chd_item = $chd_items_key[$chd_host['hostid']][$tpl_item['key_']]; + if ($upd_db_item['templateid'] == 0) { + return; + } - // Check if an item of a different type with the same key exists. - if ($tpl_item['flags'] != $chd_item['flags']) { - $this->errorInheritFlags($chd_item['flags'], $chd_item['key_'], $chd_host['host']); - } + $template = DBfetch(DBselect( + 'SELECT h.host'. + ' FROM items i,hosts h'. + ' WHERE i.hostid=h.hostid'. + ' AND '.dbConditionId('i.itemid', [$upd_db_item['templateid']]) + )); - // Check if item already linked to another template. - if ($chd_item['templateid'] != 0 && bccomp($chd_item['templateid'], $tpl_item['itemid']) != 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, _params( - $this->getErrorMsg(self::ERROR_EXISTS_TEMPLATE), [$tpl_item['key_'], $chd_host['host']] - )); - } + switch ($item['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + $error = $target_is_host + ? _('Cannot inherit item with key "%1$s" of template "%2$s" to host "%3$s", because an item with the same key is already inherited from template "%4$s".') + : _('Cannot inherit item with key "%1$s" of template "%2$s" to template "%3$s", because an item with the same key is already inherited from template "%4$s".'); + break; - if ($this instanceof CItemPrototype) { - $chd_ruleid = $chd_ruleids[$chd_host['hostid']][$tpl_item['ruleid']]; - if (bccomp($chd_item['ruleid'], $chd_ruleid) != 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Item prototype "%1$s" already exists on "%2$s", linked to another rule.', - $chd_item['key_'], $chd_host['host'] - ) - ); - } - } - } + case ZBX_FLAG_DISCOVERY_RULE: + $error = $target_is_host + ? _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to host "%3$s", because an item with the same key is already inherited from template "%4$s".') + : _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to template "%3$s", because an item with the same key is already inherited from template "%4$s".'); + break; - // copying item - $new_item = $tpl_item; - $new_item['uuid'] = ''; + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + $error = $target_is_host + ? _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to host "%3$s", because an item with the same key is already inherited from template "%4$s".') + : _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to template "%3$s", because an item with the same key is already inherited from template "%4$s".'); + break; + } - if ($chd_item !== null) { - $new_item['itemid'] = $chd_item['itemid']; + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $upd_db_item['key_'], $hosts[$item['hostid']]['host'], + $hosts[$upd_db_item['hostid']]['host'], $template['host'] + )); + } - if ($new_item['type'] == ITEM_TYPE_HTTPAGENT) { - $new_item['interfaceid'] = null; - } - } - else { - unset($new_item['itemid']); - if ($this instanceof CItemPrototype) { - $new_item['ruleid'] = $chd_ruleids[$chd_host['hostid']][$tpl_item['ruleid']]; - } - } - $new_item['hostid'] = $chd_host['hostid']; - $new_item['templateid'] = $tpl_item['itemid']; + /** + * @param array $item + * + * @return array + */ + protected static function unsetNestedObjectIds(array $item): array { + if (array_key_exists('tags', $item)) { + foreach ($item['tags'] as &$tag) { + unset($tag['itemtagid']); + } + unset($tag); + } - if ($chd_host['status'] != HOST_STATUS_TEMPLATE) { - if ($chd_item === null || $new_item['type'] != $chd_item['type']) { - $interface = self::findInterfaceForItem($new_item['type'], $chd_host['interfaces']); + if (array_key_exists('preprocessing', $item)) { + foreach ($item['preprocessing'] as &$preprocessing) { + unset($preprocessing['item_preprocid']); + } + unset($preprocessing); + } - if ($interface) { - $new_item['interfaceid'] = $interface['interfaceid']; - } - elseif ($interface !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, _params( - $this->getErrorMsg(self::ERROR_NO_INTERFACE), [$chd_host['host'], $new_item['key_']] - )); - } - } + if (array_key_exists('parameters', $item)) { + foreach ($item['parameters'] as &$parameter) { + unset($parameter['item_parameterid']); + } + unset($parameter); + } - if ($this instanceof CItem || $this instanceof CDiscoveryRule) { - if (!array_key_exists('itemid', $new_item)) { - $new_item['rtdata'] = true; - } - } - } + return $item; + } - if (array_key_exists('preprocessing', $new_item)) { - foreach ($new_item['preprocessing'] as $preprocessing) { - if ($chd_item) { - $preprocessing['itemid'] = $chd_item['itemid']; - } - else { - unset($preprocessing['itemid']); - } - } - } + /** + * Update relation to master item for inherited dependent items. + * + * @param array $upd_items + * @param array $ins_items + * @param array $hostids + */ + protected static function setChildMasterItemIds(array &$upd_items, array &$ins_items, array $hostids): void { + $upd_item_indexes = []; + $ins_item_indexes = []; - $new_items[] = $new_item; + foreach ($upd_items as $i => $upd_item) { + if ($upd_item['type'] == ITEM_TYPE_DEPENDENT && array_key_exists('master_itemid', $upd_item)) { + $upd_item_indexes[$upd_item['master_itemid']][$upd_item['hostid']][] = $i; } } - // Check if item with a new key already exists on the child host. - if ($upd_hostids_by_key) { - $sql_where = []; - foreach ($upd_hostids_by_key as $key => $hostids) { - $sql_where[] = dbConditionInt('i.hostid', $hostids).' AND i.key_='.zbx_dbstr($key); + foreach ($ins_items as $i => $ins_item) { + if ($ins_item['type'] == ITEM_TYPE_DEPENDENT) { + $ins_item_indexes[$ins_item['master_itemid']][$ins_item['hostid']][] = $i; } + } - $sql = 'SELECT i.hostid,i.key_'. - ' FROM items i'. - ' WHERE ('.implode(') OR (', $sql_where).')'; - $db_items = DBselect($sql, 1); + if (!$upd_item_indexes && !$ins_item_indexes) { + return; + } - if ($db_item = DBfetch($db_items)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _params($this->getErrorMsg(self::ERROR_EXISTS), - [$db_item['key_'], $chd_hosts[$db_item['hostid']]['host']] - )); + $options = [ + 'output' => ['itemid', 'hostid', 'templateid'], + 'filter' => [ + 'templateid' => array_keys($ins_item_indexes + $upd_item_indexes), + 'hostid' => $hostids + ] + ]; + $result = DBselect(DB::makeSql('items', $options)); + + while ($row = DBfetch($result)) { + if (array_key_exists($row['templateid'], $upd_item_indexes) + && array_key_exists($row['hostid'], $upd_item_indexes[$row['templateid']])) { + foreach ($upd_item_indexes[$row['templateid']][$row['hostid']] as $i) { + $upd_items[$i]['master_itemid'] = $row['itemid']; + } } - } - return $this->prepareDependentItems($tpl_items, $new_items, $hostids); + if (array_key_exists($row['templateid'], $ins_item_indexes) + && array_key_exists($row['hostid'], $ins_item_indexes[$row['templateid']])) { + foreach ($ins_item_indexes[$row['templateid']][$row['hostid']] as $i) { + $ins_items[$i]['master_itemid'] = $row['itemid']; + } + } + } } /** - * Update relations for inherited dependent items to master items. - * - * @param array $tpl_items - * @param int $tpl_items[<itemid>]['type'] - * @param string $tpl_items[<itemid>]['master_itemid'] - * @param array $new_items - * @param string $new_items[<itemid>]['hostid'] - * @param int $new_items[<itemid>]['type'] - * @param string $new_items[<itemid>]['templateid'] - * @param array|null $hostids + * @param array $upd_items + * @param array $upd_db_items + * @param array $ins_items * - * @return array an array of synchronized inherited items. + * @throws APIException */ - private function prepareDependentItems(array $tpl_items, array $new_items, array $hostids = null) { - $tpl_master_itemids = []; + protected static function addInterfaceIds(array &$upd_items, array $upd_db_items, array &$ins_items): void { + $upd_item_indexes = []; + $ins_item_indexes = []; + $interface_types = []; - foreach ($tpl_items as $tpl_item) { - if ($tpl_item['type'] == ITEM_TYPE_DEPENDENT) { - $tpl_master_itemids[$tpl_item['master_itemid']] = true; - } - } + $required_interface_types = [INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_IPMI, + INTERFACE_TYPE_JMX + ]; - if ($tpl_master_itemids) { - $sql = 'SELECT i.itemid,i.hostid,i.templateid'. - ' FROM items i'. - ' WHERE '.dbConditionId('i.templateid', array_keys($tpl_master_itemids)); - if ($hostids !== null) { - $sql .= ' AND '.dbConditionId('i.hostid', $hostids); + $upd_item_indexes_by_interfaceid = []; + + foreach ($upd_items as $i => $upd_item) { + if (!in_array($upd_item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])) { + continue; } - $db_items = DBselect($sql); - $master_links = []; + $interface_type = itemTypeInterface($upd_item['type']); - while ($db_item = DBfetch($db_items)) { - $master_links[$db_item['templateid']][$db_item['hostid']] = $db_item['itemid']; + if (!in_array($interface_type, $required_interface_types)) { + continue; } - foreach ($new_items as &$new_item) { - if ($new_item['type'] == ITEM_TYPE_DEPENDENT) { - $tpl_item = $tpl_items[$new_item['templateid']]; + if ($upd_db_items[$upd_item['itemid']]['interfaceid'] != 0) { + $upd_item_indexes_by_interfaceid[$upd_db_items[$upd_item['itemid']]['interfaceid']][] = $i; + } + else { + $upd_item_indexes[$upd_item['hostid']][$interface_type][] = $i; + $interface_types[$interface_type] = true; + } + } + + if ($upd_item_indexes_by_interfaceid) { + $options = [ + 'output' => ['interfaceid', 'type'], + 'interfaceids' => array_keys($upd_item_indexes_by_interfaceid) + ]; + $result = DBselect(DB::makeSql('interface', $options)); + + while ($row = DBfetch($result)) { + foreach ($upd_item_indexes_by_interfaceid[$row['interfaceid']] as $i) { + $upd_item = $upd_items[$i]; + $interface_type = itemTypeInterface($upd_item['type']); - if (array_key_exists('master_itemid', $tpl_item)) { - $new_item['master_itemid'] = $master_links[$tpl_item['master_itemid']][$new_item['hostid']]; + if ($interface_type != $row['type']) { + $upd_item_indexes[$upd_item['hostid']][$interface_type][] = $i; + $interface_types[$interface_type] = true; } } } - unset($new_item); } - return $new_items; - } + foreach ($ins_items as $i => $ins_item) { + if (!in_array($ins_item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])) { + continue; + } - /** - * Validate item pre-processing. - * - * @param array $item An array of single item data. - * @param array $item['preprocessing'] An array of item pre-processing data. - * @param string $item['preprocessing'][]['type'] The preprocessing option type. Possible values: - * 1 - ZBX_PREPROC_MULTIPLIER; - * 2 - ZBX_PREPROC_RTRIM; - * 3 - ZBX_PREPROC_LTRIM; - * 4 - ZBX_PREPROC_TRIM; - * 5 - ZBX_PREPROC_REGSUB; - * 6 - ZBX_PREPROC_BOOL2DEC; - * 7 - ZBX_PREPROC_OCT2DEC; - * 8 - ZBX_PREPROC_HEX2DEC; - * 9 - ZBX_PREPROC_DELTA_VALUE; - * 10 - ZBX_PREPROC_DELTA_SPEED; - * 11 - ZBX_PREPROC_XPATH; - * 12 - ZBX_PREPROC_JSONPATH; - * 13 - ZBX_PREPROC_VALIDATE_RANGE; - * 14 - ZBX_PREPROC_VALIDATE_REGEX; - * 15 - ZBX_PREPROC_VALIDATE_NOT_REGEX; - * 16 - ZBX_PREPROC_ERROR_FIELD_JSON; - * 17 - ZBX_PREPROC_ERROR_FIELD_XML; - * 18 - ZBX_PREPROC_ERROR_FIELD_REGEX; - * 19 - ZBX_PREPROC_THROTTLE_VALUE; - * 20 - ZBX_PREPROC_THROTTLE_TIMED_VALUE; - * 21 - ZBX_PREPROC_SCRIPT; - * 22 - ZBX_PREPROC_PROMETHEUS_PATTERN; - * 23 - ZBX_PREPROC_PROMETHEUS_TO_JSON; - * 24 - ZBX_PREPROC_CSV_TO_JSON; - * 25 - ZBX_PREPROC_STR_REPLACE; - * 26 - ZBX_PREPROC_VALIDATE_NOT_SUPPORTED; - * @param string $item['preprocessing'][]['params'] Additional parameters used by preprocessing - * option. Multiple parameters are separated by LF - * (\n) character. - * @param string $item['preprocessing'][]['error_handler'] Action type used in case of preprocessing step - * failure. Possible values: - * 0 - ZBX_PREPROC_FAIL_DEFAULT; - * 1 - ZBX_PREPROC_FAIL_DISCARD_VALUE; - * 2 - ZBX_PREPROC_FAIL_SET_VALUE; - * 3 - ZBX_PREPROC_FAIL_SET_ERROR. - * @param string $item['preprocessing'][]['error_handler_params'] Error handler parameters. - */ - protected function validateItemPreprocessing(array $item) { - if (array_key_exists('preprocessing', $item)) { - if (!is_array($item['preprocessing'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); + $interface_type = itemTypeInterface($ins_item['type']); + + if (!in_array($interface_type, $required_interface_types)) { + continue; } - $type_validator = new CLimitedSetValidator(['values' => static::SUPPORTED_PREPROCESSING_TYPES]); + $ins_item_indexes[$ins_item['hostid']][$interface_type][] = $i; + $interface_types[$interface_type] = true; + } - $error_handler_validator = new CLimitedSetValidator([ - 'values' => [ZBX_PREPROC_FAIL_DEFAULT, ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE, - ZBX_PREPROC_FAIL_SET_ERROR - ] - ]); + if (!$upd_item_indexes && !$ins_item_indexes) { + return; + } - $unsupported_error_handler_validator = new CLimitedSetValidator([ - 'values' => [ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE, ZBX_PREPROC_FAIL_SET_ERROR] - ]); + $options = [ + 'output' => ['interfaceid', 'hostid', 'type'], + 'filter' => [ + 'hostid' => array_keys($upd_item_indexes + $ins_item_indexes), + 'type' => array_keys($interface_types), + 'main' => INTERFACE_PRIMARY + ] + ]; + $result = DBselect(DB::makeSql('interface', $options)); - $prometheus_pattern_parser = new CPrometheusPatternParser(['usermacros' => true, - 'lldmacros' => ($this instanceof CItemPrototype) - ]); - $prometheus_output_parser = new CPrometheusOutputParser(['usermacros' => true, - 'lldmacros' => ($this instanceof CItemPrototype) - ]); + while ($row = DBfetch($result)) { + if (array_key_exists($row['hostid'], $upd_item_indexes) + && array_key_exists($row['type'], $upd_item_indexes[$row['hostid']])) { + foreach ($upd_item_indexes[$row['hostid']][$row['type']] as $_i => $i) { + $upd_items[$i]['interfaceid'] = $row['interfaceid']; - $required_fields = ['type', 'params', 'error_handler', 'error_handler_params']; - $delta = false; - $throttling = false; - $prometheus = false; + unset($upd_item_indexes[$row['hostid']][$row['type']][$_i]); + } - foreach ($item['preprocessing'] as $preprocessing) { - $missing_keys = array_diff($required_fields, array_keys($preprocessing)); + if (!$upd_item_indexes[$row['hostid']][$row['type']]) { + unset($upd_item_indexes[$row['hostid']][$row['type']]); + } - if ($missing_keys) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Item pre-processing is missing parameters: %1$s', implode(', ', $missing_keys)) - ); + if (!$upd_item_indexes[$row['hostid']]) { + unset($upd_item_indexes[$row['hostid']]); } + } - if (is_array($preprocessing['type'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); + if (array_key_exists($row['hostid'], $ins_item_indexes) + && array_key_exists($row['type'], $ins_item_indexes[$row['hostid']])) { + foreach ($ins_item_indexes[$row['hostid']][$row['type']] as $_i => $i) { + $ins_items[$i]['interfaceid'] = $row['interfaceid']; + + unset($ins_item_indexes[$row['hostid']][$row['type']][$_i]); } - elseif ($preprocessing['type'] === '' || $preprocessing['type'] === null - || $preprocessing['type'] === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'type', _('cannot be empty')) - ); + + if (!$ins_item_indexes[$row['hostid']][$row['type']]) { + unset($ins_item_indexes[$row['hostid']][$row['type']]); } - if (!$type_validator->validate($preprocessing['type'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'type', - _s('unexpected value "%1$s"', $preprocessing['type']) - ) - ); + if (!$ins_item_indexes[$row['hostid']]) { + unset($ins_item_indexes[$row['hostid']]); } + } + } - $preprocessing['params'] = str_replace("\r\n", "\n", $preprocessing['params']); + $item = null; - switch ($preprocessing['type']) { - case ZBX_PREPROC_MULTIPLIER: - // Check if custom multiplier is a valid number. - $params = $preprocessing['params']; + if ($upd_item_indexes) { + $hostid = key($upd_item_indexes); + $interface_type = key($upd_item_indexes[$hostid]); + $i = reset($upd_item_indexes[$hostid][$interface_type]); - if (is_array($params)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($params === '' || $params === null || $params === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) - ); - } + $item = $upd_items[$i]; + } + elseif ($ins_item_indexes) { + $hostid = key($ins_item_indexes); + $interface_type = key($ins_item_indexes[$hostid]); + $i = reset($ins_item_indexes[$hostid][$interface_type]); - if (is_numeric($params)) { - break; - } + $item = $ins_items[$i]; + } - $types = ['usermacros' => true]; + if ($item === null) { + return; + } - if ($this instanceof CItemPrototype) { - $types['lldmacros'] = true; - } + $templates = DBfetchArray(DBselect( + 'SELECT h.host'. + ' FROM items i,hosts h'. + ' WHERE i.hostid=h.hostid'. + ' AND '.dbConditionId('i.itemid', [$item['templateid']]) + )); - if (!(new CMacrosResolverGeneral)->getMacroPositions($params, $types)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('a numeric value is expected') - )); - } - break; - - case ZBX_PREPROC_RTRIM: - case ZBX_PREPROC_LTRIM: - case ZBX_PREPROC_TRIM: - case ZBX_PREPROC_XPATH: - case ZBX_PREPROC_JSONPATH: - case ZBX_PREPROC_VALIDATE_REGEX: - case ZBX_PREPROC_VALIDATE_NOT_REGEX: - case ZBX_PREPROC_ERROR_FIELD_JSON: - case ZBX_PREPROC_ERROR_FIELD_XML: - case ZBX_PREPROC_SCRIPT: - // Check 'params' if not empty. - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null - || $preprocessing['params'] === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) - ); - } - break; - - case ZBX_PREPROC_REGSUB: - case ZBX_PREPROC_ERROR_FIELD_REGEX: - case ZBX_PREPROC_STR_REPLACE: - // Check if 'params' are not empty and if second parameter contains (after \n) is not empty. - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null - || $preprocessing['params'] === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) - ); - } + $hosts = DB::select('hosts', [ + 'output' => ['host'], + 'hostids' => $item['hostid'] + ]); - $params = explode("\n", $preprocessing['params']); + switch ($item['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + $error = _('Cannot inherit item with key "%1$s" of template "%2$s" to host "%3$s", because a host interface of type "%4$s" is required.'); + break; - if ($params[0] === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('first parameter is expected') - )); - } + case ZBX_FLAG_DISCOVERY_CREATED: + $error = _('Cannot inherit LLD rule with key "%1$s" of template "%2$s" to host "%3$s", because a host interface of type "%4$s" is required.'); + break; - if (($preprocessing['type'] == ZBX_PREPROC_REGSUB - || $preprocessing['type'] == ZBX_PREPROC_ERROR_FIELD_REGEX) - && (!array_key_exists(1, $params) || $params[1] === '')) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('second parameter is expected') - )); - } - break; + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + $error = _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to host "%3$s", because a host interface of type "%4$s" is required.'); + break; + } - case ZBX_PREPROC_VALIDATE_RANGE: - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif (trim($preprocessing['params']) === '' || $preprocessing['params'] === null - || $preprocessing['params'] === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) - ); - } + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $item['key_'], $templates[0]['host'], + $hosts[0]['host'], interfaceType2str($interface_type) + )); + } - $params = explode("\n", $preprocessing['params']); + /** + * Inherit dependent items in nesting order. + * + * @param array $dep_items_to_link[<master item index>][<dependent item index>] + * @param array $items_to_link + * @param array $hostids + */ + protected static function inheritDependentItems(array $dep_items_to_link, array $items_to_link, + array $hostids): void { + while ($dep_items_to_link) { + $items = []; - if ($params[0] !== '' && !is_numeric($params[0]) - && (new CUserMacroParser())->parse($params[0]) != CParser::PARSE_SUCCESS - && (!($this instanceof CItemPrototype) - || ((new CLLDMacroFunctionParser())->parse($params[0]) != CParser::PARSE_SUCCESS - && (new CLLDMacroParser())->parse($params[0]) != CParser::PARSE_SUCCESS))) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('a numeric value is expected') - )); - } + foreach ($dep_items_to_link as $i => $_items) { + if (array_key_exists($i, $items_to_link)) { + $items += $_items; + unset($dep_items_to_link[$i]); + } + } - if ($params[1] !== '' && !is_numeric($params[1]) - && (new CUserMacroParser())->parse($params[1]) != CParser::PARSE_SUCCESS - && (!($this instanceof CItemPrototype) - || ((new CLLDMacroFunctionParser())->parse($params[1]) != CParser::PARSE_SUCCESS - && (new CLLDMacroParser())->parse($params[1]) != CParser::PARSE_SUCCESS))) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('a numeric value is expected') - )); - } + static::inherit(array_values($items), [], $hostids, true); - if (is_numeric($params[0]) && is_numeric($params[1]) && $params[0] > $params[1]) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s( - 'Incorrect value for field "%1$s": %2$s.', - 'params', - _s('"%1$s" value must be less than or equal to "%2$s" value', _('min'), _('max')) - )); - } - break; - - case ZBX_PREPROC_BOOL2DEC: - case ZBX_PREPROC_OCT2DEC: - case ZBX_PREPROC_HEX2DEC: - case ZBX_PREPROC_THROTTLE_VALUE: - // Check if 'params' is empty, because it must be empty. - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null - && $preprocessing['params'] !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty')) - ); - } + $items_to_link = $items; + } + } - if ($preprocessing['type'] == ZBX_PREPROC_THROTTLE_VALUE) { - if ($throttling) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one throttling step is allowed.')); - } - else { - $throttling = true; - } - } - break; - - case ZBX_PREPROC_DELTA_VALUE: - case ZBX_PREPROC_DELTA_SPEED: - case ZBX_PREPROC_XML_TO_JSON: - // Check if 'params' is empty, because it must be empty. - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null - && $preprocessing['params'] !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty')) - ); - } + /** + * @param array $items + * @param array $db_items + * @param array|null $hostids + * @param bool $is_dep_items Inherit called for dependent items. + */ + abstract protected static function inherit(array $items, array $db_items = [], array $hostids = null, + bool $is_dep_items = false): void; - if ($preprocessing['type'] == ZBX_PREPROC_DELTA_VALUE - || $preprocessing['type'] == ZBX_PREPROC_DELTA_SPEED) { - // Check if one of the deltas (Delta per second or Delta value) already exists. - if ($delta) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one change step is allowed.')); - } - else { - $delta = true; - } - } - break; - - case ZBX_PREPROC_THROTTLE_TIMED_VALUE: - $api_input_rules = [ - 'type' => API_TIME_UNIT, - 'flags' => ($this instanceof CItem) - ? API_NOT_EMPTY | API_ALLOW_USER_MACRO - : API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, - 'in' => '1:'.ZBX_MAX_TIMESHIFT - ]; + /** + * Add default values for fields that became unnecessary as the result of the change of the type fields. + * + * @param array $items + * @param array $db_items + */ + protected static function addFieldDefaultsByType(array &$items, array $db_items): void { + $type_field_defaults = [ + // The fields used for multiple item types. + 'interfaceid' => 0, + 'authtype' => DB::getDefault('items', 'authtype'), + 'username' => DB::getDefault('items', 'username'), + 'password' => DB::getDefault('items', 'password'), + 'params' => DB::getDefault('items', 'params'), + 'timeout' => DB::getDefault('items', 'timeout'), + 'delay' => DB::getDefault('items', 'delay'), + 'trapper_hosts' => DB::getDefault('items', 'trapper_hosts'), + + // Dependent item type specific fields. + 'master_itemid' => 0, + + // HTTP Agent item type specific fields. + 'url' => DB::getDefault('items', 'url'), + 'query_fields' => DB::getDefault('items', 'query_fields'), + 'request_method' => DB::getDefault('items', 'request_method'), + 'post_type' => DB::getDefault('items', 'post_type'), + 'posts' => DB::getDefault('items', 'posts'), + 'headers' => DB::getDefault('items', 'headers'), + 'status_codes' => DB::getDefault('items', 'status_codes'), + 'follow_redirects' => DB::getDefault('items', 'follow_redirects'), + 'retrieve_mode' => DB::getDefault('items', 'retrieve_mode'), + 'output_format' => DB::getDefault('items', 'output_format'), + 'http_proxy' => DB::getDefault('items', 'http_proxy'), + 'verify_peer' => DB::getDefault('items', 'verify_peer'), + 'verify_host' => DB::getDefault('items', 'verify_host'), + 'ssl_cert_file' => DB::getDefault('items', 'ssl_cert_file'), + 'ssl_key_file' => DB::getDefault('items', 'ssl_key_file'), + 'ssl_key_password' => DB::getDefault('items', 'ssl_key_password'), + 'allow_traps' => DB::getDefault('items', 'allow_traps'), + + // IPMI item type specific fields. + 'ipmi_sensor' => DB::getDefault('items', 'ipmi_sensor'), + + // JMX item type specific fields. + 'jmx_endpoint' => DB::getDefault('items', 'jmx_endpoint'), + + // Script item type specific fields. + 'parameters' => [], + + // SNMP item type specific fields. + 'snmp_oid' => DB::getDefault('items', 'snmp_oid'), + + // SSH item type specific fields. + 'publickey' => DB::getDefault('items', 'publickey'), + 'privatekey' => DB::getDefault('items', 'privatekey') + ]; - if (!CApiInputValidator::validate($api_input_rules, $preprocessing['params'], 'params', - $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); - } + $value_type_field_defaults = [ + 'units' => DB::getDefault('items', 'units'), + 'trends' => DB::getDefault('items', 'trends'), + 'valuemapid' => 0, + 'logtimefmt' => DB::getDefault('items', 'logtimefmt'), + 'inventory_link' => DB::getDefault('items', 'inventory_link') + ]; - if ($throttling) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one throttling step is allowed.')); - } - else { - $throttling = true; - } - break; + foreach ($items as &$item) { + if (!array_key_exists('type', $db_items[$item['itemid']])) { + continue; + } - case ZBX_PREPROC_PROMETHEUS_PATTERN: - case ZBX_PREPROC_PROMETHEUS_TO_JSON: - if ($prometheus) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one Prometheus step is allowed.')); - } + $db_item = $db_items[$item['itemid']]; - $prometheus = true; + if ($item['type'] != $db_item['type']) { + $type_field_names = CItemTypeFactory::getObject($item['type'])::FIELD_NAMES; + $db_type_field_names = CItemTypeFactory::getObject($db_item['type'])::FIELD_NAMES; - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } + $field_names = array_flip(array_diff($db_type_field_names, $type_field_names)); - if ($preprocessing['type'] == ZBX_PREPROC_PROMETHEUS_PATTERN) { - if ($preprocessing['params'] === '' || $preprocessing['params'] === null - || $preprocessing['params'] === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) - ); - } + if ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE && strncmp($item['key_'], 'mqtt.get', 8) == 0) { + $field_names += array_flip(['delay']); + } - $params = explode("\n", $preprocessing['params']); + if (array_intersect([$item['type'], $db_item['type']], [ITEM_TYPE_SSH, ITEM_TYPE_HTTPAGENT])) { + $field_names += array_flip(['authtype']); + } - if ($params[0] === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('first parameter is expected') - )); - } - elseif (!array_key_exists(1, $params)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('second parameter is expected') - )); - } - elseif (!array_key_exists(2, $params) - && ($params[1] === ZBX_PREPROC_PROMETHEUS_LABEL - || $params[1] === ZBX_PREPROC_PROMETHEUS_FUNCTION)) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('third parameter is expected') - )); - } + if ($item['host_status'] == HOST_STATUS_TEMPLATE && array_key_exists('interfaceid', $field_names)) { + unset($field_names['interfaceid']); + } - if ($prometheus_pattern_parser->parse($params[0]) != CParser::PARSE_SUCCESS) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('invalid Prometheus pattern') - )); - } + $item += array_intersect_key($type_field_defaults, $field_names); + } + elseif ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE) { + if (array_key_exists('key_', $item) && $item['key_'] !== $db_item['key_'] + && strncmp($item['key_'], 'mqtt.get', 8) == 0) { + $item += array_intersect_key($type_field_defaults, array_flip(['delay'])); + } + } + elseif ($item['type'] == ITEM_TYPE_SSH) { + if (array_key_exists('authtype', $item) && $item['authtype'] !== $db_item['authtype'] + && $item['authtype'] == ITEM_AUTHTYPE_PASSWORD) { + $item += array_intersect_key($type_field_defaults, array_flip(['publickey', 'privatekey'])); + } + } + elseif ($item['type'] == ITEM_TYPE_HTTPAGENT) { + if (array_key_exists('request_method', $item) && $item['request_method'] != $db_item['request_method'] + && $item['request_method'] == HTTPCHECK_REQUEST_HEAD) { + $item += ['retrieve_mode' => HTTPTEST_STEP_RETRIEVE_MODE_HEADERS]; + } - if (!in_array($params[1], [ZBX_PREPROC_PROMETHEUS_VALUE, ZBX_PREPROC_PROMETHEUS_LABEL, - ZBX_PREPROC_PROMETHEUS_FUNCTION])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('invalid aggregation method') - )); - } + if (array_key_exists('authtype', $item) && $item['authtype'] != $db_item['authtype'] + && $item['authtype'] == HTTPTEST_AUTH_NONE) { + $item += array_intersect_key($type_field_defaults, array_flip(['username', 'password'])); + } - switch ($params[1]) { - case ZBX_PREPROC_PROMETHEUS_VALUE: - if (array_key_exists(2, $params) && $params[2] !== '') { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', - _('invalid Prometheus output') - ) - ); - } - break; - - case ZBX_PREPROC_PROMETHEUS_LABEL: - if ($prometheus_output_parser->parse($params[2]) != CParser::PARSE_SUCCESS) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', - _('invalid Prometheus output') - ) - ); - } - break; - - case ZBX_PREPROC_PROMETHEUS_FUNCTION: - if (!in_array($params[2], [ZBX_PREPROC_PROMETHEUS_SUM, ZBX_PREPROC_PROMETHEUS_MIN, - ZBX_PREPROC_PROMETHEUS_MAX, ZBX_PREPROC_PROMETHEUS_AVG, - ZBX_PREPROC_PROMETHEUS_COUNT])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', - _('unsupported Prometheus function') - ) - ); - } - break; - } - } - // Prometheus to JSON can be empty and has only one parameter. - elseif ($preprocessing['params'] !== '') { - if ($prometheus_pattern_parser->parse($preprocessing['params']) != CParser::PARSE_SUCCESS) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('invalid Prometheus pattern') - )); - } - } - break; + if (array_key_exists('allow_traps', $item) && $item['allow_traps'] != $db_item['allow_traps'] + && $item['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF) { + $item += array_intersect_key($type_field_defaults, array_flip(['trapper_hosts'])); + } + } - case ZBX_PREPROC_CSV_TO_JSON: - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null - || $preprocessing['params'] === false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty')) - ); - } + if (array_key_exists('value_type', $item) && $item['value_type'] != $db_item['value_type']) { + $type_field_names = static::VALUE_TYPE_FIELD_NAMES[$item['value_type']]; + $db_type_field_names = static::VALUE_TYPE_FIELD_NAMES[$db_item['value_type']]; - $params = explode("\n", $preprocessing['params']); + $field_names = array_flip(array_diff($db_type_field_names, $type_field_names)); - $params_cnt = count($params); - if ($params_cnt > 3) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($params_cnt == 1) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('second parameter is expected') - )); - } - elseif ($params_cnt == 2) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('third parameter is expected') - )); - } - else { - // Correct amount of parameters, but check if they are valid. + if (array_key_exists('trends', $field_names)) { + $item += ['trends' => 0]; + } - if (mb_strlen($params[0]) > 1) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('value of first parameter is too long') - )); - } + $item += array_intersect_key($value_type_field_defaults, $field_names); + } + } + unset($item); + } - if (mb_strlen($params[1]) > 1) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'params', _('value of second parameter is too long') - )); - } + /** + * @param array $items + * @param array|null $db_items + * @param array|null $upd_itemids + */ + protected static function updateParameters(array &$items, array $db_items = null, + array &$upd_itemids = null): void { + $ins_item_parameters = []; + $upd_item_parameters = []; + $del_item_parameterids = []; - $with_header_row_validator = new CLimitedSetValidator([ - 'values' => [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER] - ]); - - if (!$with_header_row_validator->validate($params[2])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', - _s('value of third parameter must be one of %1$s', - implode(', ', [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER]) - ) - ) - ); - } - } - break; + foreach ($items as $i => &$item) { + $update = false; - case ZBX_PREPROC_VALIDATE_NOT_SUPPORTED: - // Check if 'params' is empty, because it must be empty. - if (is_array($preprocessing['params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null - && $preprocessing['params'] !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty')) - ); - } + if ($db_items === null) { + if ($item['type'] == ITEM_TYPE_SCRIPT && array_key_exists('parameters', $item) && $item['parameters']) { + $update = true; + } + } + else { + if (!array_key_exists('type', $db_items[$item['itemid']])) { + continue; + } - $preprocessing_types = array_column($item['preprocessing'], 'type'); + if ($item['type'] == ITEM_TYPE_SCRIPT) { + if (array_key_exists('parameters', $item)) { + $update = true; + } + } + elseif ($db_items[$item['itemid']]['type'] == ITEM_TYPE_SCRIPT + && $db_items[$item['itemid']]['parameters']) { + $update = true; + } + } - if (count(array_keys($preprocessing_types, ZBX_PREPROC_VALIDATE_NOT_SUPPORTED)) > 1) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _('Only one not supported value check is allowed.') - ); - } - break; - } - - switch ($preprocessing['type']) { - case ZBX_PREPROC_RTRIM: - case ZBX_PREPROC_LTRIM: - case ZBX_PREPROC_TRIM: - case ZBX_PREPROC_THROTTLE_VALUE: - case ZBX_PREPROC_THROTTLE_TIMED_VALUE: - case ZBX_PREPROC_SCRIPT: - case ZBX_PREPROC_STR_REPLACE: - if (is_array($preprocessing['error_handler'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['error_handler'] != ZBX_PREPROC_FAIL_DEFAULT) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler', - _s('unexpected value "%1$s"', $preprocessing['error_handler']) - ) - ); - } + if (!$update) { + continue; + } - if (is_array($preprocessing['error_handler_params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['error_handler_params'] !== '' - && $preprocessing['error_handler_params'] !== null - && $preprocessing['error_handler_params'] !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', - _('should be empty') - ) - ); - } - break; + $changed = false; + $db_item_parameters = ($db_items !== null) + ? array_column($db_items[$item['itemid']]['parameters'], null, 'name') + : []; - case ZBX_PREPROC_VALIDATE_NOT_SUPPORTED: - if (is_array($preprocessing['error_handler'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif (!$unsupported_error_handler_validator->validate($preprocessing['error_handler'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler', - _s('unexpected value "%1$s"', $preprocessing['error_handler']) - ) - ); - } + foreach ($item['parameters'] as &$item_parameter) { + if (array_key_exists($item_parameter['name'], $db_item_parameters)) { + $db_item_parameter = $db_item_parameters[$item_parameter['name']]; + $item_parameter['item_parameterid'] = $db_item_parameter['item_parameterid']; + unset($db_item_parameters[$db_item_parameter['name']]); - if (is_array($preprocessing['error_handler_params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DISCARD_VALUE - && $preprocessing['error_handler_params'] !== '' - && $preprocessing['error_handler_params'] !== null - && $preprocessing['error_handler_params'] !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', - _('should be empty') - ) - ); - } - elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_SET_ERROR - && ($preprocessing['error_handler_params'] === '' - || $preprocessing['error_handler_params'] === null - || $preprocessing['error_handler_params'] === false)) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', - _('cannot be empty') - ) - ); - } - break; + $upd_item_parameter = DB::getUpdatedValues('item_parameter', $item_parameter, $db_item_parameter); - default: - if (is_array($preprocessing['error_handler'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif (!$error_handler_validator->validate($preprocessing['error_handler'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler', - _s('unexpected value "%1$s"', $preprocessing['error_handler']) - ) - ); - } + if ($upd_item_parameter) { + $upd_item_parameters[] = [ + 'values' => $upd_item_parameter, + 'where' => ['item_parameterid' => $db_item_parameter['item_parameterid']] + ]; + $changed = true; + } + } + else { + $ins_item_parameters[] = ['itemid' => $item['itemid']] + $item_parameter; + $changed = true; + } + } + unset($item_parameter); - if (is_array($preprocessing['error_handler_params'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); - } - elseif (($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DEFAULT - || $preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DISCARD_VALUE) - && $preprocessing['error_handler_params'] !== '' - && $preprocessing['error_handler_params'] !== null - && $preprocessing['error_handler_params'] !== false) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', - _('should be empty') - ) - ); - } - elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_SET_ERROR - && ($preprocessing['error_handler_params'] === '' - || $preprocessing['error_handler_params'] === null - || $preprocessing['error_handler_params'] === false)) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params', - _('cannot be empty') - ) - ); - } + if ($db_item_parameters) { + $del_item_parameterids = + array_merge($del_item_parameterids, array_column($db_item_parameters, 'item_parameterid')); + $changed = true; + } + + if ($db_items !== null) { + if ($changed) { + $upd_itemids[$i] = $item['itemid']; + } + else { + unset($item['parameters']); } } } - } + unset($item); - /** - * Method validates preprocessing steps independently from other item properties. - * - * @param array $preprocessing_steps An array of item pre-processing step details. - * See self::validateItemPreprocessing for details. - * - * @return bool|string - */ - public function validateItemPreprocessingSteps(array $preprocessing_steps) { - try { - $this->validateItemPreprocessing(['preprocessing' => $preprocessing_steps]); + if ($del_item_parameterids) { + DB::delete('item_parameter', ['item_parameterid' => $del_item_parameterids]); + } - return true; + if ($upd_item_parameters) { + DB::update('item_parameter', $upd_item_parameters); } - catch (APIException $error) { - return $error->getMessage(); + + if ($ins_item_parameters) { + $item_parameterids = DB::insert('item_parameter', $ins_item_parameters); } - } - /** - * Insert item pre-processing data into DB. - * - * @param array $items An array of items. - * @param string $items[]['itemid'] - * @param array $items[]['preprocessing'] An array of item pre-processing data. - */ - protected function createItemPreprocessing(array $items) { - $item_preproc = []; + foreach ($items as &$item) { + if (!array_key_exists('parameters', $item)) { + continue; + } - foreach ($items as $item) { - if (array_key_exists('preprocessing', $item)) { - $step = 1; - - foreach ($item['preprocessing'] as $preprocessing) { - $item_preproc[] = [ - 'itemid' => $item['itemid'], - 'step' => ($preprocessing['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) ? 0 : $step++, - 'type' => $preprocessing['type'], - 'params' => $preprocessing['params'], - 'error_handler' => $preprocessing['error_handler'], - 'error_handler_params' => $preprocessing['error_handler_params'] - ]; + foreach ($item['parameters'] as &$item_parameter) { + if (!array_key_exists('item_parameterid', $item_parameter)) { + $item_parameter['item_parameterid'] = array_shift($item_parameterids); } } + unset($item_parameter); } - - if ($item_preproc) { - DB::insertBatch('item_preproc', $item_preproc); - } + unset($item); } /** - * Update item pre-processing data in DB. Delete old records and create new ones. - * - * @param array $items - * @param string $items[]['itemid'] - * @param array $items[]['preprocessing'] - * @param int $items[]['preprocessing'][]['type'] - * @param string $items[]['preprocessing'][]['params'] - * @param int $items[]['preprocessing'][]['error_handler'] - * @param string $items[]['preprocessing'][]['error_handler_params'] + * @param array $items + * @param array|null $db_items + * @param array|null $upd_itemids */ - protected function updateItemPreprocessing(array $items) { - $item_preprocs = []; + protected static function updatePreprocessing(array &$items, array $db_items = null, + array &$upd_itemids = null): void { + $ins_item_preprocs = []; + $upd_item_preprocs = []; + $del_item_preprocids = []; - foreach ($items as $item) { - if (array_key_exists('preprocessing', $item)) { - $item_preprocs[$item['itemid']] = []; - $step = 1; - - foreach ($item['preprocessing'] as $item_preproc) { - $curr_step = ($item_preproc['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) ? 0 : $step++; - $item_preprocs[$item['itemid']][$curr_step] = [ - 'type' => $item_preproc['type'], - 'params' => $item_preproc['params'], - 'error_handler' => $item_preproc['error_handler'], - 'error_handler_params' => $item_preproc['error_handler_params'] - ]; - } + foreach ($items as $i => &$item) { + if (!array_key_exists('preprocessing', $item)) { + continue; } - } - if (!$item_preprocs) { - return; - } + $changed = false; + $db_item_preprocs = ($db_items !== null) + ? array_column($db_items[$item['itemid']]['preprocessing'], null, 'step') + : []; - $ins_item_preprocs = []; - $upd_item_preprocs = []; - $del_item_preprocids = []; + $step = 1; - $options = [ - 'output' => ['item_preprocid', 'itemid', 'step', 'type', 'params', 'error_handler', 'error_handler_params'], - 'filter' => ['itemid' => array_keys($item_preprocs)] - ]; - $db_item_preprocs = DBselect(DB::makeSql('item_preproc', $options)); + foreach ($item['preprocessing'] as &$item_preproc) { + $item_preproc['step'] = ($item_preproc['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) ? 0 : $step++; - while ($db_item_preproc = DBfetch($db_item_preprocs)) { - if (array_key_exists($db_item_preproc['step'], $item_preprocs[$db_item_preproc['itemid']])) { - $item_preproc = $item_preprocs[$db_item_preproc['itemid']][$db_item_preproc['step']]; - $upd_item_preproc = []; + if (array_key_exists($item_preproc['step'], $db_item_preprocs)) { + $db_item_preproc = $db_item_preprocs[$item_preproc['step']]; + $item_preproc['item_preprocid'] = $db_item_preproc['item_preprocid']; + unset($db_item_preprocs[$db_item_preproc['step']]); - if ($item_preproc['type'] != $db_item_preproc['type']) { - $upd_item_preproc['type'] = $item_preproc['type']; - } - if ($item_preproc['params'] !== $db_item_preproc['params']) { - $upd_item_preproc['params'] = $item_preproc['params']; - } - if ($item_preproc['error_handler'] != $db_item_preproc['error_handler']) { - $upd_item_preproc['error_handler'] = $item_preproc['error_handler']; - } - if ($item_preproc['error_handler_params'] !== $db_item_preproc['error_handler_params']) { - $upd_item_preproc['error_handler_params'] = $item_preproc['error_handler_params']; - } + $upd_item_preproc = DB::getUpdatedValues('item_preproc', $item_preproc, $db_item_preproc); - if ($upd_item_preproc) { - $upd_item_preprocs[] = [ - 'values' => $upd_item_preproc, - 'where' => ['item_preprocid' => $db_item_preproc['item_preprocid']] - ]; + if ($upd_item_preproc) { + $upd_item_preprocs[] = [ + 'values' => $upd_item_preproc, + 'where' => ['item_preprocid' => $db_item_preproc['item_preprocid']] + ]; + $changed = true; + } + } + else { + $ins_item_preprocs[] = ['itemid' => $item['itemid']] + $item_preproc; + $changed = true; } - unset($item_preprocs[$db_item_preproc['itemid']][$db_item_preproc['step']]); } - else { - $del_item_preprocids[] = $db_item_preproc['item_preprocid']; + unset($item_preproc); + + if ($db_item_preprocs) { + $del_item_preprocids = + array_merge($del_item_preprocids, array_column($db_item_preprocs, 'item_preprocid')); + $changed = true; } - } - foreach ($item_preprocs as $itemid => $preprocs) { - foreach ($preprocs as $step => $preproc) { - $ins_item_preprocs[] = [ - 'itemid' => $itemid, - 'step' => $step - ] + $preproc; + if ($db_items !== null) { + if ($changed) { + $upd_itemids[$i] = $item['itemid']; + } + else { + unset($item['preprocessing']); + } } } + unset($item); if ($del_item_preprocids) { DB::delete('item_preproc', ['item_preprocid' => $del_item_preprocids]); @@ -1947,167 +1396,277 @@ abstract class CItemGeneral extends CApiService { } if ($ins_item_preprocs) { - DB::insertBatch('item_preproc', $ins_item_preprocs); + $item_preprocids = DB::insert('item_preproc', $ins_item_preprocs); } + + foreach ($items as &$item) { + if (!array_key_exists('preprocessing', $item)) { + continue; + } + + foreach ($item['preprocessing'] as &$item_preproc) { + if (!array_key_exists('item_preprocid', $item_preproc)) { + $item_preproc['item_preprocid'] = array_shift($item_preprocids); + } + } + unset($item_preproc); + } + unset($item); } /** - * Create item parameters. - * - * @param array $items Array of items. - * @param array $items[]['parameters'] Item parameters. - * @param array $items[]['parameters'][]['name'] Parameter name. - * @param array $items[]['parameters'][]['value'] Parameter value. - * @param array $itemids Array of item IDs that were created before. + * @param array $items + * @param array|null $db_items + * @param array|null $upd_itemids */ - protected function createItemParameters(array $items, array $itemids): void { - $item_parameters = []; - - foreach ($items as $key => $item) { - $items[$key]['itemid'] = $itemids[$key]; + protected static function updateTags(array &$items, array $db_items = null, array &$upd_itemids = null): void { + $ins_tags = []; + $del_itemtagids = []; - if (!array_key_exists('parameters', $item) || !$item['parameters']) { + foreach ($items as $i => &$item) { + if (!array_key_exists('tags', $item)) { continue; } - foreach ($item['parameters'] as $parameter) { - $item_parameters[] = [ - 'itemid' => $items[$key]['itemid'], - 'name' => $parameter['name'], - 'value' => $parameter['value'] - ]; + $changed = false; + $db_tags = ($db_items !== null) ? $db_items[$item['itemid']]['tags'] : []; + + foreach ($item['tags'] as &$tag) { + $db_itemtagid = key(array_filter($db_tags, static function (array $db_tag) use ($tag): bool { + return $tag['tag'] === $db_tag['tag'] + && (!array_key_exists('value', $tag) || $tag['value'] === $db_tag['value']); + })); + + if ($db_itemtagid !== null) { + $tag['itemtagid'] = $db_itemtagid; + unset($db_tags[$db_itemtagid]); + } + else { + $ins_tags[] = ['itemid' => $item['itemid']] + $tag; + $changed = true; + } + } + unset($tag); + + if ($db_tags) { + $del_itemtagids = array_merge($del_itemtagids, array_keys($db_tags)); + $changed = true; } + + if ($db_items !== null) { + if ($changed) { + $upd_itemids[$i] = $item['itemid']; + } + else { + unset($item['tags']); + } + } + } + unset($item); + + if ($del_itemtagids) { + DB::delete('item_tag', ['itemtagid' => $del_itemtagids]); } - if ($item_parameters) { - DB::insertBatch('item_parameter', $item_parameters); + if ($ins_tags) { + $itemtagids = DB::insert('item_tag', $ins_tags); } + + foreach ($items as &$item) { + if (!array_key_exists('tags', $item)) { + continue; + } + + foreach ($item['tags'] as &$tag) { + if (!array_key_exists('itemtagid', $tag)) { + $tag['itemtagid'] = array_shift($itemtagids); + } + } + unset($tag); + } + unset($item); } /** - * Update item parameters. - * - * @param array $items Array of items. - * @param int|string $items[]['itemid'] Item ID. - * @param int|string $items[]['type'] Item type. - * @param array $items[]['parameters'] Item parameters. - * @param array $items[]['parameters'][]['name'] Parameter name. - * @param array $items[]['parameters'][]['value'] Parameter value. + * Check for unique item keys. + * + * @param array $items + * @param array|null $db_items + * + * @throws APIException if item keys are not unique. */ - protected function updateItemParameters(array $items): void { - $db_item_parameters_by_itemid = []; + protected static function checkDuplicates(array $items, array $db_items = null): void { + $host_keys = []; foreach ($items as $item) { - if ($item['type'] != ITEM_TYPE_SCRIPT || array_key_exists('parameters', $item)) { - $db_item_parameters_by_itemid[$item['itemid']] = []; + if ($db_items === null || $item['key_'] !== $db_items[$item['itemid']]['key_']) { + $host_keys[$item['hostid']][] = $item['key_']; } } - if (!$db_item_parameters_by_itemid) { + if (!$host_keys) { return; } - $options = [ - 'output' => ['item_parameterid', 'itemid', 'name', 'value'], - 'filter' => ['itemid' => array_keys($db_item_parameters_by_itemid)] - ]; - $result = DBselect(DB::makeSql('item_parameter', $options)); + $where = []; + foreach ($host_keys as $hostid => $keys) { + $where[] = '('.dbConditionId('i.hostid', [$hostid]).' AND '.dbConditionString('i.key_', $keys).')'; + } - while ($row = DBfetch($result)) { - $db_item_parameters_by_itemid[$row['itemid']][$row['name']] = [ - 'item_parameterid' => $row['item_parameterid'], - 'value' => $row['value'] - ]; + $duplicates = DBfetchArray(DBselect( + 'SELECT i.key_,i.flags,h.host,h.status'. + ' FROM items i,hosts h'. + ' WHERE i.hostid=h.hostid'. + ' AND ('.implode(' OR ', $where).')', + 1 + )); + + if ($duplicates) { + $target_is_template = ($duplicates[0]['status'] == HOST_STATUS_TEMPLATE); + + switch ($duplicates[0]['flags']) { + case ZBX_FLAG_DISCOVERY_NORMAL: + case ZBX_FLAG_DISCOVERY_CREATED: + $error = $target_is_template + ? _('An item with key "%1$s" already exists on the template "%2$s".') + : _('An item with key "%1$s" already exists on the host "%2$s".'); + break; + + case ZBX_FLAG_DISCOVERY_PROTOTYPE: + $error = $target_is_template + ? _('An item prototype with key "%1$s" already exists on the template "%2$s".') + : _('An item prototype with key "%1$s" already exists on the host "%2$s".'); + break; + + case ZBX_FLAG_DISCOVERY_RULE: + $error = $target_is_template + ? _('An LLD rule with key "%1$s" already exists on the template "%2$s".') + : _('An LLD rule with key "%1$s" already exists on the host "%2$s".'); + break; + } + + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $duplicates[0]['key_'], $duplicates[0]['host'])); } + } - $ins_item_parameters = []; - $upd_item_parameters = []; - $del_item_parameterids = []; + /** + * @param array $items + * @param array|null $db_items + * + * @throws APIException + */ + protected static function checkHostInterfaces(array $items, array $db_items = null): void { + foreach ($items as $i => &$item) { + $interface_type = itemTypeInterface($item['type']); + + if (!in_array($item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]) + || $interface_type === false) { + unset($items[$i]); + continue; + } - foreach ($db_item_parameters_by_itemid as $itemid => $db_item_parameters) { - $item = $items[$itemid]; - - if ($item['type'] == ITEM_TYPE_SCRIPT && array_key_exists('parameters', $item)) { - foreach ($item['parameters'] as $parameter) { - if (array_key_exists($parameter['name'], $db_item_parameters)) { - if ($db_item_parameters[$parameter['name']]['value'] !== $parameter['value']) { - $upd_item_parameters[] = [ - 'values' => ['value' => $parameter['value']], - 'where' => [ - 'item_parameterid' => $db_item_parameters[$parameter['name']]['item_parameterid'] - ] - ]; + $check = false; + + if ($db_items === null) { + if (array_key_exists('interfaceid', $item)) { + if ($item['interfaceid'] != 0) { + $check = true; + } + elseif ($interface_type != INTERFACE_TYPE_OPT) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/interfaceid', _('the host interface ID is expected') + )); + } + } + } + else { + $db_item = $db_items[$item['itemid']]; + + if ($item['type'] == $db_item['type']) { + if (array_key_exists('interfaceid', $item)) { + if ($item['interfaceid'] != 0) { + if (bccomp($item['interfaceid'], $db_item['interfaceid']) != 0) { + $check = true; + } + } + elseif ($interface_type != INTERFACE_TYPE_OPT) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/interfaceid', _('the host interface ID is expected') + )); + } + } + } + else { + $db_interface_type = itemTypeInterface($db_item['type']); + + if (array_key_exists('interfaceid', $item)) { + if ($item['interfaceid'] != 0) { + if (bccomp($item['interfaceid'], $db_item['interfaceid']) != 0 + || ($interface_type != INTERFACE_TYPE_OPT + && $interface_type != $db_interface_type)) { + $check = true; + } + } + elseif ($interface_type != INTERFACE_TYPE_OPT) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/interfaceid', _('the host interface ID is expected') + )); } - unset($db_item_parameters[$parameter['name']]); } else { - $ins_item_parameters[] = [ - 'itemid' => $itemid, - 'name' => $parameter['name'], - 'value' => $parameter['value'] - ]; + if ($db_item['interfaceid'] != 0) { + if ($interface_type != INTERFACE_TYPE_OPT && $interface_type != $db_interface_type) { + $item += ['interfaceid' => $db_item['interfaceid']]; + + $check = true; + } + } + elseif ($interface_type != INTERFACE_TYPE_OPT) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1), _s('the parameter "%1$s" is missing', 'interfaceid') + )); + } } } } - $del_item_parameterids = array_merge($del_item_parameterids, - array_column($db_item_parameters, 'item_parameterid') - ); - } - - if ($del_item_parameterids) { - DB::delete('item_parameter', ['item_parameterid' => $del_item_parameterids]); + if (!$check) { + unset($items[$i]); + } } + unset($item); - if ($upd_item_parameters) { - DB::update('item_parameter', $upd_item_parameters); + if (!$items) { + return; } - if ($ins_item_parameters) { - DB::insertBatch('item_parameter', $ins_item_parameters); - } - } + $db_interfaces = DB::select('interface', [ + 'output' => ['interfaceid', 'hostid', 'type'], + 'interfaceids' => array_unique(array_column($items, 'interfaceid')), + 'preservekeys' => true + ]); - /** - * Check if any item from list already exists. - * If items have item ids it will check for existing item with different itemid. - * - * @throw APIException - * - * @param array $items - */ - protected function checkExistingItems(array $items) { - $itemKeysByHostId = []; - $itemIds = []; - foreach ($items as $item) { - if (!isset($itemKeysByHostId[$item['hostid']])) { - $itemKeysByHostId[$item['hostid']] = []; + foreach ($items as $i => $item) { + if (!array_key_exists($item['interfaceid'], $db_interfaces)) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/interfaceid', _('the host interface ID is expected') + )); } - $itemKeysByHostId[$item['hostid']][] = $item['key_']; - if (isset($item['itemid'])) { - $itemIds[] = $item['itemid']; + if (bccomp($db_interfaces[$item['interfaceid']]['hostid'], $item['hostid']) != 0) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/interfaceid', _('cannot be the host interface ID from another host') + )); } - } - $sqlWhere = []; - foreach ($itemKeysByHostId as $hostId => $keys) { - $sqlWhere[] = '(i.hostid='.zbx_dbstr($hostId).' AND '.dbConditionString('i.key_', $keys).')'; - } + $interface_type = itemTypeInterface($item['type']); - if ($sqlWhere) { - $sql = 'SELECT i.key_,h.host'. - ' FROM items i,hosts h'. - ' WHERE i.hostid=h.hostid AND ('.implode(' OR ', $sqlWhere).')'; - - // if we update existing items we need to exclude them from result. - if ($itemIds) { - $sql .= ' AND '.dbConditionInt('i.itemid', $itemIds, true); - } - $dbItems = DBselect($sql, 1); - while ($dbItem = DBfetch($dbItems)) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Item with key "%1$s" already exists on "%2$s".', $dbItem['key_'], $dbItem['host'])); + if ($interface_type != INTERFACE_TYPE_OPT + && $db_interfaces[$item['interfaceid']]['type'] != $interface_type) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/interfaceid', + _s('the host interface ID of type "%1$s" is expected', interfaceType2str($interface_type)) + )); } } } @@ -2222,522 +1781,499 @@ abstract class CItemGeneral extends CApiService { } /** - * Validate items with type ITEM_TYPE_DEPENDENT for create or update operation. + * Check that dependent items of given items are valid. * - * @param array $items - * @param string $items[]['itemid'] (mandatory for updated items and item prototypes) - * @param string $items[]['hostid'] - * @param int $items[]['type'] - * @param string $items[]['master_itemid'] (mandatory for ITEM_TYPE_DEPENDENT) - * @param int $items[]['flags'] (mandatory for items) + * @param array $items + * @param array $db_items + * @param bool $inherited * - * @throws APIException for invalid data. + * @throws APIException */ - protected function validateDependentItems(array $items) { - $dep_items = []; - $upd_itemids = []; + protected static function checkDependentItems(array $items, array $db_items = [], bool $inherited = false): void { + $del_links = []; + + foreach ($items as $i => $item) { + $check = false; - foreach ($items as $item) { if ($item['type'] == ITEM_TYPE_DEPENDENT) { - if ($this instanceof CDiscoveryRule || $this instanceof CItemPrototype - || $item['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) { - $dep_items[] = $item; + if (!array_key_exists('itemid', $item)) { + if ($item['master_itemid'] != 0) { + $check = true; + } + else { + $error = $item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE + ? _('an item/item prototype ID is expected') + : _('an item ID is expected'); + + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/master_itemid', $error + )); + } } + else { + if (array_key_exists('master_itemid', $item)) { + if ($item['master_itemid'] != 0) { + if (bccomp($item['master_itemid'], $db_items[$item['itemid']]['master_itemid']) != 0) { + $check = true; + + if ($db_items[$item['itemid']]['master_itemid'] != 0) { + $del_links[$item['itemid']] = $db_items[$item['itemid']]['master_itemid']; + } + } + } + else { + $error = $item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE + ? _('an item/item prototype ID is expected') + : _('an item ID is expected'); - if (array_key_exists('itemid', $item)) { - $upd_itemids[] = $item['itemid']; + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/master_itemid', $error + )); + } + } } } - } - - if (!$dep_items) { - return; - } - - if ($this instanceof CItemPrototype && $upd_itemids) { - $db_links = DBselect( - 'SELECT id.itemid,id.parent_itemid AS ruleid'. - ' FROM item_discovery id'. - ' WHERE '.dbConditionId('id.itemid', $upd_itemids) - ); - - $links = []; - - while ($db_link = DBfetch($db_links)) { - $links[$db_link['itemid']] = $db_link['ruleid']; + elseif (array_key_exists('itemid', $item) && $db_items[$item['itemid']]['type'] == ITEM_TYPE_DEPENDENT) { + $del_links[$item['itemid']] = $db_items[$item['itemid']]['master_itemid']; } - foreach ($dep_items as &$dep_item) { - if (array_key_exists('itemid', $dep_item)) { - $dep_item['ruleid'] = $links[$dep_item['itemid']]; - } + if (!$check) { + unset($items[$i]); } - unset($dep_item); } - $master_itemids = []; - - foreach ($dep_items as $dep_item) { - $master_itemids[$dep_item['master_itemid']] = true; + if (!$items) { + return; } - $master_items = []; - - // Fill relations array by master items (item prototypes). Discovery rule should not be master item. - do { - if ($this instanceof CItemPrototype) { - $db_master_items = DBselect( - 'SELECT i.itemid,i.hostid,i.master_itemid,i.flags,id.parent_itemid AS ruleid'. - ' FROM items i'. - ' LEFT JOIN item_discovery id'. - ' ON i.itemid=id.itemid'. - ' WHERE '.dbConditionId('i.itemid', array_keys($master_itemids)). - ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_PROTOTYPE]) - ); - } - // CDiscoveryRule, CItem - else { - $db_master_items = DBselect( - 'SELECT i.itemid,i.hostid,i.master_itemid'. - ' FROM items i'. - ' WHERE '.dbConditionId('i.itemid', array_keys($master_itemids)). - ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL]) - ); - } + if (!$inherited) { + self::checkMasterItems($items, $db_items); + } - while ($db_master_item = DBfetch($db_master_items)) { - $master_items[$db_master_item['itemid']] = $db_master_item; + $dep_item_links = self::getDependentItemLinks($items, $del_links); - unset($master_itemids[$db_master_item['itemid']]); - } + if (!$inherited && $db_items) { + self::checkCircularDependencies($items, $dep_item_links); + } - if ($master_itemids) { - reset($master_itemids); + $root_itemids = []; - self::exception(ZBX_API_ERROR_PERMISSIONS, - _s('Incorrect value for field "%1$s": %2$s.', 'master_itemid', - _s('Item "%1$s" does not exist or you have no access to this item', key($master_itemids)) - ) - ); + foreach ($dep_item_links as $itemid => $master_itemid) { + if ($master_itemid == 0) { + $root_itemids[] = $itemid; } + } - $master_itemids = []; - - foreach ($master_items as $master_item) { - if ($master_item['master_itemid'] != 0 - && !array_key_exists($master_item['master_itemid'], $master_items)) { - $master_itemids[$master_item['master_itemid']] = true; - } - } - } while ($master_itemids); + $master_item_links = self::getMasterItemLinks($items, $root_itemids, $del_links); - foreach ($dep_items as $dep_item) { - $master_item = $master_items[$dep_item['master_itemid']]; + foreach ($root_itemids as $root_itemid) { + if (self::maxDependencyLevelExceeded($master_item_links, $root_itemid, $links_path)) { + [$flags, $key, $master_flags, $master_key, $is_template, $host] = + self::getProblemCausedItemData($links_path, $items); - if ($dep_item['hostid'] != $master_item['hostid']) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('"hostid" of dependent item and master item should match') - )); - } + $error = self::getDependentItemError($flags, $master_flags, $is_template); - if ($this instanceof CItemPrototype && $master_item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE - && $dep_item['ruleid'] != $master_item['ruleid']) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('"ruleid" of dependent item and master item should match') + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $key, $master_key, $host, + _('allowed count of dependency levels would be exceeded') )); } - if (array_key_exists('itemid', $dep_item)) { - $master_itemid = $dep_item['master_itemid']; + if (self::maxDependentItemCountExceeded($master_item_links, $root_itemid, $links_path)) { + [$flags, $key, $master_flags, $master_key, $is_template, $host] = + self::getProblemCausedItemData($links_path, $items); - while ($master_itemid != 0) { - if ($master_itemid == $dep_item['itemid']) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('circular item dependency is not allowed') - )); - } + $error = self::getDependentItemError($flags, $master_flags, $is_template); - $master_itemid = $master_items[$master_itemid]['master_itemid']; - } + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $key, $master_key, $host, + _('allowed count of dependent items would be exceeded') + )); } } + } - // Fill relations array by dependent items (item prototypes). - $root_itemids = []; + /** + * Check that master item IDs of given dependent items are valid. + * + * @param array $items + * @param array $db_items + * + * @throws APIException + */ + private static function checkMasterItems(array $items, array $db_items): void { + $master_itemids = array_unique(array_column($items, 'master_itemid')); + $flags = $items[key($items)]['flags']; - foreach ($master_items as $master_item) { - if ($master_item['master_itemid'] == 0) { - $root_itemids[] = $master_item['itemid']; - } + if ($flags == ZBX_FLAG_DISCOVERY_PROTOTYPE) { + $db_master_items = DBfetchArrayAssoc(DBselect( + 'SELECT i.itemid,i.hostid,i.master_itemid,i.flags,id.parent_itemid AS ruleid'. + ' FROM items i'. + ' LEFT JOIN item_discovery id ON i.itemid=id.itemid'. + ' WHERE '.dbConditionId('i.itemid', $master_itemids). + ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_PROTOTYPE]) + ), 'itemid'); } - - $dependent_items = []; - - foreach ($dep_items as $dep_item) { - if (array_key_exists('itemid', $dep_item)) { - $dependent_items[$dep_item['master_itemid']][] = $dep_item['itemid']; - } + else { + $db_master_items = DB::select('items', [ + 'output' => ['itemid', 'hostid', 'master_itemid'], + 'itemids' => $master_itemids, + 'filter' => [ + 'flags' => ZBX_FLAG_DISCOVERY_NORMAL + ], + 'preservekeys' => true + ]); } - $master_itemids = $root_itemids; + foreach ($items as $i => $item) { + if (!array_key_exists($item['master_itemid'], $db_master_items)) { + $error = $flags == ZBX_FLAG_DISCOVERY_PROTOTYPE + ? _('an item/item prototype ID is expected') + : _('an item ID is expected'); - do { - $sql = 'SELECT i.master_itemid,i.itemid'. - ' FROM items i'. - ' WHERE '.dbConditionId('i.master_itemid', $master_itemids); - if ($upd_itemids) { - $sql .= ' AND '.dbConditionId('i.itemid', $upd_itemids, true); // Exclude updated items. + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/master_itemid', $error + )); } - $db_items = DBselect($sql); + $db_master_item = $db_master_items[$item['master_itemid']]; - while ($db_item = DBfetch($db_items)) { - $dependent_items[$db_item['master_itemid']][] = $db_item['itemid']; + if (bccomp($db_master_item['hostid'], $item['hostid']) != 0) { + $error = $flags == ZBX_FLAG_DISCOVERY_PROTOTYPE + ? _('cannot be an item/item prototype ID from another host or template') + : _('cannot be an item ID from another host or template'); + + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/master_itemid', $error + )); } - $_master_itemids = $master_itemids; - $master_itemids = []; + if ($flags == ZBX_FLAG_DISCOVERY_PROTOTYPE && $db_master_item['ruleid'] != 0) { + $item_ruleid = array_key_exists('itemid', $item) + ? $db_items[$item['itemid']]['ruleid'] + : $item['ruleid']; - foreach ($_master_itemids as $master_itemid) { - if (array_key_exists($master_itemid, $dependent_items)) { - $master_itemids = array_merge($master_itemids, $dependent_items[$master_itemid]); + if (bccomp($db_master_item['ruleid'], $item_ruleid) != 0) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/master_itemid', _('cannot be an item prototype ID from another LLD rule') + )); } } - } while ($master_itemids); - - foreach ($dep_items as $dep_item) { - if (!array_key_exists('itemid', $dep_item)) { - $dependent_items[$dep_item['master_itemid']][] = false; - } - } - - foreach ($root_itemids as $root_itemid) { - self::checkDependencyDepth($dependent_items, $root_itemid); } } /** - * Validate depth and amount of elements in the tree of the dependent items. + * Get dependent item links starting from the given dependent items and till the highest dependency level. * - * @param array $dependent_items - * @param string $dependent_items[<master_itemid>][] List if the dependent item IDs ("false" for new items) - * by master_itemid. - * @param string $root_itemid ID of the item being checked. - * @param int $level Current dependency level. + * @param array $items + * @param array $del_links * - * @throws APIException for invalid data. + * @return array Array of the links where each key contain the ID of dependent item and value contain the + * appropriate ID of the master item. */ - private static function checkDependencyDepth(array $dependent_items, $root_itemid, $level = 0) { - $count = 0; - - if (array_key_exists($root_itemid, $dependent_items)) { - if (++$level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('maximum number of dependency levels reached') - )); - } + private static function getDependentItemLinks(array $items, array $del_links): array { + $links = array_column($items, 'master_itemid', 'itemid'); + $master_itemids = array_flip(array_column($items, 'master_itemid')); + + while ($master_itemids) { + $options = [ + 'output' => ['itemid', 'hostid', 'master_itemid'], + 'itemids' => array_keys($master_itemids) + ]; + $db_master_items = DBselect(DB::makeSql('items', $options)); - foreach ($dependent_items[$root_itemid] as $master_itemid) { - $count++; + $master_itemids = []; - if ($master_itemid !== false) { - $count += self::checkDependencyDepth($dependent_items, $master_itemid, $level); + while ($db_master_item = DBfetch($db_master_items)) { + if (array_key_exists($db_master_item['itemid'], $del_links) + && bccomp($db_master_item['master_itemid'], $del_links[$db_master_item['itemid']]) == 0) { + $links[$db_master_item['itemid']] = 0; + continue; } - } - if ($count > ZBX_DEPENDENT_ITEM_MAX_COUNT) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', - 'master_itemid', _('maximum dependent items count reached') - )); + $links[$db_master_item['itemid']] = $db_master_item['master_itemid']; + + if ($db_master_item['master_itemid'] != 0) { + $master_itemids[$db_master_item['master_itemid']] = true; + } } } - return $count; + return $links; } /** - * Converts headers field text to hash with header name as key. + * Check that the changed master item IDs of dependent items do not create a circular dependencies. * - * @param string $headers Headers string, one header per line, line delimiter "\r\n". + * @param array $items + * @param array $dep_item_links * - * @return array + * @throws APIException */ - protected function headersStringToArray($headers) { - $result = []; - - foreach (explode("\r\n", $headers) as $header) { - $header = explode(': ', $header, 2); + private static function checkCircularDependencies(array $items, array $dep_item_links): void { + foreach ($items as $i => $item) { + $master_itemid = $item['master_itemid']; + + while ($master_itemid != 0) { + if (bccomp($master_itemid, $item['itemid']) == 0) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/master_itemid', _('circular item dependency is not allowed') + )); + } - if (count($header) == 2) { - $result[$header[0]] = $header[1]; + $master_itemid = $dep_item_links[$master_itemid]; } } - - return $result; } /** - * Converts headers fields hash to string. + * Get master item links starting from the given master items and till the lowest level master items. * - * @param array $headers Array of headers where key is header name. + * @param array $items + * @param array $master_itemids + * @param array $del_links * - * @return string + * @return array Array of the links where each key contain the ID of master item and value contain the array of + * appropriate dependent item IDs. */ - protected function headersArrayToString(array $headers) { - $result = []; + private static function getMasterItemLinks(array $items, array $master_itemids, array $del_links): array { + $ins_links = []; + $upd_item_links = []; - foreach ($headers as $k => $v) { - $result[] = $k.': '.$v; + foreach ($items as $item) { + if (array_key_exists('itemid', $item)) { + $upd_item_links[$item['master_itemid']][] = $item['itemid']; + } + else { + $ins_links[$item['master_itemid']][] = 0; + } } - return implode("\r\n", $result); + $links = []; + + do { + $options = [ + 'output' => ['master_itemid', 'itemid'], + 'filter' => [ + 'master_itemid' => $master_itemids + ] + ]; + $db_items = DBselect(DB::makeSql('items', $options)); + + $_master_itemids = []; + + while ($db_item = DBfetch($db_items)) { + if (array_key_exists($db_item['itemid'], $del_links) + && bccomp($db_item['master_itemid'], $del_links[$db_item['itemid']]) == 0) { + continue; + } + + $links[$db_item['master_itemid']][] = $db_item['itemid']; + $_master_itemids[] = $db_item['itemid']; + } + + foreach ($master_itemids as $master_itemid) { + if (array_key_exists($master_itemid, $upd_item_links)) { + foreach ($upd_item_links[$master_itemid] as $itemid) { + $_master_itemids[] = $itemid; + $links[$master_itemid][] = $itemid; + } + } + } + + $master_itemids = $_master_itemids; + } while ($master_itemids); + + foreach ($ins_links as $master_itemid => $ins_items) { + $links[$master_itemid] = array_key_exists($master_itemid, $links) + ? array_merge($links[$master_itemid], $ins_items) + : $ins_items; + } + + return $links; } /** - * Validate item with type ITEM_TYPE_HTTPAGENT. + * Check whether maximum number of dependency levels is exceeded. * - * @param array $item Array of item fields. - * @param array $db_item Array of item database fields for update action or empty array for create action. + * @param array $master_item_links + * @param string $master_itemid + * @param array|null $links_path + * @param int $level * - * @throws APIException for invalid data. + * @return bool */ - protected function validateHTTPCheck(array $item, array $db_item) { - $rules = [ - 'timeout' => [ - 'type' => API_TIME_UNIT, 'flags' => ($this instanceof CItemPrototype) - ? API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO - : API_NOT_EMPTY | API_ALLOW_USER_MACRO, - 'in' => '1:'.SEC_PER_MIN - ], - 'url' => [ - 'type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, - 'length' => DB::getFieldLength('items', 'url') - ], - 'status_codes' => [ - 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'status_codes') - ], - 'follow_redirects' => [ - 'type' => API_INT32, - 'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON]) - ], - 'post_type' => [ - 'type' => API_INT32, - 'in' => implode(',', [ZBX_POSTTYPE_RAW, ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML]) - ], - 'http_proxy' => [ - 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'http_proxy') - ], - 'headers' => [ - 'type' => API_STRINGS_UTF8 - ], - 'retrieve_mode' => [ - 'type' => API_INT32, - 'in' => implode(',', [ - HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, - HTTPTEST_STEP_RETRIEVE_MODE_BOTH - ]) - ], - 'request_method' => [ - 'type' => API_INT32, - 'in' => implode(',', [ - HTTPCHECK_REQUEST_GET, HTTPCHECK_REQUEST_POST, HTTPCHECK_REQUEST_PUT, HTTPCHECK_REQUEST_HEAD - ]) - ], - 'output_format' => [ - 'type' => API_INT32, - 'in' => implode(',', [HTTPCHECK_STORE_RAW, HTTPCHECK_STORE_JSON]) - ], - 'allow_traps' => [ - 'type' => API_INT32, - 'in' => implode(',', [HTTPCHECK_ALLOW_TRAPS_OFF, HTTPCHECK_ALLOW_TRAPS_ON]) - ], - 'ssl_cert_file' => [ - 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_cert_file') - ], - 'ssl_key_file' => [ - 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_key_file') - ], - 'ssl_key_password' => [ - 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_key_password') - ], - 'verify_peer' => [ - 'type' => API_INT32, - 'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON]) - ], - 'verify_host' => [ - 'type' => API_INT32, - 'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON]) - ], - 'authtype' => [ - 'type' => API_INT32, - 'in' => implode(',', [ - HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS, - HTTPTEST_AUTH_DIGEST - ]) - ] - ]; - - $data = $item + $db_item; - - if (array_key_exists('authtype', $data) - && ($data['authtype'] == HTTPTEST_AUTH_BASIC || $data['authtype'] == HTTPTEST_AUTH_NTLM - || $data['authtype'] == HTTPTEST_AUTH_KERBEROS || $data['authtype'] == HTTPTEST_AUTH_DIGEST)) { - $rules += [ - 'username' => [ 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'username')], - 'password' => [ 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'password')] - ]; + private static function maxDependencyLevelExceeded(array $master_item_links, string $master_itemid, + array &$links_path = null, int $level = 0): bool { + if (!array_key_exists($master_itemid, $master_item_links)) { + return false; } - // Strict validation for 'retrieve_mode' only for create action. - if (array_key_exists('request_method', $data) && $data['request_method'] == HTTPCHECK_REQUEST_HEAD - && array_key_exists('retrieve_mode', $item)) { - $rules['retrieve_mode']['in'] = (string) HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; + if ($links_path === null) { + $links_path = []; } - if (array_key_exists('post_type', $data) - && ($data['post_type'] == ZBX_POSTTYPE_JSON || $data['post_type'] == ZBX_POSTTYPE_XML)) { - $rules['posts'] = [ - 'type' => API_STRING_UTF8, - 'length' => DB::getFieldLength('items', 'posts') - ]; + $links_path[] = $master_itemid; + $level++; + + if ($level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) { + return true; } - if (array_key_exists('templateid', $data) && $data['templateid']) { - $rules['interfaceid'] = [ - 'type' => API_ID, 'flags' => API_REQUIRED - ]; + foreach ($master_item_links[$master_itemid] as $itemid) { + $_links_path = $links_path; + + if (self::maxDependencyLevelExceeded($master_item_links, $itemid, $_links_path, $level)) { + $links_path = $_links_path; - if ($item['type'] == ITEM_TYPE_HTTPAGENT) { - unset($rules['interfaceid']['flags']); + return true; } } - if (array_key_exists('trapper_hosts', $item) && $item['trapper_hosts'] !== '' - && (!array_key_exists('allow_traps', $data) || $data['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF)) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'trapper_hosts', _('should be empty')) - ); - } + return false; + } - // Keep values only for fields with defined validation rules. - $data = array_intersect_key($data, $rules); - if (!CApiInputValidator::validate(['type' => API_OBJECT, 'fields' => $rules], $data, '', $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); + /** + * Check whether maximum count of dependent items is exceeded. + * + * @param array $master_item_links + * @param string $master_itemid + * @param array|null $links_path + * @param int $count + * + * @return bool + */ + private static function maxDependentItemCountExceeded(array $master_item_links, string $master_itemid, + array &$links_path = null, int &$count = 0): bool { + if (!array_key_exists($master_itemid, $master_item_links)) { + return false; } - if (array_key_exists('query_fields', $item)) { - if (!is_array($item['query_fields'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'query_fields', _('an array is expected')) - ); - } + if ($links_path === null) { + $links_path = []; + } - foreach ($item['query_fields'] as $v) { - if (!is_array($v) || count($v) > 1 || key($v) === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'query_fields', _('nonempty key and value pair expected')) - ); - } - } + $links_path[] = $master_itemid; + $count += count($master_item_links[$master_itemid]); - if (strlen(json_encode($item['query_fields'])) > DB::getFieldLength('items', 'query_fields')) { - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 'query_fields', - _('cannot convert to JSON, result value too long') - )); - } + if ($count > ZBX_DEPENDENT_ITEM_MAX_COUNT) { + return true; } - if (array_key_exists('headers', $item)) { - if (!is_array($item['headers'])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'headers', _('an array is expected')) - ); - } + foreach ($master_item_links[$master_itemid] as $itemid) { + $_links_path = $links_path; - foreach ($item['headers'] as $k => $v) { - if (trim($k) === '' || !is_string($v) || $v === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'headers', _('nonempty key and value pair expected')) - ); - } + if (self::maxDependentItemCountExceeded($master_item_links, $itemid, $_links_path, $count)) { + $links_path = $_links_path; + + return true; } } - if (array_key_exists('status_codes', $item) && $item['status_codes']) { - $ranges_parser = new CRangesParser([ - 'usermacros' => true, - 'lldmacros' => ($this instanceof CItemPrototype) - ]); + return false; + } - if ($ranges_parser->parse($item['status_codes']) != CParser::PARSE_SUCCESS) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value "%1$s" for "%2$s" field.', $item['status_codes'], 'status_codes') - ); + /** + * Get data for a dependent item that causes a problem, based on the given path where the problem was detected. + * + * @param array $links_path + * @param array $items + * + * @return array + */ + private static function getProblemCausedItemData(array $links_path, array $items): array { + foreach ($items as $item) { + if (in_array($item['master_itemid'], $links_path)) { + break; } } - if ((array_key_exists('post_type', $item) || array_key_exists('posts', $item)) - && ($data['post_type'] == ZBX_POSTTYPE_JSON || $data['post_type'] == ZBX_POSTTYPE_XML)) { - $posts = array_key_exists('posts', $data) ? $data['posts'] : ''; - libxml_use_internal_errors(true); - - if ($data['post_type'] == ZBX_POSTTYPE_XML - && simplexml_load_string($posts, null, LIBXML_IMPORT_FLAGS) === false) { - $errors = libxml_get_errors(); - libxml_clear_errors(); + $master_item_data = DBfetch(DBselect( + 'SELECT i.flags,i.key_,h.host'. + ' FROM items i,hosts h'. + ' WHERE i.hostid=h.hostid'. + ' AND '.dbConditionId('i.itemid', [$item['master_itemid']]) + )); - if (!$errors) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'posts', _('XML is expected')) - ); - } - else { - $error = reset($errors); - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 'posts', - _s('%1$s [Line: %2$s | Column: %3$s]', '('.$error->code.') '.trim($error->message), - $error->line, $error->column - ))); - } - } + $flags = $item['flags']; + $key = $item['key_']; + $master_flags = $master_item_data['flags']; + $master_key = $master_item_data['key_']; + $is_template = $item['host_status'] == HOST_STATUS_TEMPLATE; + $host = $master_item_data['host']; - if ($data['post_type'] == ZBX_POSTTYPE_JSON) { - if (trim($posts, " \r\n") === '') { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'posts', _('JSON is expected')) - ); - } + return [$flags, $key, $master_flags, $master_key, $is_template, $host]; + } - $types = [ - 'usermacros' => true, - 'macros_n' => [ - '{HOST.IP}', '{HOST.CONN}', '{HOST.DNS}', '{HOST.HOST}', '{HOST.NAME}', '{ITEM.ID}', - '{ITEM.KEY}' - ] - ]; + /** + * Get the error message about problem with dependent item according to given data. + * + * @param int $flags + * @param int $master_flags + * @param bool $is_template + * + * @return string + */ + private static function getDependentItemError(int $flags, int $master_flags, bool $is_template): string { + if ($flags == ZBX_FLAG_DISCOVERY_NORMAL) { + return $is_template + ? _('Cannot set dependency for item with key "%1$s" on the master item with key "%2$s" on the template "%3$s": %4$s.') + : _('Cannot set dependency for item with key "%1$s" on the master item with key "%2$s" on the host "%3$s": %4$s.'); + } + elseif ($flags == ZBX_FLAG_DISCOVERY_PROTOTYPE) { + if ($master_flags == ZBX_FLAG_DISCOVERY_NORMAL) { + return $is_template + ? _('Cannot set dependency for item prototype with key "%1$s" on the master item with key "%2$s" on the template "%3$s": %4$s.') + : _('Cannot set dependency for item prototype with key "%1$s" on the master item with key "%2$s" on the host "%3$s": %4$s.'); + } + else { + return $is_template + ? _('Cannot set dependency for item prototype with key "%1$s" on the master item prototype with key "%2$s" on the template "%3$s": %4$s.') + : _('Cannot set dependency for item prototype with key "%1$s" on the master item prototype with key "%2$s" on the host "%3$s": %4$s.'); + } + } + elseif ($flags == ZBX_FLAG_DISCOVERY_RULE) { + return $is_template + ? _('Cannot set dependency for LLD rule with key "%1$s" on the master item with key "%2$s" on the template "%3$s": %4$s.') + : _('Cannot set dependency for LLD rule with key "%1$s" on the master item with key "%2$s" on the host "%3$s": %4$s.'); + } + } - if ($this instanceof CItemPrototype) { - $types['lldmacros'] = true; - } + /** + * Converts headers field text to hash with header name as key. + * + * @param string $headers Headers string, one header per line, line delimiter "\r\n". + * + * @return array + */ + protected static function headersStringToArray(string $headers): array { + $result = []; - $matches = (new CMacrosResolverGeneral)->getMacroPositions($posts, $types); + foreach (explode("\r\n", $headers) as $header) { + $header = explode(': ', $header, 2); - $shift = 0; + if (count($header) == 2) { + $result[$header[0]] = $header[1]; + } + } - foreach ($matches as $pos => $substr) { - $posts = substr_replace($posts, '1', $pos + $shift, strlen($substr)); - $shift = $shift + 1 - strlen($substr); - } + return $result; + } - json_decode($posts); + /** + * Converts headers fields hash to string. + * + * @param array $headers Array of headers where key is header name. + * + * @return string + */ + protected static function headersArrayToString(array $headers): string { + $result = []; - if (json_last_error()) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Invalid parameter "%1$s": %2$s.', 'posts', _('JSON is expected')) - ); - } - } + foreach ($headers as $k => $v) { + $result[] = $k.': '.$v; } + + return implode("\r\n", $result); } /** @@ -2812,162 +2348,254 @@ abstract class CItemGeneral extends CApiService { } /** - * Update item tags. + * Check that valuemap belong to same host as item. * - * @param array $items - * @param string $items[]['itemid'] - * @param array $items[]['tags'] - * @param string $items[]['tags'][]['tag'] - * @param string $items[]['tags'][]['value'] + * @param array $items + * @param array|null $db_items + * + * @throws APIException */ - protected function updateItemTags(array $items): void { - $items = array_filter($items, function ($item) { - return array_key_exists('tags', $item); - }); - - // Select tags from database. - $db_tags = DBselect( - 'SELECT itemtagid, itemid, tag, value'. - ' FROM item_tag'. - ' WHERE '.dbConditionInt('itemid', array_keys($items)) - ); + protected static function checkValueMaps(array $items, array $db_items = null): void { + $item_indexes = []; - array_walk($items, function (&$item) { - $item['db_tags'] = []; - }); + foreach ($items as $i => $item) { + if (array_key_exists('valuemapid', $item) && $item['valuemapid'] != 0 + && ($db_items === null + || bccomp($item['valuemapid'], $db_items[$item['itemid']]['valuemapid']) != 0)) { + $item_indexes[$item['valuemapid']][] = $i; + } + } - while ($db_tag = DBfetch($db_tags)) { - $items[$db_tag['itemid']]['db_tags'][] = $db_tag; + if (!$item_indexes) { + return; } - // Find which tags must be added/deleted. - $new_tags = []; - $del_tagids = []; - foreach ($items as $item) { - CArrayHelper::sort($item['tags'], ['tag', 'value']); + $options = [ + 'output' => ['valuemapid', 'hostid'], + 'valuemapids' => array_keys($item_indexes) + ]; + $db_valuemaps = DBselect(DB::makeSql('valuemap', $options)); - foreach ($item['db_tags'] as $del_tag_key => $tag_delete) { - foreach ($item['tags'] as $new_tag_key => $tag_add) { - if ($tag_delete['tag'] === $tag_add['tag'] && $tag_delete['value'] === $tag_add['value']) { - unset($item['db_tags'][$del_tag_key], $item['tags'][$new_tag_key]); - continue 2; - } + while ($db_valuemap = DBfetch($db_valuemaps)) { + foreach ($item_indexes[$db_valuemap['valuemapid']] as $i) { + if (bccomp($db_valuemap['hostid'], $items[$i]['hostid']) != 0) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', + '/'.($i + 1).'/valuemapid', _('cannot be a value map ID from another host or template') + )); } } + } + } + + /** + * Add the internally used fields to the given $db_items. + * + * @param array $db_items + */ + protected static function addInternalFields(array &$db_items): void { + $result = DBselect( + 'SELECT i.itemid,i.hostid,i.templateid,i.flags,h.status AS host_status'. + ' FROM items i,hosts h'. + ' WHERE i.hostid=h.hostid'. + ' AND '.dbConditionId('i.itemid', array_keys($db_items)) + ); + + while ($row = DBfetch($result)) { + $db_items[$row['itemid']] += $row; + } + } + + /** + * Note: instances may override this to add e.g. tags. + * + * @param array $items + * @param array $db_items + */ + protected static function addAffectedObjects(array $items, array &$db_items): void { + self::addAffectedTags($items, $db_items); + self::addAffectedPreprocessing($items, $db_items); + self::addAffectedParameters($items, $db_items); + } - $del_tagids = array_merge($del_tagids, array_column($item['db_tags'], 'itemtagid')); + /** + * @param array $items + * @param array $db_items + */ + protected static function addAffectedTags(array $items, array &$db_items): void { + $itemids = []; - foreach ($item['tags'] as $tag_add) { - $tag_add['itemid'] = $item['itemid']; - $new_tags[] = $tag_add; + foreach ($items as $item) { + if (array_key_exists('tags', $item)) { + $itemids[] = $item['itemid']; + $db_items[$item['itemid']]['tags'] = []; } } - if ($del_tagids) { - DB::delete('item_tag', ['itemtagid' => $del_tagids]); + if (!$itemids) { + return; } - if ($new_tags) { - DB::insert('item_tag', $new_tags); + + $options = [ + 'output' => ['itemtagid', 'itemid', 'tag', 'value'], + 'filter' => ['itemid' => $itemids] + ]; + $db_item_tags = DBselect(DB::makeSql('item_tag', $options)); + + while ($db_item_tag = DBfetch($db_item_tags)) { + $db_items[$db_item_tag['itemid']]['tags'][$db_item_tag['itemtagid']] = + array_diff_key($db_item_tag, array_flip(['itemid'])); } } /** - * Record item tags into database. - * - * @param array $items - * @param array $items[]['tags'] - * @param string $items[]['tags'][]['tag'] - * @param string $items[]['tags'][]['value'] - * @param int $items[]['itemid'] + * @param array $items + * @param array $db_items */ - protected function createItemTags(array $items): void { - $new_tags = []; - foreach ($items as $key => $item) { - if (array_key_exists('tags', $item)) { - foreach ($item['tags'] as $tag) { - $tag['itemid'] = $item['itemid']; - $new_tags[] = $tag; - } + protected static function addAffectedPreprocessing(array $items, array &$db_items): void { + $itemids = []; + + foreach ($items as $item) { + if (array_key_exists('preprocessing', $item)) { + $itemids[] = $item['itemid']; + $db_items[$item['itemid']]['preprocessing'] = []; } } - if ($new_tags) { - DB::insert('item_tag', $new_tags); + if (!$itemids) { + return; + } + + $options = [ + 'output' => [ + 'item_preprocid', 'itemid', 'step', 'type', 'params', 'error_handler', 'error_handler_params' + ], + 'filter' => ['itemid' => $itemids] + ]; + $db_item_preprocs = DBselect(DB::makeSql('item_preproc', $options)); + + while ($db_item_preproc = DBfetch($db_item_preprocs)) { + $db_items[$db_item_preproc['itemid']]['preprocessing'][$db_item_preproc['item_preprocid']] = + array_diff_key($db_item_preproc, array_flip(['itemid'])); } } /** - * Check that valuemap belong to same host as item. - * * @param array $items + * @param array $db_items */ - protected function validateValueMaps(array $items): void { - $valuemapids_by_hostid = []; + protected static function addAffectedParameters(array $items, array &$db_items): void { + $itemids = []; foreach ($items as $item) { - if (array_key_exists('valuemapid', $item) && $item['valuemapid'] != 0) { - $valuemapids_by_hostid[$item['hostid']][$item['valuemapid']] = true; + $db_type = $db_items[$item['itemid']]['type']; + + if ((array_key_exists('parameters', $item) && $item['type'] == ITEM_TYPE_SCRIPT) + || ($item['type'] != $db_type && $db_type == ITEM_TYPE_SCRIPT)) { + $itemids[] = $item['itemid']; + $db_items[$item['itemid']]['parameters'] = []; + } + elseif (array_key_exists('parameters', $item)) { + $db_items[$item['itemid']]['parameters'] = []; } } - $sql_where = []; - foreach ($valuemapids_by_hostid as $hostid => $valuemapids) { - $sql_where[] = '(vm.hostid='.zbx_dbstr($hostid).' AND '. - dbConditionId('vm.valuemapid', array_keys($valuemapids)).')'; + if (!$itemids) { + return; } - if ($sql_where) { - $result = DBselect( - 'SELECT vm.valuemapid,vm.hostid'. - ' FROM valuemap vm'. - ' WHERE '.implode(' OR ', $sql_where) - ); + $options = [ + 'output' => ['item_parameterid', 'itemid', 'name', 'value'], + 'filter' => ['itemid' => $itemids] + ]; + $db_item_parameters = DBselect(DB::makeSql('item_parameter', $options)); + + while ($db_item_parameter = DBfetch($db_item_parameters)) { + $db_items[$db_item_parameter['itemid']]['parameters'][$db_item_parameter['item_parameterid']] = + array_diff_key($db_item_parameter, array_flip(['itemid'])); + } + } + + /** + * Add the inherited items of the given items to the given item array. + * + * @param array $db_items + */ + public static function addInheritedItems(array &$db_items): void { + $templateids = array_keys($db_items); + + do { + $options = [ + 'output' => ['itemid', 'name'], + 'filter' => ['templateid' => $templateids] + ]; + $result = DBselect(DB::makeSql('items', $options)); + + $templateids = []; + while ($row = DBfetch($result)) { - unset($valuemapids_by_hostid[$row['hostid']][$row['valuemapid']]); + if (!array_key_exists($row['itemid'], $db_items)) { + $templateids[] = $row['itemid']; - if (!$valuemapids_by_hostid[$row['hostid']]) { - unset($valuemapids_by_hostid[$row['hostid']]); + $db_items[$row['itemid']] = $row; } } + } while ($templateids); + } - if ($valuemapids_by_hostid) { - $hostid = key($valuemapids_by_hostid); - $valuemapid = key($valuemapids_by_hostid[$hostid]); + /** + * Reset the MIN and MAX values of Y axis in the graphs, if such are calculated using the given items. + * + * @param array $del_itemids + */ + protected static function resetGraphsYAxis(array $del_itemids): void { + DB::update('graphs', [ + 'values' => [ + 'ymin_type' => GRAPH_YAXIS_TYPE_CALCULATED, + 'ymin_itemid' => null + ], + 'where' => ['ymin_itemid' => $del_itemids] + ]); - $host_row = DBfetch(DBselect('SELECT h.host FROM hosts h WHERE h.hostid='.zbx_dbstr($hostid))); - self::exception(ZBX_API_ERROR_PARAMETERS, _s('Valuemap with ID "%1$s" is not available on "%2$s".', - $valuemapid, $host_row['host'] - )); - } - } + DB::update('graphs', [ + 'values' => [ + 'ymax_type' => GRAPH_YAXIS_TYPE_CALCULATED, + 'ymax_itemid' => null + ], + 'where' => ['ymax_itemid' => $del_itemids] + ]); } /** - * Normalize preprocessing step parameters. - * - * @param array $preprocessing Preprocessing steps. - * @param string $preprocessing[<num>]['params'] Preprocessing step parameters. - * @param int $preprocessing[<num>]['type'] Preprocessing step type. + * Delete triggers and trigger prototypes, which contain the given items in the expression. * - * @return array + * @param array $del_itemids */ - protected function normalizeItemPreprocessingSteps(array $preprocessing): array { - foreach ($preprocessing as &$step) { - $step['params'] = str_replace("\r\n", "\n", $step['params']); - $params = explode("\n", $step['params']); - - switch ($step['type']) { - case ZBX_PREPROC_PROMETHEUS_PATTERN: - if (!array_key_exists(2, $params)) { - $params[2] = ''; - } - break; + protected static function deleteAffectedTriggers(array $del_itemids): void { + $result = DBselect( + 'SELECT DISTINCT f.triggerid,t.flags'. + ' FROM functions f,triggers t'. + ' WHERE f.triggerid=t.triggerid'. + ' AND '.dbConditionInt('f.itemid', $del_itemids) + ); + + $del_trigger_prototypeids = []; + $del_triggerids = []; + + while ($row = DBfetch($result)) { + if ($row['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) { + $del_trigger_prototypeids[] = $row['triggerid']; } + else { + $del_triggerids[] = $row['triggerid']; + } + } - $step['params'] = implode("\n", $params); + if ($del_triggerids) { + CTriggerManager::delete($del_triggerids); } - unset($step); - return $preprocessing; + if ($del_trigger_prototypeids) { + CTriggerPrototypeManager::delete($del_trigger_prototypeids); + } } } |