You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2780 lines
86 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
/**
* Class containing methods for operations with item general.
*/
abstract class CItemGeneral extends CApiService {
public const ACCESS_RULES = [
'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
];
public const INTERFACE_TYPES_BY_PRIORITY = [
INTERFACE_TYPE_AGENT,
INTERFACE_TYPE_SNMP,
INTERFACE_TYPE_JMX,
INTERFACE_TYPE_IPMI
];
/**
* A list of supported preprocessing types.
*
* @var array
*/
public const SUPPORTED_PREPROCESSING_TYPES = [];
/**
* A list of preprocessing types that supports the "params" field.
*
* @var array
*/
protected const PREPROC_TYPES_WITH_PARAMS = [
ZBX_PREPROC_MULTIPLIER, ZBX_PREPROC_RTRIM, ZBX_PREPROC_LTRIM, ZBX_PREPROC_TRIM, ZBX_PREPROC_REGSUB,
ZBX_PREPROC_XPATH, ZBX_PREPROC_JSONPATH, ZBX_PREPROC_VALIDATE_RANGE, ZBX_PREPROC_VALIDATE_REGEX,
ZBX_PREPROC_VALIDATE_NOT_REGEX, ZBX_PREPROC_ERROR_FIELD_JSON, ZBX_PREPROC_ERROR_FIELD_XML,
ZBX_PREPROC_ERROR_FIELD_REGEX, ZBX_PREPROC_THROTTLE_TIMED_VALUE, ZBX_PREPROC_SCRIPT,
ZBX_PREPROC_PROMETHEUS_PATTERN, ZBX_PREPROC_PROMETHEUS_TO_JSON, ZBX_PREPROC_CSV_TO_JSON,
ZBX_PREPROC_STR_REPLACE, ZBX_PREPROC_SNMP_WALK_VALUE, ZBX_PREPROC_SNMP_WALK_TO_JSON
];
/**
* A list of preprocessing types that supports the error handling.
*
* @var array
*/
protected const PREPROC_TYPES_WITH_ERR_HANDLING = [
ZBX_PREPROC_MULTIPLIER, ZBX_PREPROC_REGSUB, ZBX_PREPROC_BOOL2DEC, ZBX_PREPROC_OCT2DEC, ZBX_PREPROC_HEX2DEC,
ZBX_PREPROC_DELTA_VALUE, ZBX_PREPROC_DELTA_SPEED, ZBX_PREPROC_XPATH, ZBX_PREPROC_JSONPATH,
ZBX_PREPROC_VALIDATE_RANGE, ZBX_PREPROC_VALIDATE_REGEX, ZBX_PREPROC_VALIDATE_NOT_REGEX,
ZBX_PREPROC_ERROR_FIELD_JSON, ZBX_PREPROC_ERROR_FIELD_XML, ZBX_PREPROC_ERROR_FIELD_REGEX,
ZBX_PREPROC_PROMETHEUS_PATTERN, ZBX_PREPROC_PROMETHEUS_TO_JSON, ZBX_PREPROC_CSV_TO_JSON,
ZBX_PREPROC_VALIDATE_NOT_SUPPORTED, ZBX_PREPROC_XML_TO_JSON, ZBX_PREPROC_SNMP_WALK_VALUE,
ZBX_PREPROC_SNMP_WALK_TO_JSON
];
/**
* A list of supported item types.
*
* @var array
*/
protected const SUPPORTED_ITEM_TYPES = [];
/**
* A list of field names for each of value types.
*
* @var array
*/
protected const VALUE_TYPE_FIELD_NAMES = [];
/**
* Maximum number of inheritable items per iteration.
*
* @var int
*/
protected const INHERIT_CHUNK_SIZE = 1000;
/**
* @abstract
*
* @param array $options
*
* @return array
*/
abstract public function get($options = []);
/**
* @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]);
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']);
if ($db_item === null) {
$api_input_rules['fields'] += $item_type::getCreateValidationRules($item);
}
elseif ($db_item['templateid'] != 0) {
if ($item['type'] == ITEM_TYPE_HTTPAGENT) {
$item += array_intersect_key($db_item, array_flip(['allow_traps']));
}
elseif ($item['type'] == ITEM_TYPE_SSH) {
$item += array_intersect_key($db_item, array_flip(['authtype']));
}
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::getUpdateValidationRulesInherited($db_item);
}
elseif ($db_item['flags'] == ZBX_FLAG_DISCOVERY_CREATED) {
$api_input_rules['fields'] += $item_type::getUpdateValidationRulesDiscovered();
}
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']));
}
$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
];
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
];
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 (in_array($item['type'], [ITEM_TYPE_SSH, ITEM_TYPE_TELNET])) {
$opt_username_types = array_diff($username_types, [ITEM_TYPE_SSH, ITEM_TYPE_TELNET]);
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']));
}
}
$params_types = [ITEM_TYPE_DB_MONITOR, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_CALCULATED,
ITEM_TYPE_SCRIPT
];
if (in_array($item['type'], $params_types) && !in_array($db_item['type'], $params_types)) {
$item += array_intersect_key($db_item, array_flip(['params']));
}
$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
];
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 ($item['type'] == ITEM_TYPE_DEPENDENT && $db_item['type'] != ITEM_TYPE_DEPENDENT) {
$item += array_intersect_key($db_item, array_flip(['master_itemid']));
}
if ($item['type'] == ITEM_TYPE_HTTPAGENT) {
if ($db_item['type'] != ITEM_TYPE_HTTPAGENT) {
$item += array_intersect_key($db_item, array_flip(['url']));
}
$post_types = [ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML];
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 ($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 ($item['type'] == ITEM_TYPE_JMX && $db_item['type'] != ITEM_TYPE_JMX) {
$item += array_intersect_key($db_item, array_flip(['jmx_endpoint']));
}
if ($item['type'] == ITEM_TYPE_SNMP && $db_item['type'] != ITEM_TYPE_SNMP) {
$item += array_intersect_key($db_item, array_flip(['snmp_oid']));
}
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);
}
$api_input_rules['fields'] += CItemType::getDefaultValidationRules();
if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($i + 1), $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
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 ($db_item === null) {
$_item += array_fill_keys(['username', 'password'], '');
}
else {
$_item += array_intersect_key($db_item, array_flip(['username', 'password']));
}
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')
));
}
}
}
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'))
);
}
}
$item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : '';
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')
));
}
}
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')
));
}
}
$item['headers'] = self::headersArrayToString($item['headers']);
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);
}
/**
* @param array $items
*/
protected static function validateUniqueness(array &$items): void {
$api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['uuid'], ['hostid', 'key_']], 'fields' => [
'uuid' => ['type' => API_ANY],
'hostid' => ['type' => API_ANY],
'key_' => ['type' => API_ANY]
]];
if (!CApiInputValidator::validateUniqueness($api_input_rules, $items, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
}
/**
* @return array
*/
protected static function getTagsValidationRules(): array {
return ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, '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')]
]];
}
/**
* @param int $flags
*
* @return array
*/
public static function getPreprocessingValidationRules(int $flags = 0x00): array {
return [
'type' => API_OBJECTS,
'flags' => API_NORMALIZE,
'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 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
* @param array|null $db_hosts
* @param array|null $db_templates
*
* @throws APIException
*/
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
]);
$_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!'));
}
}
/**
* Add host_status property to given items in accordance to statuses of given hosts and templates.
*
* @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) {
$item['host_status'] = array_key_exists($item['hostid'], $db_templates)
? HOST_STATUS_TEMPLATE
: $db_hosts[$item['hostid']]['status'];
}
unset($item);
}
/**
* Add flags property to given items with the given flags value.
*
* @param array $items
* @param int $flags
*/
protected static function addFlags(array &$items, int $flags): void {
foreach ($items as &$item) {
$item['flags'] = $flags;
}
unset($item);
}
/**
* Add the UUID to those of the given items that belong to a template and don't have the 'uuid' parameter set.
*
* @param array $items
*/
protected static function addUuid(array &$items): void {
foreach ($items as &$item) {
if ($item['host_status'] == HOST_STATUS_TEMPLATE && !array_key_exists('uuid', $item)) {
$item['uuid'] = generateUuidV4();
}
}
unset($item);
}
/**
* Verify host prototype UUIDs are not repeated.
*
* @param array $items
* @param array|null $db_items
*
* @throws APIException
*/
protected static function checkUuidDuplicates(array $items, array $db_items = null): void {
$item_indexes = [];
foreach ($items as $i => $item) {
if (!array_key_exists('uuid', $item)) {
continue;
}
if ($db_items === null || $item['uuid'] !== $db_items[$item['itemid']]['uuid']) {
$item_indexes[$item['uuid']] = $i;
}
}
if (!$item_indexes) {
return;
}
$flags = $items[reset($item_indexes)]['flags'];
$duplicates = DB::select('items', [
'output' => ['uuid'],
'filter' => [
'flags' => $flags,
'uuid' => array_keys($item_indexes)
],
'limit' => 1
]);
if ($duplicates) {
switch ($flags) {
case ZBX_FLAG_DISCOVERY_NORMAL:
$error = _s('Invalid parameter "%1$s": %2$s.', '/'.($item_indexes[$duplicates[0]['uuid']] + 1),
_('item with the same UUID already exists')
);
break;
case ZBX_FLAG_DISCOVERY_RULE:
$error = _s('Invalid parameter "%1$s": %2$s.', '/'.($item_indexes[$duplicates[0]['uuid']] + 1),
_('LLD rule with the same UUID already exists')
);
break;
case ZBX_FLAG_DISCOVERY_PROTOTYPE:
$error = _s('Invalid parameter "%1$s": %2$s.', '/'.($item_indexes[$duplicates[0]['uuid']] + 1),
_('item prototype with the same UUID already exists')
);
break;
}
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
}
/**
* @param array $items
* @param array|null $hostids
*
* @return array
*/
protected static function getTemplateLinks(array $items, ?array $hostids): array {
if ($hostids !== null) {
$db_hosts = DB::select('hosts', [
'output' => ['hostid', 'status'],
'hostids' => $hostids,
'preservekeys' => true
]);
$tpl_links = [];
foreach ($items as $item) {
$tpl_links[$item['hostid']] = $db_hosts;
}
}
else {
$templateids = [];
foreach ($items as $item) {
if ($item['host_status'] == HOST_STATUS_TEMPLATE) {
$templateids[$item['hostid']] = true;
}
}
if (!$templateids) {
return [];
}
$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)).
' AND '.dbConditionInt('h.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED])
);
$tpl_links = [];
while ($row = DBfetch($result)) {
$tpl_links[$row['templateid']][$row['hostid']] = [
'hostid' => $row['hostid'],
'status' => $row['status']
];
}
}
return $tpl_links;
}
/**
* Filter out inheritable items from the given items.
*
* @param array $items
* @param array $db_items
* @param array $tpl_links
*/
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]);
if (array_key_exists($item['itemid'], $db_items)) {
unset($db_items[$item['itemid']]);
}
}
}
}
/**
* 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
*/
protected static function checkDoubleInheritedNames(array $items, array $db_items, array $tpl_links): void {
$item_indexes = [];
foreach ($items as $i => $item) {
if (array_key_exists($item['itemid'], $db_items) && $item['key_'] === $db_items[$item['itemid']]['key_']) {
continue;
}
$item_indexes[$item['key_']][] = $i;
}
foreach ($item_indexes as $key => $indexes) {
if (count($indexes) == 1) {
continue;
}
$hostids = [];
foreach ($indexes as $i) {
$templateid = $items[$i]['hostid'];
$same_hosts = array_intersect_key($tpl_links[$templateid], $hostids);
if ($same_hosts) {
$same_host = reset($same_hosts);
$templateid_first = $hostids[$same_host['hostid']];
$templateid_second = $templateid;
$hosts = DB::select('hosts', [
'output' => ['hostid', 'host'],
'hostids' => [$templateid_first, $templateid_second, $same_host['hostid']],
'preservekeys' => true
]);
$target_is_host = in_array($same_host['status'],
[HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]
);
switch ($items[$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;
}
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $key,
$hosts[$templateid_first]['host'], $hosts[$templateid_second]['host'],
$hosts[$same_host['hostid']]['host']
));
}
$hostids += array_fill_keys(array_keys($tpl_links[$templateid]), $templateid);
}
}
}
/**
* Get item chunks to inherit.
*
* @param array $items
* @param array $tpl_links
*
* @return array
*/
protected static function getInheritChunks(array $items, array $tpl_links): array {
$chunks = [
[
'item_indexes' => [],
'hosts' => [],
'size' => 0
]
];
$last = 0;
foreach ($items as $i => $item) {
$hosts_chunks = array_chunk($tpl_links[$item['hostid']], self::INHERIT_CHUNK_SIZE, true);
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);
$can_add_hosts = true;
foreach ($chunks[$last]['item_indexes'] as $_i) {
$new_hosts = array_diff_key($_hosts, $chunks[$last]['hosts']);
if (array_intersect_key($tpl_links[$items[$_i]['hostid']], $new_hosts)) {
$can_add_hosts = false;
break;
}
}
if ($can_add_hosts) {
$chunks[$last]['item_indexes'][] = $i;
$chunks[$last]['hosts'] += $_hosts;
$chunks[$last]['size'] += count($_hosts);
$hosts = array_diff_key($hosts, $_hosts);
}
}
if ($hosts) {
$chunks[++$last] = [
'item_indexes' => [$i],
'hosts' => $hosts,
'size' => count($hosts)
];
}
}
}
return $chunks;
}
/**
* @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]);
$hosts = DB::select('hosts', [
'output' => ['host'],
'hostids' => [$item['hostid'], $upd_db_item['hostid']],
'preservekeys' => true
]);
$error = '';
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;
}
break;
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 an item 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 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;
}
break;
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 an item with the same key already exists.')
: _('Cannot inherit item prototype with key "%1$s" of template "%2$s" to template "%3$s", because an item with the same key already exists.');
break 2;
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;
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;
}
break;
}
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 ($upd_db_item['templateid'] == 0) {
return;
}
$template = DBfetch(DBselect(
'SELECT h.host'.
' FROM items i,hosts h'.
' WHERE i.hostid=h.hostid'.
' AND '.dbConditionId('i.itemid', [$upd_db_item['templateid']])
));
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;
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 LLD rule 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 LLD rule with the same key is already inherited from template "%4$s".');
break;
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 prototype 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 prototype with the same key is already inherited from template "%4$s".');
break;
}
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $upd_db_item['key_'], $hosts[$item['hostid']]['host'],
$hosts[$upd_db_item['hostid']]['host'], $template['host']
));
}
/**
* @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 (array_key_exists('preprocessing', $item)) {
foreach ($item['preprocessing'] as &$preprocessing) {
unset($preprocessing['item_preprocid']);
}
unset($preprocessing);
}
if (array_key_exists('parameters', $item)) {
foreach ($item['parameters'] as &$parameter) {
unset($parameter['item_parameterid']);
}
unset($parameter);
}
return $item;
}
/**
* 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 = [];
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;
}
}
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;
}
}
if (!$upd_item_indexes && !$ins_item_indexes) {
return;
}
$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'];
}
}
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'];
}
}
}
}
/**
* @param array $upd_items
* @param array $upd_db_items
* @param array $ins_items
*
* @throws APIException
*/
protected static function addInterfaceIds(array &$upd_items, array $upd_db_items, array &$ins_items): void {
$upd_item_indexes = [];
$ins_item_indexes = [];
$interface_types = [];
$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;
}
$interface_type = itemTypeInterface($upd_item['type']);
if ($interface_type === false) {
continue;
}
if ($upd_db_items[$upd_item['itemid']]['interfaceid'] != 0) {
$db_interface_type = itemTypeInterface($upd_db_items[$upd_item['itemid']]['type']);
if ($interface_type != $db_interface_type) {
if ($db_interface_type == INTERFACE_TYPE_OPT) {
$upd_item_indexes_by_interfaceid[$upd_db_items[$upd_item['itemid']]['interfaceid']][] = $i;
}
elseif ($interface_type != INTERFACE_TYPE_OPT) {
$upd_item_indexes[$upd_item['hostid']][$interface_type][] = $i;
if ($interface_types !== null) {
$interface_types[$interface_type] = true;
}
}
}
}
else {
$upd_item_indexes[$upd_item['hostid']][$interface_type][] = $i;
if ($interface_types !== null) {
if ($interface_type == INTERFACE_TYPE_OPT) {
$interface_types = null;
}
else {
$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 ($interface_type != $row['type']) {
$upd_item_indexes[$upd_item['hostid']][$interface_type][] = $i;
if ($interface_types !== null) {
$interface_types[$interface_type] = true;
}
}
}
}
}
foreach ($ins_items as $i => $ins_item) {
if (!in_array($ins_item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])) {
continue;
}
$interface_type = itemTypeInterface($ins_item['type']);
if ($interface_type === false) {
continue;
}
$ins_item_indexes[$ins_item['hostid']][$interface_type][] = $i;
if ($interface_types !== null) {
if ($interface_type == INTERFACE_TYPE_OPT) {
$interface_types = null;
}
else {
$interface_types[$interface_type] = true;
}
}
}
if (!$upd_item_indexes && !$ins_item_indexes) {
return;
}
$options = [
'output' => ['interfaceid', 'hostid', 'type'],
'filter' => [
'hostid' => array_keys($upd_item_indexes + $ins_item_indexes),
'main' => INTERFACE_PRIMARY
]
];
if ($interface_types !== null) {
$options['filter']['type'] = array_keys($interface_types);
}
$result = DBselect(DB::makeSql('interface', $options));
$priority_interfaces = [];
while ($row = DBfetch($result)) {
$has_opt_type_items = false;
if (array_key_exists($row['hostid'], $upd_item_indexes)) {
if (array_key_exists(INTERFACE_TYPE_OPT, $upd_item_indexes[$row['hostid']])) {
$has_opt_type_items = true;
}
if (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'];
unset($upd_item_indexes[$row['hostid']][$row['type']][$_i]);
}
if (!$upd_item_indexes[$row['hostid']][$row['type']]) {
unset($upd_item_indexes[$row['hostid']][$row['type']]);
}
if (!$upd_item_indexes[$row['hostid']]) {
unset($upd_item_indexes[$row['hostid']]);
}
}
}
if (array_key_exists($row['hostid'], $ins_item_indexes)) {
if (array_key_exists(INTERFACE_TYPE_OPT, $ins_item_indexes[$row['hostid']])) {
$has_opt_type_items = true;
}
if (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]);
}
if (!$ins_item_indexes[$row['hostid']][$row['type']]) {
unset($ins_item_indexes[$row['hostid']][$row['type']]);
}
if (!$ins_item_indexes[$row['hostid']]) {
unset($ins_item_indexes[$row['hostid']]);
}
}
}
if ($has_opt_type_items) {
$priority_index = array_search($row['type'], self::INTERFACE_TYPES_BY_PRIORITY);
if (!array_key_exists($row['hostid'], $priority_interfaces)
|| $priority_index < $priority_interfaces[$row['hostid']]['priority_index']) {
$priority_interfaces[$row['hostid']] = [
'interfaceid' => $row['interfaceid'],
'type' => $row['type'],
'priority_index' => $priority_index
];
}
}
}
foreach ($upd_item_indexes as $hostid => $item_indexes) {
if (!array_key_exists(INTERFACE_TYPE_OPT, $item_indexes)) {
continue;
}
foreach ($item_indexes[INTERFACE_TYPE_OPT] as $i) {
if (array_key_exists($hostid, $priority_interfaces)) {
$upd_items[$i]['interfaceid'] = $priority_interfaces[$hostid]['interfaceid'];
}
}
unset($upd_item_indexes[$hostid][INTERFACE_TYPE_OPT]);
if (!$upd_item_indexes[$hostid]) {
unset($upd_item_indexes[$hostid]);
}
}
foreach ($ins_item_indexes as $hostid => $item_indexes) {
if (!array_key_exists(INTERFACE_TYPE_OPT, $item_indexes)) {
continue;
}
foreach ($item_indexes[INTERFACE_TYPE_OPT] as $i) {
if (array_key_exists($hostid, $priority_interfaces)) {
$ins_items[$i]['interfaceid'] = $priority_interfaces[$hostid]['interfaceid'];
}
}
unset($ins_item_indexes[$hostid][INTERFACE_TYPE_OPT]);
if (!$ins_item_indexes[$hostid]) {
unset($ins_item_indexes[$hostid]);
}
}
$item = null;
if ($upd_item_indexes) {
$hostid = key($upd_item_indexes);
$interface_type = key($upd_item_indexes[$hostid]);
$i = reset($upd_item_indexes[$hostid][$interface_type]);
$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]);
$item = $ins_items[$i];
}
if ($item === null) {
return;
}
$templates = DBfetchArray(DBselect(
'SELECT h.host'.
' FROM items i,hosts h'.
' WHERE i.hostid=h.hostid'.
' AND '.dbConditionId('i.itemid', [$item['templateid']])
));
$hosts = DB::select('hosts', [
'output' => ['host'],
'hostids' => $item['hostid']
]);
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;
case ZBX_FLAG_DISCOVERY_RULE:
$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;
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;
}
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $item['key_'], $templates[0]['host'],
$hosts[0]['host'], interfaceType2str($interface_type)
));
}
/**
* 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 = [];
foreach ($dep_items_to_link as $i => $_items) {
if (array_key_exists($i, $items_to_link)) {
$items += $_items;
unset($dep_items_to_link[$i]);
}
}
static::inherit(array_values($items), [], $hostids, true);
$items_to_link = $items;
}
}
/**
* @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;
/**
* 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')
];
$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')
];
foreach ($items as &$item) {
if (!array_key_exists('type', $db_items[$item['itemid']])) {
continue;
}
$db_item = $db_items[$item['itemid']];
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;
$field_names = array_flip(array_diff($db_type_field_names, $type_field_names));
if ($item['host_status'] == HOST_STATUS_TEMPLATE && array_key_exists('interfaceid', $field_names)) {
unset($field_names['interfaceid']);
}
$item += array_intersect_key($type_field_defaults, $field_names);
}
if ($item['type'] == ITEM_TYPE_ZABBIX_ACTIVE) {
if (($item['type'] != $db_item['type'] || $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 ($item['type'] != $db_item['type']) {
if ($db_item['type'] == ITEM_TYPE_HTTPAGENT) {
$item += array_intersect_key($type_field_defaults, array_flip(['authtype']));
}
}
elseif (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 ($item['type'] != $db_item['type']) {
if (!array_key_exists('authtype', $item)) {
$item += array_intersect_key($type_field_defaults, array_flip(['authtype']));
}
if ($item['authtype'] == ZBX_HTTP_AUTH_NONE) {
$item += array_intersect_key($type_field_defaults, array_flip(['username', 'password']));
}
if (!array_key_exists('allow_traps', $item) || $item['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF) {
$item += array_intersect_key($type_field_defaults, array_flip(['trapper_hosts']));
}
}
else {
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 (array_key_exists('authtype', $item) && $item['authtype'] != $db_item['authtype']
&& $item['authtype'] == ZBX_HTTP_AUTH_NONE) {
$item += array_intersect_key($type_field_defaults, array_flip(['username', 'password']));
}
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']));
}
}
}
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']];
$field_names = array_flip(array_diff($db_type_field_names, $type_field_names));
if (array_key_exists('trends', $field_names)) {
$item += ['trends' => 0];
}
$item += array_intersect_key($value_type_field_defaults, $field_names);
}
}
unset($item);
}
/**
* @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 = [];
foreach ($items as $i => &$item) {
$update = false;
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;
}
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 (!$update) {
continue;
}
$changed = false;
$db_item_parameters = ($db_items !== null)
? array_column($db_items[$item['itemid']]['parameters'], null, 'name')
: [];
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']]);
$upd_item_parameter = DB::getUpdatedValues('item_parameter', $item_parameter, $db_item_parameter);
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 ($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'], $db_items[$item['itemid']]['parameters']);
}
}
}
unset($item);
if ($del_item_parameterids) {
DB::delete('item_parameter', ['item_parameterid' => $del_item_parameterids]);
}
if ($upd_item_parameters) {
DB::update('item_parameter', $upd_item_parameters);
}
if ($ins_item_parameters) {
$item_parameterids = DB::insert('item_parameter', $ins_item_parameters);
}
foreach ($items as &$item) {
if (!array_key_exists('parameters', $item)) {
continue;
}
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);
}
unset($item);
}
/**
* @param array $items
* @param array|null $db_items
* @param array|null $upd_itemids
*/
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 $i => &$item) {
if (!array_key_exists('preprocessing', $item)) {
continue;
}
$changed = false;
$db_item_preprocs = ($db_items !== null)
? array_column($db_items[$item['itemid']]['preprocessing'], null, 'step')
: [];
$step = 1;
foreach ($item['preprocessing'] as &$item_preproc) {
$item_preproc['step'] = ($item_preproc['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) ? 0 : $step++;
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']]);
$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']]
];
$changed = true;
}
}
else {
$ins_item_preprocs[] = ['itemid' => $item['itemid']] + $item_preproc;
$changed = true;
}
}
unset($item_preproc);
if ($db_item_preprocs) {
$del_item_preprocids =
array_merge($del_item_preprocids, array_column($db_item_preprocs, 'item_preprocid'));
$changed = true;
}
if ($db_items !== null) {
if ($changed) {
$upd_itemids[$i] = $item['itemid'];
}
else {
unset($item['preprocessing'], $db_items[$item['itemid']]['preprocessing']);
}
}
}
unset($item);
if ($del_item_preprocids) {
DB::delete('item_preproc', ['item_preprocid' => $del_item_preprocids]);
}
if ($upd_item_preprocs) {
DB::update('item_preproc', $upd_item_preprocs);
}
if ($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);
}
/**
* @param array $items
* @param array|null $db_items
* @param array|null $upd_itemids
*/
protected static function updateTags(array &$items, array &$db_items = null, array &$upd_itemids = null): void {
$ins_tags = [];
$del_itemtagids = [];
foreach ($items as $i => &$item) {
if (!array_key_exists('tags', $item)) {
continue;
}
$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'], $db_items[$item['itemid']]['tags']);
}
}
}
unset($item);
if ($del_itemtagids) {
DB::delete('item_tag', ['itemtagid' => $del_itemtagids]);
}
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);
}
/**
* Check for unique item keys.
*
* @param array $items
* @param array|null $db_items
*
* @throws APIException if item keys are not unique.
*/
protected static function checkDuplicates(array $items, array $db_items = null): void {
$host_keys = [];
foreach ($items as $item) {
if ($db_items === null || $item['key_'] !== $db_items[$item['itemid']]['key_']) {
$host_keys[$item['hostid']][] = $item['key_'];
}
}
if (!$host_keys) {
return;
}
$where = [];
foreach ($host_keys as $hostid => $keys) {
$where[] = '('.dbConditionId('i.hostid', [$hostid]).' AND '.dbConditionString('i.key_', $keys).')';
}
$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']));
}
}
/**
* @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;
}
$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')
));
}
}
else {
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')
));
}
}
}
}
if (!$check) {
unset($items[$i]);
}
}
unset($item);
if (!$items) {
return;
}
$db_interfaces = DB::select('interface', [
'output' => ['interfaceid', 'hostid', 'type'],
'interfaceids' => array_unique(array_column($items, 'interfaceid')),
'preservekeys' => true
]);
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')
));
}
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')
));
}
$interface_type = itemTypeInterface($item['type']);
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))
));
}
}
}
protected function addRelatedObjects(array $options, array $result) {
$result = parent::addRelatedObjects($options, $result);
// adding hosts
if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) {
$relationMap = $this->createRelationMap($result, 'itemid', 'hostid');
$hosts = API::Host()->get([
'hostids' => $relationMap->getRelatedIds(),
'templated_hosts' => true,
'output' => $options['selectHosts'],
'nopermissions' => true,
'preservekeys' => true
]);
$result = $relationMap->mapMany($result, $hosts, 'hosts');
}
// adding preprocessing
if ($options['selectPreprocessing'] !== null && $options['selectPreprocessing'] != API_OUTPUT_COUNT) {
$db_item_preproc = API::getApiService()->select('item_preproc', [
'output' => $this->outputExtend($options['selectPreprocessing'], ['itemid', 'step']),
'filter' => ['itemid' => array_keys($result)]
]);
CArrayHelper::sort($db_item_preproc, ['step']);
foreach ($result as &$item) {
$item['preprocessing'] = [];
}
unset($item);
foreach ($db_item_preproc as $step) {
$itemid = $step['itemid'];
unset($step['item_preprocid'], $step['itemid'], $step['step']);
if (array_key_exists($itemid, $result)) {
$result[$itemid]['preprocessing'][] = $step;
}
}
}
// Add value mapping.
if (($this instanceof CItemPrototype || $this instanceof CItem) && $options['selectValueMap'] !== null) {
if ($options['selectValueMap'] === API_OUTPUT_EXTEND) {
$options['selectValueMap'] = ['valuemapid', 'name', 'mappings'];
}
foreach ($result as &$item) {
$item['valuemap'] = [];
}
unset($item);
$valuemaps = DB::select('valuemap', [
'output' => array_diff($this->outputExtend($options['selectValueMap'], ['valuemapid', 'hostid']),
['mappings']
),
'filter' => ['valuemapid' => array_keys(array_flip(array_column($result, 'valuemapid')))],
'preservekeys' => true
]);
if ($this->outputIsRequested('mappings', $options['selectValueMap']) && $valuemaps) {
$params = [
'output' => ['valuemapid', 'type', 'value', 'newvalue'],
'filter' => ['valuemapid' => array_keys($valuemaps)],
'sortfield' => ['sortorder']
];
$query = DBselect(DB::makeSql('valuemap_mapping', $params));
while ($mapping = DBfetch($query)) {
$valuemaps[$mapping['valuemapid']]['mappings'][] = [
'type' => $mapping['type'],
'value' => $mapping['value'],
'newvalue' => $mapping['newvalue']
];
}
}
foreach ($result as &$item) {
if (array_key_exists('valuemapid', $item) && array_key_exists($item['valuemapid'], $valuemaps)) {
$item['valuemap'] = array_intersect_key($valuemaps[$item['valuemapid']],
array_flip($options['selectValueMap'])
);
}
}
unset($item);
}
if (!$options['countOutput'] && $this->outputIsRequested('parameters', $options['output'])) {
$item_parameters = DBselect(
'SELECT ip.itemid,ip.name,ip.value'.
' FROM item_parameter ip'.
' WHERE '.dbConditionInt('ip.itemid', array_keys($result))
);
foreach ($result as &$item) {
$item['parameters'] = [];
}
unset($item);
while ($row = DBfetch($item_parameters)) {
$result[$row['itemid']]['parameters'][] = [
'name' => $row['name'],
'value' => $row['value']
];
}
}
return $result;
}
/**
* Check that dependent items of given items are valid.
*
* @param array $items
* @param array $db_items
* @param bool $inherited
*
* @throws APIException
*/
protected static function checkDependentItems(array $items, array $db_items = [], bool $inherited = false): void {
$del_links = [];
foreach ($items as $i => $item) {
$check = false;
if ($item['type'] == ITEM_TYPE_DEPENDENT) {
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');
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
'/'.($i + 1).'/master_itemid', $error
));
}
}
}
}
elseif (array_key_exists('itemid', $item) && $db_items[$item['itemid']]['type'] == ITEM_TYPE_DEPENDENT) {
$del_links[$item['itemid']] = $db_items[$item['itemid']]['master_itemid'];
}
if (!$check) {
unset($items[$i]);
}
}
if (!$items) {
return;
}
if (!$inherited) {
self::checkMasterItems($items, $db_items);
}
$dep_item_links = self::getDependentItemLinks($items, $del_links);
if (!$inherited && $db_items) {
self::checkCircularDependencies($items, $dep_item_links);
}
$root_itemids = [];
foreach ($dep_item_links as $itemid => $master_itemid) {
if ($master_itemid == 0) {
$root_itemids[] = $itemid;
}
}
$master_item_links = self::getMasterItemLinks($items, $root_itemids, $del_links);
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);
$error = self::getDependentItemError($flags, $master_flags, $is_template);
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $key, $master_key, $host,
_('allowed count of dependency levels would be exceeded')
));
}
if (self::maxDependentItemCountExceeded($master_item_links, $root_itemid, $links_path)) {
[$flags, $key, $master_flags, $master_key, $is_template, $host] =
self::getProblemCausedItemData($links_path, $items);
$error = self::getDependentItemError($flags, $master_flags, $is_template);
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $key, $master_key, $host,
_('allowed count of dependent items would be exceeded')
));
}
}
}
/**
* 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'];
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');
}
else {
$db_master_items = DB::select('items', [
'output' => ['itemid', 'hostid', 'master_itemid'],
'itemids' => $master_itemids,
'filter' => [
'flags' => ZBX_FLAG_DISCOVERY_NORMAL
],
'preservekeys' => true
]);
}
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');
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
'/'.($i + 1).'/master_itemid', $error
));
}
$db_master_item = $db_master_items[$item['master_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
));
}
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'];
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')
));
}
}
}
}
/**
* Get dependent item links starting from the given dependent items and till the highest dependency level.
*
* @param array $items
* @param array $del_links
*
* @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 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));
$master_itemids = [];
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;
}
$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 $links;
}
/**
* Check that the changed master item IDs of dependent items do not create a circular dependencies.
*
* @param array $items
* @param array $dep_item_links
*
* @throws APIException
*/
private static function checkCircularDependencies(array $items, array $dep_item_links): void {
foreach ($items as $i => $item) {
if ($item['flags'] == ZBX_FLAG_DISCOVERY_RULE) {
continue;
}
$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')
));
}
$master_itemid = $dep_item_links[$master_itemid];
}
}
}
/**
* Get master item links starting from the given master items and till the lowest level master items.
*
* @param array $items
* @param array $master_itemids
* @param array $del_links
*
* @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.
*/
private static function getMasterItemLinks(array $items, array $master_itemids, array $del_links): array {
$ins_links = [];
$upd_item_links = [];
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;
}
}
$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;
}
/**
* Check whether maximum number of dependency levels is exceeded.
*
* @param array $master_item_links
* @param string $master_itemid
* @param array|null $links_path
* @param int $level
*
* @return bool
*/
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;
}
if ($links_path === null) {
$links_path = [];
}
$links_path[] = $master_itemid;
$level++;
if ($level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) {
return true;
}
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;
return true;
}
}
return false;
}
/**
* 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 ($links_path === null) {
$links_path = [];
}
$links_path[] = $master_itemid;
$count += count($master_item_links[$master_itemid]);
if ($count > ZBX_DEPENDENT_ITEM_MAX_COUNT) {
return true;
}
foreach ($master_item_links[$master_itemid] as $itemid) {
$_links_path = $links_path;
if (self::maxDependentItemCountExceeded($master_item_links, $itemid, $_links_path, $count)) {
$links_path = $_links_path;
return true;
}
}
return false;
}
/**
* 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;
}
}
$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']])
));
$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'];
return [$flags, $key, $master_flags, $master_key, $is_template, $host];
}
/**
* 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.');
}
}
/**
* 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 = [];
foreach (explode("\r\n", $headers) as $header) {
$header = explode(': ', $header, 2);
if (count($header) == 2) {
$result[$header[0]] = $header[1];
}
}
return $result;
}
/**
* 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 = [];
foreach ($headers as $k => $v) {
$result[] = $k.': '.$v;
}
return implode("\r\n", $result);
}
/**
* Remove NCLOB value type fields from resulting query SELECT part if DISTINCT will be used.
*
* @param string $table_name Table name.
* @param string $table_alias Table alias value.
* @param array $options Array of query options.
* @param array $sql_parts Array of query parts already initialized from $options.
*
* @return array The resulting SQL parts array.
*/
protected function applyQueryOutputOptions($table_name, $table_alias, array $options, array $sql_parts) {
if (!$options['countOutput'] && self::dbDistinct($sql_parts)) {
$schema = $this->getTableSchema();
$nclob_fields = [];
foreach ($schema['fields'] as $field_name => $field) {
if ($field['type'] == DB::FIELD_TYPE_NCLOB
&& $this->outputIsRequested($field_name, $options['output'])) {
$nclob_fields[] = $field_name;
}
}
if ($nclob_fields) {
$output = ($options['output'] === API_OUTPUT_EXTEND)
? array_keys($schema['fields'])
: $options['output'];
$options['output'] = array_diff($output, $nclob_fields);
}
}
return parent::applyQueryOutputOptions($table_name, $table_alias, $options, $sql_parts);
}
/**
* Add NCLOB type fields if there was DISTINCT in query.
*
* @param array $options Array of query options.
* @param array $result Query results.
*
* @return array The result array with added NCLOB fields.
*/
protected function addNclobFieldValues(array $options, array $result): array {
$schema = $this->getTableSchema();
$nclob_fields = [];
foreach ($schema['fields'] as $field_name => $field) {
if ($field['type'] == DB::FIELD_TYPE_NCLOB && $this->outputIsRequested($field_name, $options['output'])) {
$nclob_fields[] = $field_name;
}
}
if (!$nclob_fields) {
return $result;
}
$pk = $schema['key'];
$options = [
'output' => $nclob_fields,
'filter' => [$pk => array_keys($result)]
];
$db_items = DBselect(DB::makeSql($this->tableName, $options));
while ($db_item = DBfetch($db_items)) {
$result[$db_item[$pk]] += $db_item;
}
return $result;
}
/**
* Check that valuemap belong to same host as item.
*
* @param array $items
* @param array|null $db_items
*
* @throws APIException
*/
protected static function checkValueMaps(array $items, array $db_items = null): void {
$item_indexes = [];
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;
}
}
if (!$item_indexes) {
return;
}
$options = [
'output' => ['valuemapid', 'hostid'],
'valuemapids' => array_keys($item_indexes)
];
$db_valuemaps = DBselect(DB::makeSql('valuemap', $options));
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);
}
/**
* @param array $items
* @param array $db_items
*/
protected static function addAffectedTags(array $items, array &$db_items): void {
$itemids = [];
foreach ($items as $item) {
if (array_key_exists('tags', $item)) {
$itemids[] = $item['itemid'];
$db_items[$item['itemid']]['tags'] = [];
}
}
if (!$itemids) {
return;
}
$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']));
}
}
/**
* @param array $items
* @param array $db_items
*/
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 (!$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']));
}
}
/**
* @param array $items
* @param array $db_items
*/
protected static function addAffectedParameters(array $items, array &$db_items): void {
$itemids = [];
foreach ($items as $item) {
$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'] = [];
}
}
if (!$itemids) {
return;
}
$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)) {
if (!array_key_exists($row['itemid'], $db_items)) {
$templateids[] = $row['itemid'];
$db_items[$row['itemid']] = $row;
}
}
} while ($templateids);
}
/**
* 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]
]);
DB::update('graphs', [
'values' => [
'ymax_type' => GRAPH_YAXIS_TYPE_CALCULATED,
'ymax_itemid' => null
],
'where' => ['ymax_itemid' => $del_itemids]
]);
}
/**
* Delete triggers and trigger prototypes, which contain the given items in the expression.
*
* @param array $del_itemids
*/
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'];
}
}
if ($del_triggerids) {
CTriggerManager::delete($del_triggerids);
}
if ($del_trigger_prototypeids) {
CTriggerPrototypeManager::delete($del_trigger_prototypeids);
}
}
}