diff options
Diffstat (limited to 'ui/include/classes/api/services/CItemPrototype.php')
-rw-r--r-- | ui/include/classes/api/services/CItemPrototype.php | 1298 |
1 files changed, 1016 insertions, 282 deletions
diff --git a/ui/include/classes/api/services/CItemPrototype.php b/ui/include/classes/api/services/CItemPrototype.php index 8376a8e4eee..cd522aadb76 100644 --- a/ui/include/classes/api/services/CItemPrototype.php +++ b/ui/include/classes/api/services/CItemPrototype.php @@ -29,39 +29,60 @@ class CItemPrototype extends CItemGeneral { protected $sortColumns = ['itemid', 'name', 'key_', 'delay', 'history', 'trends', 'type', 'status', 'discover']; /** - * Define a set of supported pre-processing rules. - * - * @var array + * @inheritDoc + */ + public const SUPPORTED_PREPROCESSING_TYPES = [ + ZBX_PREPROC_MULTIPLIER, ZBX_PREPROC_RTRIM, ZBX_PREPROC_LTRIM, ZBX_PREPROC_TRIM, 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_THROTTLE_VALUE, + 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_VALIDATE_NOT_SUPPORTED, ZBX_PREPROC_XML_TO_JSON + ]; + + /** + * @inheritDoc + */ + 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 + ]; + + /** + * @inheritDoc */ - const SUPPORTED_PREPROCESSING_TYPES = [ZBX_PREPROC_REGSUB, ZBX_PREPROC_TRIM, ZBX_PREPROC_RTRIM, - ZBX_PREPROC_LTRIM, ZBX_PREPROC_XPATH, ZBX_PREPROC_JSONPATH, ZBX_PREPROC_MULTIPLIER, ZBX_PREPROC_DELTA_VALUE, - ZBX_PREPROC_DELTA_SPEED, ZBX_PREPROC_BOOL2DEC, ZBX_PREPROC_OCT2DEC, ZBX_PREPROC_HEX2DEC, + 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_THROTTLE_VALUE, 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_VALIDATE_NOT_SUPPORTED, ZBX_PREPROC_XML_TO_JSON + ZBX_PREPROC_VALIDATE_NOT_SUPPORTED, ZBX_PREPROC_XML_TO_JSON ]; - public function __construct() { - parent::__construct(); - - $this->errorMessages = array_merge($this->errorMessages, [ - self::ERROR_EXISTS_TEMPLATE => _('Item prototype "%1$s" already exists on "%2$s", inherited from another template.'), - self::ERROR_EXISTS => _('Item prototype "%1$s" already exists on "%2$s".'), - self::ERROR_INVALID_KEY => _('Invalid key "%1$s" for item prototype "%2$s" on "%3$s": %4$s.') - ]); - } + /** + * @inheritDoc + */ + protected const SUPPORTED_ITEM_TYPES = [ + ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, 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_SNMPTRAP, ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT + ]; /** - * Define a set of supported item types. - * - * @var array + * @inheritDoc */ - const SUPPORTED_ITEM_TYPES = [ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, 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_SNMPTRAP, ITEM_TYPE_DEPENDENT, - ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT + protected const VALUE_TYPE_FIELD_NAMES = [ + ITEM_VALUE_TYPE_FLOAT => ['units', 'trends', 'valuemapid'], + ITEM_VALUE_TYPE_STR => ['valuemapid'], + ITEM_VALUE_TYPE_LOG => ['logtimefmt'], + ITEM_VALUE_TYPE_UINT64 => ['units', 'trends', 'valuemapid'], + ITEM_VALUE_TYPE_TEXT => [] ]; /** @@ -306,7 +327,7 @@ class CItemPrototype extends CItemGeneral { } if (array_key_exists('headers', $item)) { - $item['headers'] = $this->headersStringToArray($item['headers']); + $item['headers'] = self::headersStringToArray($item['headers']); } } unset($item); @@ -337,403 +358,1009 @@ class CItemPrototype extends CItemGeneral { } /** - * Check item prototype data and set flags field. + * @param array $items + * + * @return array + */ + public function create(array $items): array { + self::validateCreate($items); + + self::createForce($items); + self::inherit($items); + + return ['itemids' => array_column($items, 'itemid')]; + } + + /** + * @param array $items * - * @param array $items an array of items passed by reference - * @param bool $update + * @throws APIException + */ + protected static function validateCreate(array &$items): void { + $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE | API_ALLOW_UNEXPECTED, 'fields' => [ + 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED] + ]]; + + if (!CApiInputValidator::validate($api_input_rules, $items, '/', $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); + } + + self::checkHostsAndTemplates($items, $db_hosts, $db_templates); + self::addHostStatus($items, $db_hosts, $db_templates); + self::addFlags($items, ZBX_FLAG_DISCOVERY_PROTOTYPE); + + $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_ALLOW_UNEXPECTED, 'uniq' => [['uuid'], ['hostid', 'key_']], 'fields' => [ + 'host_status' => ['type' => API_ANY], + 'flags' => ['type' => API_ANY], + 'uuid' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'host_status', 'in' => implode(',', [HOST_STATUS_TEMPLATE])], 'type' => API_UUID], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'uuid')] + ]], + 'hostid' => ['type' => API_ANY], + 'ruleid' => ['type' => API_ID, 'flags' => API_REQUIRED], + 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('items', 'name')], + 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', self::SUPPORTED_ITEM_TYPES)], + 'key_' => ['type' => API_ITEM_KEY, 'flags' => API_REQUIRED | API_REQUIRED_LLD_MACRO, 'length' => DB::getFieldLength('items', 'key_')], + 'value_type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_TEXT])], + 'units' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'units')], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'units')] + ]], + 'history' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'history')], + 'trends' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_DAY, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'trends')], + ['else' => true, 'type' => API_TIME_UNIT, 'in' => '0', 'default' => 0] + ]], + 'valuemapid' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_UINT64])], 'type' => API_ID], + ['else' => true, 'type' => API_ID, 'in' => '0'] + ]], + 'logtimefmt' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => ITEM_VALUE_TYPE_LOG], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'logtimefmt')], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'logtimefmt')] + ]], + 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'description')], + 'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])], + 'discover' => ['type' => API_INT32, 'in' => implode(',', [ITEM_DISCOVER, ITEM_NO_DISCOVER])], + 'tags' => self::getTagsValidationRules(), + 'preprocessing' => self::getPreprocessingValidationRules() + ]]; + + if (!CApiInputValidator::validate($api_input_rules, $items, '/', $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); + } + + self::validateByType(array_keys($api_input_rules['fields']), $items); + + self::checkAndAddUuid($items); + self::checkDuplicates($items); + self::checkDiscoveryRules($items); + self::checkValueMaps($items); + self::checkHostInterfaces($items); + self::checkDependentItems($items); + } + + /** + * @param array $items */ - protected function checkInput(array &$items, $update = false) { - parent::checkInput($items, $update); + public static function createForce(array &$items): void { + $itemids = DB::insert('items', $items); + + $ins_items_discovery = []; + $host_statuses = []; + $flags = []; - // set proper flags to divide normal and discovered items in future processing foreach ($items as &$item) { - $item['flags'] = ZBX_FLAG_DISCOVERY_PROTOTYPE; + $item['itemid'] = array_shift($itemids); + + if ($item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) { + $ins_items_discovery[] = [ + 'itemid' => $item['itemid'], + 'parent_itemid' => $item['ruleid'] + ]; + } + + $host_statuses[] = $item['host_status']; + $flags[] = $item['flags']; + unset($item['host_status'], $item['flags']); + } + unset($item); + + if ($ins_items_discovery) { + DB::insertBatch('item_discovery', $ins_items_discovery); + } + + self::updateParameters($items); + self::updatePreprocessing($items); + self::updateTags($items); + + self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_ITEM_PROTOTYPE, $items); + + foreach ($items as &$item) { + $item['host_status'] = array_shift($host_statuses); + $item['flags'] = array_shift($flags); } unset($item); } /** - * Create item prototype. - * * @param array $items * * @return array */ - public function create($items) { - $items = zbx_toArray($items); + public function update(array $items): array { + $this->validateUpdate($items, $db_items); - $this->checkInput($items); + $itemids = array_column($items, 'itemid'); - foreach ($items as $key => $item) { - $items[$key]['flags'] = ZBX_FLAG_DISCOVERY_PROTOTYPE; - unset($items[$key]['itemid']); - } + self::updateForce($items, $db_items); + self::inherit($items, $db_items); - // Validate item prototype status and discover status fields. - $api_input_rules = ['type' => API_OBJECT, 'fields' => [ - 'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])], - 'discover' => ['type' => API_INT32, 'in' => implode(',', [ITEM_DISCOVER, ITEM_NO_DISCOVER])] + return ['itemids' => $itemids]; + } + + /** + * @param array $items + * @param array|null $db_items + * + * @throws APIException + */ + protected function validateUpdate(array &$items, ?array &$db_items): void { + $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE | API_ALLOW_UNEXPECTED, 'uniq' => [['itemid']], 'fields' => [ + 'itemid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]]; - foreach ($items as $key => $item) { - $item = array_intersect_key($item, $api_input_rules['fields']); + if (!CApiInputValidator::validate($api_input_rules, $items, '/', $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); + } - if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($key + 1), $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); - } + $count = $this->get([ + 'countOutput' => true, + 'itemids' => array_column($items, 'itemid'), + 'editable' => true + ]); + + if ($count != count($items)) { + self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } - $this->validateDependentItems($items); + /* + * The fields "headers" and "query_fields" in API are arrays, but there is necessary to get the values of these + * fields as they stored in database. + */ + $db_items = DB::select('items', [ + 'output' => array_merge(['itemid', 'name', 'type', 'key_', 'value_type', 'units', 'history', 'trends', + 'valuemapid', 'logtimefmt', 'description', 'status', 'discover' + ], array_diff(CItemType::FIELD_NAMES, ['parameters'])), + 'itemids' => array_column($items, 'itemid'), + 'preservekeys' => true + ]); - foreach ($items as &$item) { - if ($item['type'] == ITEM_TYPE_HTTPAGENT) { - if (array_key_exists('query_fields', $item)) { - $item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : ''; - } + self::addInternalFields($db_items); - if (array_key_exists('headers', $item)) { - $item['headers'] = $this->headersArrayToString($item['headers']); - } + foreach ($items as $i => &$item) { + $db_item = $db_items[$item['itemid']]; + + if ($db_item['templateid'] != 0) { + $api_input_rules = ['type' => API_OBJECT, 'flags' => API_ALLOW_UNEXPECTED, 'fields' => [ + 'value_type' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED] + ]]; - if (array_key_exists('request_method', $item) && $item['request_method'] == HTTPCHECK_REQUEST_HEAD - && !array_key_exists('retrieve_mode', $item)) { - $item['retrieve_mode'] = HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; + if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($i + 1), $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); } + + $item += array_intersect_key($db_item, array_flip(['value_type'])); + + $api_input_rules = self::getInheritedValidationRules(); } else { - $item['query_fields'] = ''; - $item['headers'] = ''; + $item += array_intersect_key($db_item, array_flip(['value_type'])); + + $api_input_rules = self::getValidationRules(); } - if (array_key_exists('preprocessing', $item)) { - $item['preprocessing'] = $this->normalizeItemPreprocessingSteps($item['preprocessing']); + if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($i + 1), $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); } } unset($item); - $this->createReal($items); - $this->inherit($items); + $items = $this->extendObjectsByKey($items, $db_items, 'itemid', ['type', 'key_']); + + self::validateByType(array_keys($api_input_rules['fields']), $items, $db_items); + + $items = $this->extendObjectsByKey($items, $db_items, 'itemid', ['hostid', 'host_status', 'flags', 'ruleid']); + + self::validateUniqueness($items); + + self::addAffectedObjects($items, $db_items); - return ['itemids' => zbx_objectValues($items, 'itemid')]; + self::checkDuplicates($items, $db_items); + self::checkValueMaps($items, $db_items); + self::checkHostInterfaces($items, $db_items); + self::checkDependentItems($items, $db_items); } - protected function createReal(&$items) { - foreach ($items as &$item) { - if ($item['type'] != ITEM_TYPE_DEPENDENT) { - $item['master_itemid'] = null; + /** + * @inheritDoc + */ + public static function getPreprocessingValidationRules(int $flags = 0x00): array { + return parent::getPreprocessingValidationRules(API_ALLOW_LLD_MACRO); + } + + /** + * @return array + */ + private static function getValidationRules(): array { + return ['type' => API_OBJECT, 'flags' => API_ALLOW_UNEXPECTED, 'fields' => [ + 'itemid' => ['type' => API_ANY], + 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('items', 'name')], + 'type' => ['type' => API_INT32, 'in' => implode(',', self::SUPPORTED_ITEM_TYPES)], + 'key_' => ['type' => API_ITEM_KEY, 'flags' => API_REQUIRED_LLD_MACRO, 'length' => DB::getFieldLength('items', 'key_')], + 'value_type' => ['type' => API_INT32, 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_TEXT])], + 'units' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'units')], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'units')] + ]], + 'history' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'history')], + 'trends' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_DAY, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'trends')], + ['else' => true, 'type' => API_TIME_UNIT, 'in' => '0'] + ]], + 'valuemapid' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_UINT64])], 'type' => API_ID], + ['else' => true, 'type' => API_ID, 'in' => '0'] + ]], + 'logtimefmt' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => ITEM_VALUE_TYPE_LOG], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'logtimefmt')], + ['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'logtimefmt')] + ]], + 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'description')], + 'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])], + 'discover' => ['type' => API_INT32, 'in' => implode(',', [ITEM_DISCOVER, ITEM_NO_DISCOVER])], + 'tags' => self::getTagsValidationRules(), + 'preprocessing' => self::getPreprocessingValidationRules() + ]]; + } + + /** + * @return array + */ + private static function getInheritedValidationRules(): array { + return ['type' => API_OBJECT, 'flags' => API_ALLOW_UNEXPECTED, 'fields' => [ + 'itemid' => ['type' => API_ANY], + 'name' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED], + 'type' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED], + 'key_' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED], + 'value_type' => ['type' => API_ANY], + 'units' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED], + 'history' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'history')], + 'trends' => ['type' => API_MULTIPLE, 'rules' => [ + ['if' => ['field' => 'value_type', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO, 'in' => '0,'.implode(':', [SEC_PER_DAY, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'trends')], + ['else' => true, 'type' => API_TIME_UNIT, 'in' => '0'] + ]], + 'valuemapid' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED], + 'logtimefmt' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED], + 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'description')], + 'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])], + 'discover' => ['type' => API_INT32, 'in' => implode(',', [ITEM_DISCOVER, ITEM_NO_DISCOVER])], + 'tags' => self::getTagsValidationRules(), + 'preprocessing' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED] + ]]; + } + + /** + * @inheritDoc + */ + protected static function addInternalFields(array &$db_items): void { + $result = DBselect( + 'SELECT i.itemid,i.hostid,i.templateid,i.flags,h.status AS host_status,id.parent_itemid AS ruleid'. + ' FROM items i,hosts h,item_discovery id'. + ' WHERE i.hostid=h.hostid'. + ' AND i.itemid=id.itemid'. + ' AND '.dbConditionId('i.itemid', array_keys($db_items)) + ); + + while ($row = DBfetch($result)) { + $db_items[$row['itemid']] += $row; + } + } + + /** + * @param array $items + * @param array $db_items + */ + public static function updateForce(array &$items, array &$db_items): void { + // Helps to avoid deadlocks. + CArrayHelper::sort($items, ['itemid'], ZBX_SORT_DOWN); + + self::addFieldDefaultsByType($items, $db_items); + + $upd_items = []; + $upd_itemids = []; + + $internal_fields = array_flip(['itemid', 'type', 'key_', 'hostid', 'flags', 'host_status']); + $nested_object_fields = array_flip(['tags', 'preprocessing', 'parameters']); + + foreach ($items as $i => &$item) { + $upd_item = DB::getUpdatedValues('items', $item, $db_items[$item['itemid']]); + + if ($upd_item) { + $upd_items[] = [ + 'values' => $upd_item, + 'where' => ['itemid' => $item['itemid']] + ]; + + if (array_key_exists('type', $item) && $item['type'] == ITEM_TYPE_HTTPAGENT) { + $item = array_intersect_key($item, + array_flip(['authtype']) + $internal_fields + $upd_item + $nested_object_fields + ); + } + else { + $item = array_intersect_key($item, $internal_fields + $upd_item + $nested_object_fields); + } + + $upd_itemids[$i] = $item['itemid']; + } + else { + $item = array_intersect_key($item, $internal_fields + $nested_object_fields); } } unset($item); - $itemids = DB::insert('items', $items); - - $insertItemDiscovery = []; - foreach ($items as $key => $item) { - $items[$key]['itemid'] = $itemids[$key]; - $insertItemDiscovery[] = [ - 'itemid' => $items[$key]['itemid'], - 'parent_itemid' => $item['ruleid'] - ]; + if ($upd_items) { + DB::update('items', $upd_items); } - DB::insertBatch('item_discovery', $insertItemDiscovery); - $this->createItemParameters($items, $itemids); - $this->createItemPreprocessing($items); - $this->createItemTags($items); + self::updateTags($items, $db_items, $upd_itemids); + self::updatePreprocessing($items, $db_items, $upd_itemids); + self::updateParameters($items, $db_items, $upd_itemids); + self::updateDiscoveredItems($items, $db_items); + + $items = array_intersect_key($items, $upd_itemids); + $db_items = array_intersect_key($db_items, array_flip($upd_itemids)); + + self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_ITEM_PROTOTYPE, $items, $db_items); } - protected function updateReal(array $items) { - CArrayHelper::sort($items, ['itemid']); + /** + * @param array $items + * @param array $db_items + */ + private static function updateDiscoveredItems(array $item_prototypes, array $db_item_prototypes): void { + foreach ($item_prototypes as $i => $item_prototype) { + if (!in_array($item_prototype['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]) + || !array_key_exists('update_discovered_items', $db_item_prototypes[$item_prototype['itemid']])) { + unset($item_prototype[$i]); + continue; + } + } - $data = []; - foreach ($items as $item) { - $data[] = ['values' => $item, 'where'=> ['itemid' => $item['itemid']]]; + if (!$item_prototypes) { + return; } - $result = DB::update('items', $data); - if (!$result) { - self::exception(ZBX_API_ERROR_PARAMETERS, 'DBerror'); + $result = DBselect( + 'SELECT id.itemid,i.name,i.valuemapid'. + ' FROM item_discovery id,items i'. + ' WHERE id.itemid=i.itemid'. + ' AND '.dbConditionId('id.parent_itemid', array_column($item_prototypes, 'itemid')) + ); + + $items = []; + $db_items = []; + + while ($row = DBfetch($result)) { + $items[] = [ + 'itemid' => $row['itemid'], + 'valuemapid' => 0 + ]; + + $db_items[$row['itemid']] = $row; } - $this->updateItemParameters($items); - $this->updateItemPreprocessing($items); - $this->updateItemTags($items); + if ($items) { + CItem::updateForce($items, $db_items); + } } /** - * Update ItemPrototype. + * @param array $itemids * - * @param array $items + * @throws APIException * * @return array */ - public function update($items) { - $items = zbx_toArray($items); + public function delete(array $itemids): array { + $this->validateDelete($itemids, $db_items); - $this->checkInput($items, true); + self::deleteForce($db_items); - // Validate item prototype status and discover status fields. - $api_input_rules = ['type' => API_OBJECT, 'fields' => [ - 'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])], - 'discover' => ['type' => API_INT32, 'in' => implode(',', [ITEM_DISCOVER, ITEM_NO_DISCOVER])] - ]]; + return ['prototypeids' => $itemids]; + } - foreach ($items as $key => $item) { - $items[$key]['flags'] = ZBX_FLAG_DISCOVERY_PROTOTYPE; + /** + * @param array $itemids + * @param array|null $db_items + * + * @throws APIException + */ + private function validateDelete(array $itemids, ?array &$db_items): void { + $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; - $item = array_intersect_key($item, $api_input_rules['fields']); - if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($key + 1), $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); - } + if (!CApiInputValidator::validate($api_input_rules, $itemids, '/', $error)) { + self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_items = $this->get([ - 'output' => ['type', 'master_itemid', 'authtype', 'allow_traps', 'retrieve_mode', 'value_type'], - 'itemids' => zbx_objectValues($items, 'itemid'), + 'output' => ['itemid', 'name', 'templateid'], + 'itemids' => $itemids, 'editable' => true, 'preservekeys' => true ]); - $items = $this->extendFromObjects(zbx_toHash($items, 'itemid'), $db_items, ['type', 'authtype', - 'master_itemid', 'value_type' + if (count($db_items) != count($itemids)) { + self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); + } + + foreach ($itemids as $i => $itemid) { + if ($db_items[$itemid]['templateid'] != 0) { + self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.($i + 1), + _('cannot delete inherited item prototype') + )); + } + } + } + + /** + * @param array $templateids + * @param array $hostids + */ + public static function linkTemplateObjects(array $templateids, array $hostids): void { + $db_items = DB::select('items', [ + 'output' => array_merge(['itemid', 'name', 'type', 'key_', 'value_type', 'units', 'history', 'trends', + 'valuemapid', 'logtimefmt', 'description', 'status', 'discover' + ], array_diff(CItemType::FIELD_NAMES, ['interfaceid', 'parameters'])), + 'filter' => [ + 'flags' => ZBX_FLAG_DISCOVERY_PROTOTYPE, + 'hostid' => $templateids + ], + 'preservekeys' => true ]); - $this->validateDependentItems($items); - - $defaults = DB::getDefaults('items'); - $clean = [ - ITEM_TYPE_HTTPAGENT => [ - 'url' => '', - 'query_fields' => '', - 'timeout' => $defaults['timeout'], - 'status_codes' => $defaults['status_codes'], - 'follow_redirects' => $defaults['follow_redirects'], - 'request_method' => $defaults['request_method'], - 'allow_traps' => $defaults['allow_traps'], - 'post_type' => $defaults['post_type'], - 'http_proxy' => '', - 'headers' => '', - 'retrieve_mode' => $defaults['retrieve_mode'], - 'output_format' => $defaults['output_format'], - 'ssl_key_password' => '', - 'verify_peer' => $defaults['verify_peer'], - 'verify_host' => $defaults['verify_host'], - 'ssl_cert_file' => '', - 'ssl_key_file' => '', - 'posts' => '' - ] - ]; + if (!$db_items) { + return; + } + + self::addInternalFields($db_items); + + $items = []; + + foreach ($db_items as $db_item) { + $item = array_intersect_key($db_item, array_flip(['itemid', 'type'])); + + if ($db_item['type'] == ITEM_TYPE_SCRIPT) { + $item += ['parameters' => []]; + } + + $items[] = $item + [ + 'preprocessing' => [], + 'tags' => [] + ]; + } + + self::addAffectedObjects($items, $db_items); + + $items = array_values($db_items); foreach ($items as &$item) { - $type_change = ($item['type'] != $db_items[$item['itemid']]['type']); + if (array_key_exists('parameters', $item)) { + $item['parameters'] = array_values($item['parameters']); + } + + $item['preprocessing'] = array_values($item['preprocessing']); + $item['tags'] = array_values($item['tags']); + } + unset($item); - if ($item['type'] != ITEM_TYPE_DEPENDENT && $db_items[$item['itemid']]['master_itemid'] != 0) { - $item['master_itemid'] = 0; + self::inherit($items, [], $hostids); + } + + /** + * @inheritDoc + */ + protected static function inherit(array $items, array $db_items = [], array $hostids = null, + bool $is_dep_items = false): void { + $tpl_links = self::getTemplateLinks($items, $hostids); + + if ($hostids === null) { + self::filterObjectsToInherit($items, $db_items, $tpl_links); + + if (!$items) { + return; } + } - if ($type_change && $db_items[$item['itemid']]['type'] == ITEM_TYPE_HTTPAGENT) { - $item = array_merge($item, $clean[ITEM_TYPE_HTTPAGENT]); + self::checkDoubleInheritedNames($items, $db_items, $tpl_links); - if ($item['type'] != ITEM_TYPE_SSH) { - $item['authtype'] = $defaults['authtype']; - $item['username'] = ''; - $item['password'] = ''; - } + if ($hostids !== null && !$is_dep_items) { + $dep_items_to_link = []; + + $item_indexes = array_flip(array_column($items, 'itemid')); + + foreach ($items as $i => $item) { + if ($item['type'] == ITEM_TYPE_DEPENDENT + && array_key_exists($item['master_itemid'], $item_indexes)) { + $dep_items_to_link[$item_indexes[$item['master_itemid']]][$i] = $item; - if ($item['type'] != ITEM_TYPE_TRAPPER) { - $item['trapper_hosts'] = ''; + unset($items[$i]); } } + } - if ($item['type'] == ITEM_TYPE_HTTPAGENT) { - // Clean username and password when authtype is set to HTTPTEST_AUTH_NONE. - if ($item['authtype'] == HTTPTEST_AUTH_NONE) { - $item['username'] = ''; - $item['password'] = ''; - } + $chunks = self::getInheritChunks($items, $tpl_links); - if (array_key_exists('allow_traps', $item) && $item['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF - && $item['allow_traps'] != $db_items[$item['itemid']]['allow_traps']) { - $item['trapper_hosts'] = ''; - } + foreach ($chunks as $chunk) { + $_items = array_intersect_key($items, array_flip($chunk['item_indexes'])); + $_db_items = array_intersect_key($db_items, array_flip(array_column($_items, 'itemid'))); + $_hostids = array_keys($chunk['hosts']); - if (array_key_exists('query_fields', $item) && is_array($item['query_fields'])) { - $item['query_fields'] = $item['query_fields'] ? json_encode($item['query_fields']) : ''; - } + self::inheritChunk($_items, $_db_items, $tpl_links, $_hostids); + } - if (array_key_exists('headers', $item) && is_array($item['headers'])) { - $item['headers'] = $this->headersArrayToString($item['headers']); - } + if ($hostids !== null && !$is_dep_items) { + self::inheritDependentItems($dep_items_to_link, $items, $hostids); + } + } - if (array_key_exists('request_method', $item) && $item['request_method'] == HTTPCHECK_REQUEST_HEAD - && !array_key_exists('retrieve_mode', $item) - && $db_items[$item['itemid']]['retrieve_mode'] != HTTPTEST_STEP_RETRIEVE_MODE_HEADERS) { - $item['retrieve_mode'] = HTTPTEST_STEP_RETRIEVE_MODE_HEADERS; - } + /** + * @param array $items + * @param array $db_items + * @param array $tpl_links + * @param array $hostids + */ + protected static function inheritChunk(array $items, array $db_items, array $tpl_links, array $hostids): void { + $items_to_link = []; + $items_to_update = []; + + foreach ($items as $i => $item) { + if (!array_key_exists($item['itemid'], $db_items)) { + $items_to_link[] = $item; } else { - $item['query_fields'] = ''; - $item['headers'] = ''; + $items_to_update[] = $item; } - if ($type_change && $db_items[$item['itemid']]['type'] == ITEM_TYPE_SCRIPT) { - if ($item['type'] != ITEM_TYPE_SSH && $item['type'] != ITEM_TYPE_DB_MONITOR - && $item['type'] != ITEM_TYPE_TELNET && $item['type'] != ITEM_TYPE_CALCULATED) { - $item['params'] = ''; - } + unset($items[$i]); + } - if ($item['type'] != ITEM_TYPE_HTTPAGENT) { - $item['timeout'] = $defaults['timeout']; - } + $ins_items = []; + $upd_items = []; + $upd_db_items = []; + + if ($items_to_link) { + $lld_links = self::getLldLinks($items_to_link); + + $upd_db_items = self::getChildObjectsUsingName($items_to_link, $hostids, $lld_links); + + if ($upd_db_items) { + $upd_items = self::getUpdChildObjectsUsingName($items_to_link, $upd_db_items); } - if ($item['value_type'] == ITEM_VALUE_TYPE_LOG || $item['value_type'] == ITEM_VALUE_TYPE_TEXT) { - if ($item['value_type'] != $db_items[$item['itemid']]['value_type']) { - // Reset valuemapid when value_type is LOG or TEXT. - $item['valuemapid'] = 0; + $ins_items = self::getInsChildObjects($items_to_link, $upd_db_items, $tpl_links, $hostids, $lld_links); + } + + if ($items_to_update) { + $_upd_db_items = self::getChildObjectsUsingTemplateid($items_to_update, $db_items, $hostids); + $_upd_items = self::getUpdChildObjectsUsingTemplateid($items_to_update, $db_items, $_upd_db_items); + + self::checkDuplicates($_upd_items, $_upd_db_items); + + $upd_items = array_merge($upd_items, $_upd_items); + $upd_db_items += $_upd_db_items; + } + + self::setChildMasterItemIds($upd_items, $ins_items, $hostids); + + $edit_items = array_merge($upd_items, $ins_items); + + self::checkDependentItems($edit_items, $upd_db_items, true); + + self::addInterfaceIds($upd_items, $upd_db_items, $ins_items); + + if ($upd_items) { + self::updateForce($upd_items, $upd_db_items); + } + + if ($ins_items) { + self::createForce($ins_items); + } + + self::inherit(array_merge($upd_items, $ins_items), $upd_db_items); + } + + /** + * @param array $items + * @param array $db_items + * @param array $hostids + * + * @return array + */ + private static function getChildObjectsUsingTemplateid(array $items, array $db_items, array $hostids): array { + $upd_db_items = DB::select('items', [ + 'output' => array_merge(['itemid', 'name', 'type', 'key_', 'value_type', 'units', 'history', 'trends', + 'valuemapid', 'logtimefmt', 'description', 'status', 'discover' + ], array_diff(CItemType::FIELD_NAMES, ['parameters'])), + 'filter' => [ + 'templateid' => array_keys($db_items), + 'hostid' => $hostids + ], + 'preservekeys' => true + ]); + + self::addInternalFields($upd_db_items); + + if ($upd_db_items) { + $parent_indexes = array_flip(array_column($items, 'itemid')); + $upd_items = []; + + foreach ($upd_db_items as &$upd_db_item) { + $item = $items[$parent_indexes[$upd_db_item['templateid']]]; + $db_item = $db_items[$upd_db_item['templateid']]; + + if (array_key_exists('update_discovered_items', $db_item)) { + $upd_db_item['update_discovered_items'] = true; } - } - if (array_key_exists('tags', $item)) { - $item['tags'] = array_map(function ($tag) { - return $tag + ['value' => '']; - }, $item['tags']); + $upd_item = [ + 'itemid' => $upd_db_item['itemid'], + 'type' => $item['type'] + ]; + + $upd_item += array_intersect_key([ + 'tags' => [], + 'preprocessing' => [], + 'parameters' => [] + ], $db_item); + + $upd_items[] = $upd_item; } + unset($upd_db_item); - if (array_key_exists('preprocessing', $item)) { - $item['preprocessing'] = $this->normalizeItemPreprocessingSteps($item['preprocessing']); + self::addAffectedObjects($upd_items, $upd_db_items); + } + + return $upd_db_items; + } + + /** + * @param array $items + * @param array $db_items + * @param array $upd_db_items + * + * @return array + */ + private static function getUpdChildObjectsUsingTemplateid(array $items, array $db_items, + array $upd_db_items): array { + + foreach ($items as &$item) { + if (!array_key_exists($item['itemid'], $db_items)) { + continue; } + + $item = self::unsetNestedObjectIds($item); } unset($item); - $this->updateReal($items); - $this->inherit($items); + $parent_indexes = array_flip(array_column($items, 'itemid')); + $upd_items = []; - return ['itemids' => zbx_objectValues($items, 'itemid')]; + foreach ($upd_db_items as $upd_db_item) { + $item = $items[$parent_indexes[$upd_db_item['templateid']]]; + + $upd_items[] = array_intersect_key($upd_db_item, + array_flip(['itemid', 'hostid', 'templateid', 'host_status', 'ruleid']) + ) + $item; + } + + return $upd_items; } /** - * Delete Item prototypes. - * - * @param array $itemids + * @param array $items + * @param array $tpl_links * * @return array */ - public function delete(array $itemids) { - $this->validateDelete($itemids, $db_items); + private static function getLldLinks(array $items): array { + $options = [ + 'output' => ['templateid', 'hostid', 'itemid'], + 'filter' => ['templateid' => array_unique(array_column($items, 'ruleid'))] + ]; + $result = DBselect(DB::makeSql('items', $options)); - CItemPrototypeManager::delete($itemids); + $lld_links = []; - $this->addAuditBulk(CAudit::ACTION_DELETE, CAudit::RESOURCE_ITEM_PROTOTYPE, $db_items); + while ($row = DBfetch($result)) { + $lld_links[$row['templateid']][$row['hostid']] = $row['itemid']; + } - return ['prototypeids' => $itemids]; + return $lld_links; } /** - * Validates the input parameters for the delete() method. + * @param array $items + * @param array $hostids + * @param array $lld_links * - * @param array $itemids [IN/OUT] - * @param array $db_items [OUT] + * @return array + */ + private static function getChildObjectsUsingName(array $items, array $hostids, array $lld_links): array { + $result = DBselect( + 'SELECT i.itemid,ht.hostid,i.key_,i.templateid,i.flags,h.status AS host_status,'. + 'ht.templateid AS parent_hostid,id.parent_itemid AS ruleid,'. + dbConditionCoalesce('id.parent_itemid', 0, 'ruleid'). + ' FROM hosts_templates ht'. + ' INNER JOIN items i ON ht.hostid=i.hostid'. + ' INNER JOIN hosts h ON ht.hostid=h.hostid'. + ' LEFT JOIN item_discovery id ON i.itemid=id.itemid'. + ' WHERE '.dbConditionId('ht.templateid', array_unique(array_column($items, 'hostid'))). + ' AND '.dbConditionString('i.key_', array_unique(array_column($items, 'key_'))). + ' AND '.dbConditionId('ht.hostid', $hostids) + ); + + $upd_db_items = []; + $parent_indexes = []; + + while ($row = DBfetch($result)) { + foreach ($items as $i => $item) { + if (bccomp($row['parent_hostid'], $item['hostid']) == 0 && $row['key_'] === $item['key_']) { + if ($row['flags'] == $item['flags'] && $row['templateid'] == 0 + && bccomp($row['ruleid'], $lld_links[$item['ruleid']][$row['hostid']]) == 0) { + $upd_db_items[$row['itemid']] = $row; + $parent_indexes[$row['itemid']] = $i; + } + else { + self::showObjectMismatchError($item, $row); + } + } + } + } + + if (!$upd_db_items) { + return []; + } + + $options = [ + 'output' => array_merge(['itemid', 'name', 'type', 'key_', 'value_type', 'units', 'history', 'trends', + 'valuemapid', 'logtimefmt', 'description', 'status', 'discover' + ], array_diff(CItemType::FIELD_NAMES, ['parameters'])), + 'itemids' => array_keys($upd_db_items) + ]; + $result = DBselect(DB::makeSql('items', $options)); + + while ($row = DBfetch($result)) { + $upd_db_items[$row['itemid']] = $row + $upd_db_items[$row['itemid']]; + } + + $upd_items = []; + + foreach ($upd_db_items as $upd_db_item) { + $item = $items[$parent_indexes[$upd_db_item['itemid']]]; + + $upd_items[] = [ + 'itemid' => $upd_db_item['itemid'], + 'type' => $item['type'], + 'tags' => [], + 'preprocessing' => [], + 'parameters' => [] + ]; + } + + self::addAffectedObjects($upd_items, $upd_db_items); + + return $upd_db_items; + } + + /** + * @param array $items + * @param array $upd_db_items * - * @throws APIException if the input is invalid. + * @return array */ - private function validateDelete(array &$itemids, array &$db_items = null) { - $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; - if (!CApiInputValidator::validate($api_input_rules, $itemids, '/', $error)) { - self::exception(ZBX_API_ERROR_PARAMETERS, $error); + private static function getUpdChildObjectsUsingName(array $items, array $upd_db_items): array { + $parent_indexes = []; + + foreach ($items as $i => &$item) { + $item = self::unsetNestedObjectIds($item); + $parent_indexes[$item['hostid']][$item['key_']] = $i; } + unset($item); - $db_items = $this->get([ - 'output' => ['itemid', 'name', 'templateid', 'flags'], - 'itemids' => $itemids, - 'editable' => true, + $upd_items = []; + + foreach ($upd_db_items as $upd_db_item) { + $item = $items[$parent_indexes[$upd_db_item['parent_hostid']][$upd_db_item['key_']]]; + + $upd_item = [ + 'itemid' => $upd_db_item['itemid'], + 'hostid' => $upd_db_item['hostid'], + 'templateid' => $item['itemid'], + 'host_status' => $upd_db_item['host_status'], + 'ruleid' => $upd_db_item['ruleid'] + ] + $item; + + $upd_item += [ + 'tags' => [], + 'preprocessing' => [], + 'parameters' => [] + ]; + + $upd_items[] = $upd_item; + } + + return $upd_items; + } + + /** + * @param array $item + * @param array $upd_db_item + * + * @throws APIException + */ + protected static function showObjectMismatchError(array $item, array $upd_db_item): void { + parent::showObjectMismatchError($item, $upd_db_item); + + $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 ]); - foreach ($itemids as $itemid) { - if (!array_key_exists($itemid, $db_items)) { - self::exception(ZBX_API_ERROR_PERMISSIONS, - _('No permissions to referred object or it does not exist!') - ); - } + $lld_rules = DB::select('items', [ + 'output' => ['name'], + 'itemids' => [$item['ruleid'], $upd_db_item['ruleid']], + 'preservekeys' => true + ]); - $db_item = $db_items[$itemid]; + $error = $target_is_host + ? _('Cannot inherit item prototype with key "%1$s" of template "%2$s" and LLD rule "%3$s" to host "%4$s", because an item prototype with the same key already belongs to LLD rule "%5$s".') + : _('Cannot inherit item prototype with key "%1$s" of template "%2$s" and LLD rule "%3$s" to template "%4$s", because an item prototype with the same key already belongs to LLD rule "%5$s".'); - if ($db_item['templateid'] != 0) { - self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete templated item prototype.')); - } - } + self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $upd_db_item['key_'], $hosts[$item['hostid']]['host'], + $lld_rules[$item['ruleid']]['name'], $hosts[$upd_db_item['hostid']]['host'], + $lld_rules[$upd_db_item['ruleid']]['name'] + )); } - public function syncTemplates($data) { - $data['templateids'] = zbx_toArray($data['templateids']); - $data['hostids'] = zbx_toArray($data['hostids']); + /** + * @param array $items + * @param array $upd_db_items + * @param array $tpl_links + * @param array $hostids + * @param array $lld_links + * + * @return array + */ + private static function getInsChildObjects(array $items, array $upd_db_items, array $tpl_links, array $hostids, + array $lld_links): array { + $ins_items = []; + + $upd_item_keys = []; - $output = []; - foreach ($this->fieldRules as $field_name => $rules) { - if (!array_key_exists('system', $rules) && !array_key_exists('host', $rules)) { - $output[] = $field_name; + foreach ($upd_db_items as $upd_db_item) { + $upd_item_keys[$upd_db_item['hostid']][] = $upd_db_item['key_']; + } + + foreach ($items as $item) { + $item['uuid'] = ''; + $item = self::unsetNestedObjectIds($item); + + foreach ($tpl_links[$item['hostid']] as $host) { + if (!in_array($host['hostid'], $hostids) + || (array_key_exists($host['hostid'], $upd_item_keys) + && in_array($item['key_'], $upd_item_keys[$host['hostid']]))) { + continue; + } + + $ins_items[] = [ + 'hostid' => $host['hostid'], + 'templateid' => $item['itemid'], + 'host_status' => $host['status'], + 'ruleid' => $lld_links[$item['ruleid']][$host['hostid']] + ] + array_diff_key($item, array_flip(['itemid'])); } } - $tpl_items = $this->get([ - 'output' => $output, - 'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'], - 'selectTags' => ['tag', 'value'], - 'hostids' => $data['templateids'], - 'preservekeys' => true, - 'nopermissions' => true - ]); + return $ins_items; + } - foreach ($tpl_items as &$tpl_item) { - if ($tpl_item['type'] == ITEM_TYPE_HTTPAGENT) { - if (array_key_exists('query_fields', $tpl_item) && is_array($tpl_item['query_fields'])) { - $tpl_item['query_fields'] = $tpl_item['query_fields'] - ? json_encode($tpl_item['query_fields']) - : ''; - } + /** + * @param array $ruleids + */ + public static function unlinkTemplateObjects(array $ruleids): void { + $result = DBselect( + 'SELECT id.itemid,i.name,i.type,i.key_,i.templateid,i.uuid,i.valuemapid,i.hostid,h.status AS host_status'. + ' FROM item_discovery id,items i,hosts h'. + ' WHERE id.itemid=i.itemid'. + ' AND i.hostid=h.hostid'. + ' AND '.dbConditionId('id.parent_itemid', $ruleids). + ' AND '.dbConditionId('i.templateid', [0], true) + ); + + $items = []; + $db_items = []; + $i = 0; + $tpl_itemids = []; + + while ($row = DBfetch($result)) { + $item = [ + 'itemid' => $row['itemid'], + 'type' => $row['type'], + 'templateid' => 0, + 'host_status' => $row['host_status'] + ]; - if (array_key_exists('headers', $tpl_item) && is_array($tpl_item['headers'])) { - $tpl_item['headers'] = $this->headersArrayToString($tpl_item['headers']); - } + if ($row['host_status'] == HOST_STATUS_TEMPLATE) { + $item += ['uuid' => generateUuidV4()]; } - else { - $tpl_item['query_fields'] = ''; - $tpl_item['headers'] = ''; + + if ($row['valuemapid'] != 0) { + $item += ['valuemapid' => 0]; + $row['update_discovered_items'] = true; + + if ($row['host_status'] == HOST_STATUS_TEMPLATE) { + $tpl_itemids[$i] = $row['itemid']; + $item += array_intersect_key($row, array_flip(['key_', 'hostid'])); + } } + + $items[$i++] = $item; + $db_items[$row['itemid']] = $row; } - unset($tpl_item); - $this->inherit($tpl_items, $data['hostids']); + if ($items) { + self::updateForce($items, $db_items); + + if ($tpl_itemids) { + $items = array_intersect_key($items, $tpl_itemids); + $db_items = array_intersect_key($db_items, array_flip($tpl_itemids)); - return true; + self::inherit($items, $db_items); + } + } } /** - * Check item prototype specific fields: - * - validate history and trends using simple interval parser, user macro parser and lld macro parser; - * - validate item preprocessing. + * Check that discovery rule IDs of given items are valid. * - * @param array $item An array of single item data. - * @param string $method A string of "create" or "update" method. + * @param array $items * - * @throws APIException if the input is invalid. - */ - protected function checkSpecificFields(array $item, $method) { - if (array_key_exists('history', $item) - && !validateTimeUnit($item['history'], SEC_PER_HOUR, 25 * SEC_PER_YEAR, true, $error, - ['usermacros' => true, 'lldmacros' => true])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'history', $error) - ); + * @throws APIException + */ + private static function checkDiscoveryRules(array $items): void { + $ruleids = array_unique(array_column($items, 'ruleid')); + + $db_discovery_rules = DB::select('items', [ + 'output' => ['hostid'], + 'filter' => [ + 'flags' => ZBX_FLAG_DISCOVERY_RULE, + 'itemid' => $ruleids + ], + 'preservekeys' => true + ]); + + if (count($db_discovery_rules) != count($ruleids)) { + self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); } - if (array_key_exists('trends', $item) - && !validateTimeUnit($item['trends'], SEC_PER_DAY, 25 * SEC_PER_YEAR, true, $error, - ['usermacros' => true, 'lldmacros' => true])) { - self::exception(ZBX_API_ERROR_PARAMETERS, - _s('Incorrect value for field "%1$s": %2$s.', 'trends', $error) - ); + foreach ($items as $item) { + if (bccomp($db_discovery_rules[$item['ruleid']]['hostid'], $item['hostid']) != 0) { + self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); + } } } @@ -753,7 +1380,7 @@ class CItemPrototype extends CItemGeneral { return $sqlParts; } - public function addRelatedObjects(array $options, array $result) { + protected function addRelatedObjects(array $options, array $result) { $result = parent::addRelatedObjects($options, $result); $itemids = array_keys($result); @@ -870,4 +1497,111 @@ class CItemPrototype extends CItemGeneral { return $result; } + + /** + * @param array $db_items + */ + public static function deleteForce(array $db_items): void { + self::addInheritedItems($db_items); + self::addDependentItems($db_items); + + $del_itemids = array_keys($db_items); + + // Lock item prototypes before delete to prevent server from adding new LLD elements. + DBselect( + 'SELECT NULL'. + ' FROM items i'. + ' WHERE '.dbConditionInt('i.itemid', $del_itemids). + ' FOR UPDATE' + ); + + self::deleteAffectedGraphPrototypes($del_itemids); + self::resetGraphsYAxis($del_itemids); + + self::deleteDiscoveredItems($del_itemids); + + self::deleteAffectedTriggers($del_itemids); + + DB::delete('graphs_items', ['itemid' => $del_itemids]); + DB::delete('widget_field', ['value_itemid' => $del_itemids]); + DB::delete('item_discovery', ['itemid' => $del_itemids]); + DB::delete('item_parameter', ['itemid' => $del_itemids]); + DB::delete('item_preproc', ['itemid' => $del_itemids]); + DB::delete('item_tag', ['itemid' => $del_itemids]); + DB::update('items', [ + 'values' => ['templateid' => 0, 'master_itemid' => 0], + 'where' => ['itemid' => $del_itemids] + ]); + DB::delete('items', ['itemid' => $del_itemids]); + + self::addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_ITEM_PROTOTYPE, $db_items); + } + + /** + * Add the dependent item prototypes of the given items to the given item prototypes array. + * + * @param array $db_items + */ + protected static function addDependentItems(array &$db_items): void { + $master_itemids = array_keys($db_items); + + do { + $options = [ + 'output' => ['itemid', 'name'], + 'filter' => ['master_itemid' => $master_itemids] + ]; + $result = DBselect(DB::makeSql('items', $options)); + + $master_itemids = []; + + while ($row = DBfetch($result)) { + $master_itemids[] = $row['itemid']; + + $db_items[$row['itemid']] = $row; + } + } while ($master_itemids); + } + + /** + * Delete graph prototypes, which would remain without item prototypes after the given item prototypes deletion. + * + * @param array $del_itemids + */ + private static function deleteAffectedGraphPrototypes(array $del_itemids): void { + $del_graphids = DBfetchColumn(DBselect( + 'SELECT DISTINCT gi.graphid'. + ' FROM graphs_items gi'. + ' WHERE '.dbConditionId('gi.itemid', $del_itemids). + ' AND NOT EXISTS ('. + 'SELECT NULL'. + ' FROM graphs_items gii,items i'. + ' WHERE gi.graphid=gii.graphid'. + ' AND gii.itemid=i.itemid'. + ' AND '.dbConditionId('gii.itemid', $del_itemids, true). + ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]). + ')' + ), 'graphid'); + + if ($del_graphids) { + CGraphPrototypeManager::delete($del_graphids); + } + } + + /** + * Delete discovered items of the given item prototypes. + * + * @param array $del_itemids + */ + private static function deleteDiscoveredItems(array $del_itemids): void { + $db_items = DBfetchArrayAssoc(DBselect( + 'SELECT id.itemid,i.name'. + ' FROM item_discovery id,items i'. + ' WHERE id.itemid=i.itemid'. + ' AND '.dbConditionId('id.parent_itemid', $del_itemids) + ), 'itemid'); + + if ($db_items) { + CItem::deleteForce($db_items); + } + } } |