1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * Class containing methods for operations with item general.
24 */
25abstract class CItemGeneral extends CApiService {
26
27	const ERROR_EXISTS_TEMPLATE = 'existsTemplate';
28	const ERROR_EXISTS = 'exists';
29	const ERROR_NO_INTERFACE = 'noInterface';
30	const ERROR_INVALID_KEY = 'invalidKey';
31
32	protected $fieldRules;
33
34	/**
35	 * @abstract
36	 *
37	 * @param array $options
38	 *
39	 * @return array
40	 */
41	abstract public function get($options = []);
42
43	public function __construct() {
44		parent::__construct();
45
46		// template - if templated item, value is taken from template item, cannot be changed on host
47		// system - values should not be updated
48		// host - value should be null for template items
49		$this->fieldRules = [
50			'type'					=> ['template' => 1],
51			'snmp_oid'				=> ['template' => 1],
52			'hostid'				=> [],
53			'name'					=> ['template' => 1],
54			'description'			=> [],
55			'key_'					=> ['template' => 1],
56			'master_itemid'			=> ['template' => 1],
57			'delay'					=> [],
58			'history'				=> [],
59			'trends'				=> [],
60			'status'				=> [],
61			'discover'				=> [],
62			'value_type'			=> ['template' => 1],
63			'trapper_hosts'			=> [],
64			'units'					=> ['template' => 1],
65			'formula'				=> ['template' => 1],
66			'error'					=> ['system' => 1],
67			'lastlogsize'			=> ['system' => 1],
68			'logtimefmt'			=> [],
69			'templateid'			=> ['system' => 1],
70			'valuemapid'			=> ['template' => 1],
71			'params'				=> [],
72			'ipmi_sensor'			=> ['template' => 1],
73			'authtype'				=> [],
74			'username'				=> [],
75			'password'				=> [],
76			'publickey'				=> [],
77			'privatekey'			=> [],
78			'mtime'					=> ['system' => 1],
79			'flags'					=> [],
80			'filter'				=> [],
81			'interfaceid'			=> ['host' => 1],
82			'inventory_link'		=> [],
83			'lifetime'				=> [],
84			'preprocessing'			=> ['template' => 1],
85			'overrides'				=> ['template' => 1],
86			'jmx_endpoint'			=> [],
87			'url'					=> ['template' => 1],
88			'timeout'				=> ['template' => 1],
89			'query_fields'			=> ['template' => 1],
90			'posts'					=> ['template' => 1],
91			'status_codes'			=> ['template' => 1],
92			'follow_redirects'		=> ['template' => 1],
93			'post_type'				=> ['template' => 1],
94			'http_proxy'			=> ['template' => 1],
95			'headers'				=> ['template' => 1],
96			'retrieve_mode'			=> ['template' => 1],
97			'request_method'		=> ['template' => 1],
98			'output_format'			=> ['template' => 1],
99			'allow_traps'			=> [],
100			'ssl_cert_file'			=> ['template' => 1],
101			'ssl_key_file'			=> ['template' => 1],
102			'ssl_key_password'		=> ['template' => 1],
103			'verify_peer'			=> ['template' => 1],
104			'verify_host'			=> ['template' => 1]
105		];
106
107		$this->errorMessages = array_merge($this->errorMessages, [
108			self::ERROR_NO_INTERFACE => _('Cannot find host interface on "%1$s" for item key "%2$s".')
109		]);
110	}
111
112	/**
113	 * Check items data.
114	 *
115	 * Any system field passed to the function will be unset.
116	 *
117	 * @throw APIException
118	 *
119	 * @param array $items passed by reference
120	 * @param bool  $update
121	 */
122	protected function checkInput(array &$items, $update = false) {
123		$api_input_rules = ['type' => API_OBJECT, 'fields' => [
124			'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', static::SUPPORTED_ITEM_TYPES)]
125		]];
126		if ($update) {
127			unset($api_input_rules['fields']['type']['flags']);
128		}
129
130		foreach ($items as $num => $item) {
131			$data = array_intersect_key($item, $api_input_rules['fields']);
132			if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($num + 1), $error)) {
133				self::exception(ZBX_API_ERROR_PARAMETERS, $error);
134			}
135		}
136
137		if ($update) {
138			$itemDbFields = ['itemid' => null];
139
140			$dbItemsFields = ['itemid', 'templateid'];
141			foreach ($this->fieldRules as $field => $rule) {
142				if (!isset($rule['system'])) {
143					$dbItemsFields[] = $field;
144				}
145			}
146
147			$dbItems = $this->get([
148				'output' => $dbItemsFields,
149				'itemids' => zbx_objectValues($items, 'itemid'),
150				'editable' => true,
151				'preservekeys' => true
152			]);
153
154			$dbHosts = API::Host()->get([
155				'output' => ['hostid', 'status', 'name'],
156				'hostids' => zbx_objectValues($dbItems, 'hostid'),
157				'templated_hosts' => true,
158				'editable' => true,
159				'selectApplications' => ['applicationid', 'flags'],
160				'preservekeys' => true
161			]);
162		}
163		else {
164			$itemDbFields = [
165				'name' => null,
166				'key_' => null,
167				'hostid' => null,
168				'type' => null,
169				'value_type' => null,
170				'delay' => null
171			];
172
173			$dbHosts = API::Host()->get([
174				'output' => ['hostid', 'status', 'name'],
175				'hostids' => zbx_objectValues($items, 'hostid'),
176				'templated_hosts' => true,
177				'editable' => true,
178				'selectApplications' => ['applicationid', 'flags'],
179				'preservekeys' => true
180			]);
181
182			$discovery_rules = [];
183
184			if ($this instanceof CItemPrototype) {
185				$itemDbFields['ruleid'] = null;
186				$druleids = zbx_objectValues($items, 'ruleid');
187
188				if ($druleids) {
189					$discovery_rules = API::DiscoveryRule()->get([
190						'output' => ['hostid'],
191						'itemids' => $druleids,
192						'preservekeys' => true
193					]);
194				}
195			}
196		}
197
198		// interfaces
199		$interfaces = API::HostInterface()->get([
200			'output' => ['interfaceid', 'hostid', 'type'],
201			'hostids' => zbx_objectValues($dbHosts, 'hostid'),
202			'nopermissions' => true,
203			'preservekeys' => true
204		]);
205
206		if ($update) {
207			$updateDiscoveredValidator = new CUpdateDiscoveredValidator([
208				'allowed' => ['itemid', 'status'],
209				'messageAllowedField' => _('Cannot update "%2$s" for a discovered item "%1$s".')
210			]);
211			foreach ($items as &$item) {
212				// check permissions
213				if (!array_key_exists($item['itemid'], $dbItems)) {
214					self::exception(ZBX_API_ERROR_PERMISSIONS,
215						_('No permissions to referred object or it does not exist!')
216					);
217				}
218
219				$dbItem = $dbItems[$item['itemid']];
220
221				if (array_key_exists('hostid', $item) && bccomp($dbItem['hostid'], $item['hostid']) != 0) {
222					self::exception(ZBX_API_ERROR_PARAMETERS,
223						_s('Incorrect value for field "%1$s": %2$s.', 'hostid', _('cannot be changed'))
224					);
225				}
226
227				$itemName = array_key_exists('name', $item) ? $item['name'] : $dbItem['name'];
228
229				// discovered fields, except status, cannot be updated
230				$updateDiscoveredValidator->setObjectName($itemName);
231				$this->checkPartialValidator($item, $updateDiscoveredValidator, $dbItem);
232
233				$item += [
234					'hostid' => $dbItem['hostid'],
235					'type' => $dbItem['type'],
236					'name' => $dbItem['name'],
237					'key_' => $dbItem['key_'],
238					'flags' => $dbItem['flags']
239				];
240			}
241			unset($item);
242		}
243
244		$item_key_parser = new CItemKey();
245		$ip_range_parser = new CIPRangeParser([
246			'v6' => ZBX_HAVE_IPV6,
247			'ranges' => false,
248			'usermacros' => true,
249			'macros' => [
250				'{HOST.HOST}', '{HOSTNAME}', '{HOST.NAME}', '{HOST.CONN}', '{HOST.IP}', '{IPADDRESS}', '{HOST.DNS}'
251			]
252		]);
253		$update_interval_parser = new CUpdateIntervalParser([
254			'usermacros' => true,
255			'lldmacros' => (get_class($this) === 'CItemPrototype')
256		]);
257
258		foreach ($items as $inum => &$item) {
259			$item = $this->clearValues($item);
260
261			$fullItem = $items[$inum];
262
263			if (!check_db_fields($itemDbFields, $item)) {
264				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
265			}
266
267			if ($update) {
268				$type = array_key_exists('type', $item) ? $item['type'] : $dbItems[$item['itemid']]['type'];
269
270				if ($type == ITEM_TYPE_HTTPAGENT) {
271					$this->validateHTTPCheck($fullItem, $dbItems[$item['itemid']]);
272				}
273
274				check_db_fields($dbItems[$item['itemid']], $fullItem);
275
276				$this->checkNoParameters(
277					$item,
278					['templateid', 'state', 'lastlogsize', 'mtime', 'error'],
279					_('Cannot update "%1$s" for item "%2$s".'),
280					$item['name']
281				);
282
283				// apply rules
284				foreach ($this->fieldRules as $field => $rules) {
285					if ((0 != $fullItem['templateid'] && isset($rules['template'])) || isset($rules['system'])) {
286						unset($item[$field]);
287
288						// For templated item and fields that should not be modified, use the value from DB.
289						if (array_key_exists($field, $dbItems[$item['itemid']])
290								&& array_key_exists($field, $fullItem)) {
291							$fullItem[$field] = $dbItems[$item['itemid']][$field];
292						}
293					}
294				}
295
296				if (!isset($item['key_'])) {
297					$item['key_'] = $fullItem['key_'];
298				}
299				if (!isset($item['hostid'])) {
300					$item['hostid'] = $fullItem['hostid'];
301				}
302
303				// if a templated item is being assigned to an interface with a different type, ignore it
304				$itemInterfaceType = itemTypeInterface($dbItems[$item['itemid']]['type']);
305				if ($fullItem['templateid'] && isset($item['interfaceid']) && isset($interfaces[$item['interfaceid']])
306						&& $itemInterfaceType !== INTERFACE_TYPE_ANY && $interfaces[$item['interfaceid']]['type'] != $itemInterfaceType) {
307
308					unset($item['interfaceid']);
309				}
310			}
311			else {
312				if ($fullItem['type'] == ITEM_TYPE_HTTPAGENT) {
313					$this->validateHTTPCheck($fullItem, []);
314				}
315
316				if (!isset($dbHosts[$item['hostid']])) {
317					self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
318				}
319
320				check_db_fields($itemDbFields, $fullItem);
321
322				$this->checkNoParameters(
323					$item,
324					['templateid', 'state'],
325					_('Cannot set "%1$s" for item "%2$s".'),
326					$item['name']
327				);
328
329				if ($this instanceof CItemPrototype && (!array_key_exists($fullItem['ruleid'], $discovery_rules)
330						|| $discovery_rules[$fullItem['ruleid']]['hostid'] != $fullItem['hostid'])) {
331					self::exception(ZBX_API_ERROR_PARAMETERS,
332						_('No permissions to referred object or it does not exist!')
333					);
334				}
335			}
336
337			if ($fullItem['type'] == ITEM_TYPE_CALCULATED) {
338				$api_input_rules = ['type' => API_OBJECT, 'fields' => [
339					'params' => ['type' => API_CALC_FORMULA, 'flags' => $this instanceof CItemPrototype ? API_ALLOW_LLD_MACRO : 0]
340				]];
341
342				$data = array_intersect_key($item, $api_input_rules['fields']);
343
344				if (!CApiInputValidator::validate($api_input_rules, $data, '/'.($inum + 1), $error)) {
345					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
346				}
347			}
348
349			$host = $dbHosts[$fullItem['hostid']];
350
351			// Validate update interval.
352			if (!in_array($fullItem['type'], [ITEM_TYPE_TRAPPER, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT])
353					&& !validateDelay($update_interval_parser, 'delay', $fullItem['delay'], $error)) {
354				self::exception(ZBX_API_ERROR_PARAMETERS, $error);
355			}
356
357			// For non-numeric types, whichever value was entered in trends field, is overwritten to zero.
358			if ($fullItem['value_type'] == ITEM_VALUE_TYPE_STR || $fullItem['value_type'] == ITEM_VALUE_TYPE_LOG
359					|| $fullItem['value_type'] == ITEM_VALUE_TYPE_TEXT) {
360				$item['trends'] = '0';
361			}
362
363			// check if the item requires an interface
364			if ($host['status'] == HOST_STATUS_TEMPLATE) {
365				unset($item['interfaceid']);
366			}
367			else {
368				$itemInterfaceType = itemTypeInterface($fullItem['type']);
369
370				if ($itemInterfaceType !== false) {
371					if (!array_key_exists('interfaceid', $fullItem) || !$fullItem['interfaceid']) {
372						self::exception(ZBX_API_ERROR_PARAMETERS, _('No interface found.'));
373					}
374					elseif (!isset($interfaces[$fullItem['interfaceid']]) || bccomp($interfaces[$fullItem['interfaceid']]['hostid'], $fullItem['hostid']) != 0) {
375						self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses host interface from non-parent host.'));
376					}
377					elseif ($itemInterfaceType !== INTERFACE_TYPE_ANY && $interfaces[$fullItem['interfaceid']]['type'] != $itemInterfaceType) {
378						self::exception(ZBX_API_ERROR_PARAMETERS, _('Item uses incorrect interface type.'));
379					}
380				}
381				// no interface required, just set it to null
382				else {
383					$item['interfaceid'] = 0;
384				}
385			}
386
387			// item key
388			if ($fullItem['type'] == ITEM_TYPE_DB_MONITOR) {
389				if (!isset($fullItem['flags']) || $fullItem['flags'] != ZBX_FLAG_DISCOVERY_RULE) {
390					if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR) == 0) {
391						self::exception(ZBX_API_ERROR_PARAMETERS,
392							_('Check the key, please. Default example was passed.')
393						);
394					}
395				}
396				elseif ($fullItem['flags'] == ZBX_FLAG_DISCOVERY_RULE) {
397					if (strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_DB_MONITOR_DISCOVERY) == 0) {
398						self::exception(ZBX_API_ERROR_PARAMETERS,
399							_('Check the key, please. Default example was passed.')
400						);
401					}
402				}
403			}
404			elseif (($fullItem['type'] == ITEM_TYPE_SSH && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_SSH) == 0)
405					|| ($fullItem['type'] == ITEM_TYPE_TELNET && strcmp($fullItem['key_'], ZBX_DEFAULT_KEY_TELNET) == 0)) {
406				self::exception(ZBX_API_ERROR_PARAMETERS, _('Check the key, please. Default example was passed.'));
407			}
408
409			// key
410			if ($item_key_parser->parse($fullItem['key_']) != CParser::PARSE_SUCCESS) {
411				self::exception(ZBX_API_ERROR_PARAMETERS,
412					_params($this->getErrorMsg(self::ERROR_INVALID_KEY), [
413						$fullItem['key_'], $fullItem['name'], $host['name'], $item_key_parser->getError()
414					])
415				);
416			}
417
418			// parameters
419			if ($fullItem['type'] == ITEM_TYPE_AGGREGATE) {
420				$params_num = $item_key_parser->getParamsNum();
421
422				if (!str_in_array($item_key_parser->getKey(), ['grpmax', 'grpmin', 'grpsum', 'grpavg'])
423						|| $params_num > 4 || $params_num < 3
424						|| ($params_num == 3 && $item_key_parser->getParam(2) !== 'last')
425						|| !str_in_array($item_key_parser->getParam(2), ['last', 'min', 'max', 'avg', 'sum', 'count'])) {
426					self::exception(ZBX_API_ERROR_PARAMETERS,
427						_s('Key "%1$s" does not match <grpmax|grpmin|grpsum|grpavg>["Host group(s)", "Item key",'.
428							' "<last|min|max|avg|sum|count>", "parameter"].', $item_key_parser->getKey()));
429				}
430			}
431
432			// type of information
433			if ($fullItem['type'] == ITEM_TYPE_AGGREGATE && $fullItem['value_type'] != ITEM_VALUE_TYPE_UINT64
434					&& $fullItem['value_type'] != ITEM_VALUE_TYPE_FLOAT) {
435					self::exception(ZBX_API_ERROR_PARAMETERS,
436						_('Type of information must be "Numeric (unsigned)" or "Numeric (float)" for aggregate items.'));
437			}
438
439			if (($fullItem['type'] == ITEM_TYPE_TRAPPER || $fullItem['type'] == ITEM_TYPE_HTTPAGENT)
440					&& array_key_exists('trapper_hosts', $fullItem) && $fullItem['trapper_hosts'] !== ''
441					&& !$ip_range_parser->parse($fullItem['trapper_hosts'])) {
442				self::exception(ZBX_API_ERROR_PARAMETERS,
443					_s('Incorrect value for field "%1$s": %2$s.', 'trapper_hosts', $ip_range_parser->getError())
444				);
445			}
446
447			// jmx
448			if ($fullItem['type'] == ITEM_TYPE_JMX) {
449				if (!array_key_exists('jmx_endpoint', $fullItem) && !$update) {
450					$item['jmx_endpoint'] = ZBX_DEFAULT_JMX_ENDPOINT;
451				}
452				if (array_key_exists('jmx_endpoint', $fullItem) && $fullItem['jmx_endpoint'] === '') {
453					self::exception(ZBX_API_ERROR_PARAMETERS,
454						_s('Incorrect value for field "%1$s": %2$s.', 'jmx_endpoint', _('cannot be empty'))
455					);
456				}
457
458				if (($fullItem['username'] === '') !== ($fullItem['password'] === '')) {
459					self::exception(ZBX_API_ERROR_PARAMETERS,
460						_s('Incorrect value for field "%1$s": %2$s.', 'username',
461								_('both username and password should be either present or empty'))
462					);
463				}
464			}
465			else {
466				if (array_key_exists('jmx_endpoint', $item) && $item['jmx_endpoint'] !== '') {
467					self::exception(ZBX_API_ERROR_PARAMETERS,
468						_s('Incorrect value for field "%1$s": %2$s.', 'jmx_endpoint', _('should be empty'))
469					);
470				}
471				elseif (array_key_exists('jmx_endpoint', $fullItem) && $fullItem['jmx_endpoint'] !== '') {
472					$item['jmx_endpoint'] = '';
473				}
474			}
475
476			// Dependent item.
477			if ($fullItem['type'] == ITEM_TYPE_DEPENDENT) {
478				if ($update) {
479					if (array_key_exists('master_itemid', $item) && !$item['master_itemid']) {
480						self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.',
481							'master_itemid', _('cannot be empty')
482						));
483					}
484					if ($dbItems[$fullItem['itemid']]['type'] != ITEM_TYPE_DEPENDENT
485							&& !array_key_exists('master_itemid', $item)) {
486						self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.',
487							'master_itemid', _('cannot be empty')
488						));
489					}
490				}
491				elseif (!array_key_exists('master_itemid', $item) || !$item['master_itemid']) {
492					self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Incorrect value for field "%1$s": %2$s.',
493						'master_itemid', _('cannot be empty')
494					));
495				}
496				if (array_key_exists('master_itemid', $item) && !is_int($item['master_itemid'])
497						&& !(is_string($item['master_itemid']) && ctype_digit($item['master_itemid']))) {
498					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value "%1$s" for "%2$s" field.',
499						$item['master_itemid'], 'master_itemid'
500					));
501				}
502			}
503			else {
504				if (array_key_exists('master_itemid', $item) && $item['master_itemid']) {
505					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
506						'master_itemid', _('should be empty')
507					));
508				}
509				$item['master_itemid'] = 0;
510			}
511
512			// ssh, telnet
513			if ($fullItem['type'] == ITEM_TYPE_SSH || $fullItem['type'] == ITEM_TYPE_TELNET) {
514				if ($fullItem['username'] === '') {
515					self::exception(ZBX_API_ERROR_PARAMETERS, _('No authentication user name specified.'));
516				}
517
518				if ($fullItem['type'] == ITEM_TYPE_SSH && $fullItem['authtype'] == ITEM_AUTHTYPE_PUBLICKEY) {
519					if ($fullItem['publickey'] === '') {
520						self::exception(ZBX_API_ERROR_PARAMETERS, _('No public key file specified.'));
521					}
522					if ($fullItem['privatekey'] === '') {
523						self::exception(ZBX_API_ERROR_PARAMETERS, _('No private key file specified.'));
524					}
525				}
526			}
527
528			// Prevent IPMI sensor field being empty if item key is not "ipmi.get".
529			if ($fullItem['type'] == ITEM_TYPE_IPMI && $fullItem['key_'] !== 'ipmi.get'
530					&& (!array_key_exists('ipmi_sensor', $fullItem) || $fullItem['ipmi_sensor'] === '')) {
531				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
532					'ipmi_sensor', _('cannot be empty')
533				));
534			}
535
536			// snmp trap
537			if ($fullItem['type'] == ITEM_TYPE_SNMPTRAP
538					&& $fullItem['key_'] !== 'snmptrap.fallback' && $item_key_parser->getKey() !== 'snmptrap') {
539				self::exception(ZBX_API_ERROR_PARAMETERS, _('SNMP trap key is invalid.'));
540			}
541
542			// snmp oid
543			if ($fullItem['type'] == ITEM_TYPE_SNMP
544					&& (!array_key_exists('snmp_oid', $fullItem) || $fullItem['snmp_oid'] === '')) {
545				self::exception(ZBX_API_ERROR_PARAMETERS, _('No SNMP OID specified.'));
546			}
547
548			if (isset($item['applications']) && $item['applications']) {
549				/*
550				 * 'flags' is available for update and item prototypes.
551				 * Don't allow discovered or any other application types for item prototypes in 'applications' option.
552				 */
553				if (array_key_exists('flags', $fullItem) && $fullItem['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
554					foreach ($host['applications'] as $num => $application) {
555						if ($application['flags'] != ZBX_FLAG_DISCOVERY_NORMAL) {
556							unset($host['applications'][$num]);
557						}
558					}
559				}
560
561				// check that the given applications belong to the item's host
562				$dbApplicationIds = zbx_objectValues($host['applications'], 'applicationid');
563				foreach ($item['applications'] as $appId) {
564					if (!in_array($appId, $dbApplicationIds)) {
565						$error = _s('Application with ID "%1$s" is not available on "%2$s".', $appId, $host['name']);
566						self::exception(ZBX_API_ERROR_PARAMETERS, $error);
567					}
568				}
569			}
570
571			$this->checkSpecificFields($fullItem, $update ? 'update' : 'create');
572
573			$this->validateItemPreprocessing($fullItem);
574		}
575		unset($item);
576
577		$this->checkExistingItems($items);
578	}
579
580	/**
581	 * Check item specific fields. Each API like Item, Itemprototype and Discovery rule may inherit different fields
582	 * to validate.
583	 *
584	 * @param array  $item    An array of single item data.
585	 * @param string $method  A string of "create" or "update" method.
586	 *
587	 * @return bool
588	 */
589	abstract protected function checkSpecificFields(array $item, $method);
590
591	protected function clearValues(array $item) {
592		if (isset($item['port']) && $item['port'] != '') {
593			$item['port'] = ltrim($item['port'], '0');
594			if ($item['port'] == '') {
595				$item['port'] = 0;
596			}
597		}
598
599		if (array_key_exists('type', $item) &&
600				($item['type'] == ITEM_TYPE_DEPENDENT || $item['type'] == ITEM_TYPE_TRAPPER)) {
601			$item['delay'] = 0;
602		}
603
604		return $item;
605	}
606
607	protected function errorInheritFlags($flag, $key, $host) {
608		switch ($flag) {
609			case ZBX_FLAG_DISCOVERY_NORMAL:
610				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item.', $key, $host));
611				break;
612			case ZBX_FLAG_DISCOVERY_RULE:
613				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as a discovery rule.', $key, $host));
614				break;
615			case ZBX_FLAG_DISCOVERY_PROTOTYPE:
616				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item prototype.', $key, $host));
617				break;
618			case ZBX_FLAG_DISCOVERY_CREATED:
619				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as an item created from item prototype.', $key, $host));
620				break;
621			default:
622				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with key "%1$s" already exists on "%2$s" as unknown item element.', $key, $host));
623		}
624	}
625
626	/**
627	 * Returns the interface that best matches the given item.
628	 *
629	 * @param array $item_type  An item type
630	 * @param array $interfaces An array of interfaces to choose from
631	 *
632	 * @return array|boolean    The best matching interface;
633	 *							an empty array of no matching interface was found;
634	 *							false, if the item does not need an interface
635	 */
636	public static function findInterfaceForItem($item_type, array $interfaces) {
637		$interface_by_type = [];
638		foreach ($interfaces as $interface) {
639			if ($interface['main'] == 1) {
640				$interface_by_type[$interface['type']] = $interface;
641			}
642		}
643
644		// find item interface type
645		$type = itemTypeInterface($item_type);
646
647		// the item can use any interface
648		if ($type == INTERFACE_TYPE_ANY) {
649			$interface_types = [INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI];
650			foreach ($interface_types as $interface_type) {
651				if (array_key_exists($interface_type, $interface_by_type)) {
652					return $interface_by_type[$interface_type];
653				}
654			}
655		}
656		// the item uses a specific type of interface
657		elseif ($type !== false) {
658			return array_key_exists($type, $interface_by_type) ? $interface_by_type[$type] : [];
659		}
660		// the item does not need an interface
661		else {
662			return false;
663		}
664	}
665
666	/**
667	 * Updates the children of the item on the given hosts and propagates the inheritance to the child hosts.
668	 *
669	 * @param array      $tpl_items  An array of items to inherit.
670	 * @param array|null $hostids    An array of hosts to inherit to; if set to null, the items will be inherited to all
671	 *                               linked hosts or templates.
672	 */
673	protected function inherit(array $tpl_items, array $hostids = null) {
674		$tpl_items = zbx_toHash($tpl_items, 'itemid');
675
676		// Inherit starting from common items and finishing up dependent.
677		while ($tpl_items) {
678			$_tpl_items = [];
679
680			foreach ($tpl_items as $tpl_item) {
681				if ($tpl_item['type'] != ITEM_TYPE_DEPENDENT
682						|| !array_key_exists($tpl_item['master_itemid'], $tpl_items)) {
683					$_tpl_items[$tpl_item['itemid']] = $tpl_item;
684				}
685			}
686
687			foreach ($_tpl_items as $itemid => $_tpl_item) {
688				unset($tpl_items[$itemid]);
689			}
690
691			$this->_inherit($_tpl_items, $hostids);
692		}
693	}
694
695	/**
696	 * Auxiliary method for item inheritance. See full description in inherit() method.
697	 */
698	private function _inherit(array $tpl_items, array $hostids = null) {
699		// Prepare the child items.
700		$new_items = $this->prepareInheritedItems($tpl_items, $hostids);
701		if (!$new_items) {
702			return;
703		}
704
705		$ins_items = [];
706		$upd_items = [];
707
708		foreach ($new_items as $new_item) {
709			if (array_key_exists('itemid', $new_item)) {
710				if ($this instanceof CItemPrototype) {
711					unset($new_item['ruleid']);
712				}
713				$upd_items[] = $new_item;
714			}
715			else {
716				$ins_items[] = $new_item;
717			}
718		}
719
720		$this->validateDependentItems($new_items);
721
722		// Save the new items.
723		if ($ins_items) {
724			if ($this instanceof CItem) {
725				static::validateInventoryLinks($ins_items, false);
726			}
727
728			$this->createReal($ins_items);
729		}
730
731		if ($upd_items) {
732			if ($this instanceof CItem) {
733				static::validateInventoryLinks($upd_items, true);
734			}
735
736			$this->updateReal($upd_items);
737		}
738
739		$new_items = array_merge($upd_items, $ins_items);
740
741		// Inheriting items from the templates.
742		$db_items = DBselect(
743			'SELECT i.itemid'.
744			' FROM items i,hosts h'.
745			' WHERE i.hostid=h.hostid'.
746				' AND '.dbConditionInt('i.itemid', zbx_objectValues($new_items, 'itemid')).
747				' AND '.dbConditionInt('h.status', [HOST_STATUS_TEMPLATE])
748		);
749
750		$tpl_itemids = [];
751		while ($db_item = DBfetch($db_items)) {
752			$tpl_itemids[$db_item['itemid']] = true;
753		}
754
755		foreach ($new_items as $index => $new_item) {
756			if (!array_key_exists($new_item['itemid'], $tpl_itemids)) {
757				unset($new_items[$index]);
758			}
759		}
760
761		$this->inherit($new_items);
762	}
763
764	/**
765	 * Prepares and returns an array of child items, inherited from items $tpl_items on the given hosts.
766	 *
767	 * @param array      $tpl_items
768	 * @param string     $tpl_items[<itemid>]['itemid']
769	 * @param string     $tpl_items[<itemid>]['hostid']
770	 * @param string     $tpl_items[<itemid>]['key_']
771	 * @param int        $tpl_items[<itemid>]['type']
772	 * @param array      $tpl_items[<itemid>]['applicationPrototypes']            (optional) Suitable for item
773	 *                                                                            prototypes.
774	 * @param string     $tpl_items[<itemid>]['applicationPrototypes'][]['name']
775	 * @param array      $tpl_items[<itemid>]['applications']                     (optional) Array of applicationids.
776	 * @param array      $tpl_items[<itemid>]['preprocessing']                    (optional)
777	 * @param int        $tpl_items[<itemid>]['preprocessing'][]['type']
778	 * @param string     $tpl_items[<itemid>]['preprocessing'][]['params']
779	 * @param int        $tpl_items[<itemid>]['flags']
780	 * @param string     $tpl_items[<itemid>]['master_itemid']                    (optional)
781	 * @param mixed      $tpl_items[<itemid>][<field_name>]                       (optional)
782	 * @param array|null $hostids
783	 *
784	 * @return array an array of unsaved child items
785	 */
786	private function prepareInheritedItems(array $tpl_items, array $hostids = null) {
787		$itemids_by_templateid = [];
788		foreach ($tpl_items as $tpl_item) {
789			$itemids_by_templateid[$tpl_item['hostid']][] = $tpl_item['itemid'];
790		}
791
792		// Fetch all child hosts.
793		$chd_hosts = API::Host()->get([
794			'output' => ['hostid', 'host', 'status'],
795			'selectParentTemplates' => ['templateid'],
796			'selectInterfaces' => ['interfaceid', 'main', 'type'],
797			'templateids' => array_keys($itemids_by_templateid),
798			'hostids' => $hostids,
799			'preservekeys' => true,
800			'nopermissions' => true,
801			'templated_hosts' => true
802		]);
803		if (!$chd_hosts) {
804			return [];
805		}
806
807		$chd_items_tpl = [];
808		$chd_items_key = [];
809
810		// Preparing list of items by item templateid.
811		$sql = 'SELECT i.itemid,i.hostid,i.type,i.key_,i.flags,i.templateid'.
812			' FROM items i'.
813			' WHERE '.dbConditionInt('i.templateid', zbx_objectValues($tpl_items, 'itemid'));
814		if ($hostids !== null) {
815			$sql .= ' AND '.dbConditionInt('i.hostid', $hostids);
816		}
817		$db_items = DBselect($sql);
818
819		while ($db_item = DBfetch($db_items)) {
820			$hostid = $db_item['hostid'];
821			unset($db_item['hostid']);
822
823			$chd_items_tpl[$hostid][$db_item['templateid']] = $db_item;
824		}
825
826		$hostids_by_key = [];
827
828		// Preparing list of items by item key.
829		foreach ($chd_hosts as $chd_host) {
830			$tpl_itemids = [];
831
832			foreach ($chd_host['parentTemplates'] as $parent_template) {
833				if (array_key_exists($parent_template['templateid'], $itemids_by_templateid)) {
834					$tpl_itemids = array_merge($tpl_itemids, $itemids_by_templateid[$parent_template['templateid']]);
835				}
836			}
837
838			foreach ($tpl_itemids as $tpl_itemid) {
839				if (!array_key_exists($chd_host['hostid'], $chd_items_tpl)
840						|| !array_key_exists($tpl_itemid, $chd_items_tpl[$chd_host['hostid']])) {
841					$hostids_by_key[$tpl_items[$tpl_itemid]['key_']][] = $chd_host['hostid'];
842				}
843			}
844		}
845
846		foreach ($hostids_by_key as $key_ => $key_hostids) {
847			$sql_select = ($this instanceof CItemPrototype) ? ',id.parent_itemid AS ruleid' : '';
848			// "LEFT JOIN" is needed to check flags on inherited and existing item, item prototype or lld rule.
849			// For example, when linking an item prototype with same key as in an item on target host or template.
850			$sql_join = ($this instanceof CItemPrototype) ? ' LEFT JOIN item_discovery id ON i.itemid=id.itemid' : '';
851			$db_items = DBselect(
852				'SELECT i.itemid,i.hostid,i.type,i.key_,i.flags,i.templateid'.$sql_select.
853					' FROM items i'.$sql_join.
854					' WHERE '.dbConditionInt('i.hostid', $key_hostids).
855						' AND '.dbConditionString('i.key_', [$key_])
856			);
857
858			while ($db_item = DBfetch($db_items)) {
859				$hostid = $db_item['hostid'];
860				unset($db_item['hostid']);
861
862				$chd_items_key[$hostid][$db_item['key_']] = $db_item;
863			}
864		}
865
866		// Preparing list of application prototypes.
867		if ($this instanceof CItemPrototype) {
868			$tpl_app_prototypes = [];
869			$item_prototypeids = [];
870
871			foreach ($tpl_items as $tpl_item) {
872				if (array_key_exists('applicationPrototypes', $tpl_item) && $tpl_item['applicationPrototypes']) {
873					$item_prototypeids[] = $tpl_item['itemid'];
874				}
875			}
876
877			if ($item_prototypeids) {
878				$db_tpl_app_prototypes = DBselect(
879					'SELECT iap.itemid,iap.application_prototypeid,ap.name'.
880					' FROM item_application_prototype iap,application_prototype ap'.
881					' WHERE iap.application_prototypeid=ap.application_prototypeid'.
882						' AND '.dbConditionInt('iap.itemid', $item_prototypeids)
883				);
884
885				while ($db_tpl_app_prototype = DBfetch($db_tpl_app_prototypes)) {
886					$tpl_app_prototypes[$db_tpl_app_prototype['itemid']][$db_tpl_app_prototype['name']] =
887						$db_tpl_app_prototype['application_prototypeid'];
888				}
889			}
890		}
891
892		// List of the discovery rules.
893		if ($this instanceof CItemPrototype) {
894			// List of itemids without 'ruleid' property.
895			$tpl_itemids = [];
896			$tpl_ruleids = [];
897			foreach ($tpl_items as $tpl_item) {
898				if (!array_key_exists('ruleid', $tpl_item)) {
899					$tpl_itemids[] = $tpl_item['itemid'];
900				}
901				else {
902					$tpl_ruleids[$tpl_item['ruleid']] = true;
903				}
904			}
905
906			if ($tpl_itemids) {
907				$db_rules = DBselect(
908					'SELECT id.parent_itemid,id.itemid'.
909						' FROM item_discovery id'.
910						' WHERE '.dbConditionInt('id.itemid', $tpl_itemids)
911				);
912
913				while ($db_rule = DBfetch($db_rules)) {
914					$tpl_items[$db_rule['itemid']]['ruleid'] = $db_rule['parent_itemid'];
915					$tpl_ruleids[$db_rule['parent_itemid']] = true;
916				}
917			}
918
919			$sql = 'SELECT i.hostid,i.templateid,i.itemid'.
920					' FROM items i'.
921					' WHERE '.dbConditionInt('i.templateid', array_keys($tpl_ruleids));
922			if ($hostids !== null) {
923				$sql .= ' AND '.dbConditionInt('i.hostid', $hostids);
924			}
925			$db_rules = DBselect($sql);
926
927			// List of child lld ruleids by child hostid and parent lld ruleid.
928			$chd_ruleids = [];
929			while ($db_rule = DBfetch($db_rules)) {
930				$chd_ruleids[$db_rule['hostid']][$db_rule['templateid']] = $db_rule['itemid'];
931			}
932		}
933
934		$new_items = [];
935		// List of the updated item keys by hostid.
936		$upd_hostids_by_key = [];
937
938		foreach ($chd_hosts as $chd_host) {
939			$tpl_itemids = [];
940
941			foreach ($chd_host['parentTemplates'] as $parent_template) {
942				if (array_key_exists($parent_template['templateid'], $itemids_by_templateid)) {
943					$tpl_itemids = array_merge($tpl_itemids, $itemids_by_templateid[$parent_template['templateid']]);
944				}
945			}
946
947			foreach ($tpl_itemids as $tpl_itemid) {
948				$tpl_item = $tpl_items[$tpl_itemid];
949
950				$chd_item = null;
951
952				// Update by templateid.
953				if (array_key_exists($chd_host['hostid'], $chd_items_tpl)
954						&& array_key_exists($tpl_item['itemid'], $chd_items_tpl[$chd_host['hostid']])) {
955					$chd_item = $chd_items_tpl[$chd_host['hostid']][$tpl_item['itemid']];
956
957					if ($tpl_item['key_'] !== $chd_item['key_']) {
958						$upd_hostids_by_key[$tpl_item['key_']][] = $chd_host['hostid'];
959					}
960				}
961				// Update by key.
962				elseif (array_key_exists($chd_host['hostid'], $chd_items_key)
963						&& array_key_exists($tpl_item['key_'], $chd_items_key[$chd_host['hostid']])) {
964					$chd_item = $chd_items_key[$chd_host['hostid']][$tpl_item['key_']];
965
966					// Check if an item of a different type with the same key exists.
967					if ($tpl_item['flags'] != $chd_item['flags']) {
968						$this->errorInheritFlags($chd_item['flags'], $chd_item['key_'], $chd_host['host']);
969					}
970
971					// Check if item already linked to another template.
972					if ($chd_item['templateid'] != 0 && bccomp($chd_item['templateid'], $tpl_item['itemid']) != 0) {
973						self::exception(ZBX_API_ERROR_PARAMETERS, _params(
974							$this->getErrorMsg(self::ERROR_EXISTS_TEMPLATE), [$tpl_item['key_'], $chd_host['host']]
975						));
976					}
977
978					if ($this instanceof CItemPrototype) {
979						$chd_ruleid = $chd_ruleids[$chd_host['hostid']][$tpl_item['ruleid']];
980						if (bccomp($chd_item['ruleid'], $chd_ruleid) != 0) {
981							self::exception(ZBX_API_ERROR_PARAMETERS,
982								_s('Item prototype "%1$s" already exists on "%2$s", linked to another rule.',
983									$chd_item['key_'], $chd_host['host']
984								)
985							);
986						}
987					}
988				}
989
990				// copying item
991				$new_item = $tpl_item;
992				if ($chd_item !== null) {
993					$new_item['itemid'] = $chd_item['itemid'];
994				}
995				else {
996					unset($new_item['itemid']);
997					if ($this instanceof CItemPrototype) {
998						$new_item['ruleid'] = $chd_ruleids[$chd_host['hostid']][$tpl_item['ruleid']];
999					}
1000				}
1001				$new_item['hostid'] = $chd_host['hostid'];
1002				$new_item['templateid'] = $tpl_item['itemid'];
1003
1004				if ($chd_host['status'] != HOST_STATUS_TEMPLATE) {
1005					if ($chd_item === null || $new_item['type'] != $chd_item['type']) {
1006						$interface = self::findInterfaceForItem($new_item['type'], $chd_host['interfaces']);
1007
1008						if ($interface) {
1009							$new_item['interfaceid'] = $interface['interfaceid'];
1010						}
1011						elseif ($interface !== false) {
1012							self::exception(ZBX_API_ERROR_PARAMETERS, _params(
1013								$this->getErrorMsg(self::ERROR_NO_INTERFACE), [$chd_host['host'], $new_item['key_']]
1014							));
1015						}
1016					}
1017
1018					if ($this instanceof CItem || $this instanceof CDiscoveryRule) {
1019						if (!array_key_exists('itemid', $new_item)) {
1020							$new_item['rtdata'] = true;
1021						}
1022					}
1023				}
1024
1025				if (array_key_exists('preprocessing', $new_item)) {
1026					foreach ($new_item['preprocessing'] as $preprocessing) {
1027						if ($chd_item) {
1028							$preprocessing['itemid'] = $chd_item['itemid'];
1029						}
1030						else {
1031							unset($preprocessing['itemid']);
1032						}
1033					}
1034				}
1035
1036				if ($this instanceof CItemPrototype && array_key_exists('applicationPrototypes', $new_item)) {
1037					foreach ($new_item['applicationPrototypes'] as &$application_prototype) {
1038						$application_prototype['templateid'] =
1039							$tpl_app_prototypes[$tpl_item['itemid']][$application_prototype['name']];
1040					}
1041					unset($application_prototype);
1042				}
1043
1044				$new_items[] = $new_item;
1045			}
1046		}
1047
1048		// Check if item with a new key already exists on the child host.
1049		if ($upd_hostids_by_key) {
1050			$sql_where = [];
1051			foreach ($upd_hostids_by_key as $key => $hostids) {
1052				$sql_where[] = dbConditionInt('i.hostid', $hostids).' AND i.key_='.zbx_dbstr($key);
1053			}
1054
1055			$sql = 'SELECT i.hostid,i.key_'.
1056				' FROM items i'.
1057				' WHERE ('.implode(') OR (', $sql_where).')';
1058			$db_items = DBselect($sql, 1);
1059
1060			if ($db_item = DBfetch($db_items)) {
1061				self::exception(ZBX_API_ERROR_PARAMETERS, _params($this->getErrorMsg(self::ERROR_EXISTS),
1062					[$db_item['key_'], $chd_hosts[$db_item['hostid']]['host']]
1063				));
1064			}
1065		}
1066
1067		// Setting item applications.
1068		if ($this instanceof CItem || $this instanceof CItemPrototype) {
1069			$tpl_applicationids = [];
1070			foreach ($tpl_items as $tpl_item) {
1071				if (array_key_exists('applications', $tpl_item)) {
1072					foreach ($tpl_item['applications'] as $applicationid) {
1073						$tpl_applicationids[$applicationid] = true;
1074					}
1075				}
1076			}
1077
1078			if ($tpl_applicationids) {
1079				$db_applications = DBselect('SELECT a.hostid,at.templateid,a.applicationid'.
1080					' FROM application_template at,applications a'.
1081					' WHERE at.applicationid=a.applicationid'.
1082						' AND '.dbConditionInt('at.templateid', array_keys($tpl_applicationids)).
1083						' AND '.dbConditionInt('a.hostid', array_keys($chd_hosts))
1084				);
1085
1086				$app_links = [];
1087				while ($db_application = DBfetch($db_applications)) {
1088					$app_links[$db_application['hostid']][$db_application['templateid']] =
1089						$db_application['applicationid'];
1090				}
1091
1092				foreach ($new_items as &$new_item) {
1093					if (array_key_exists('applications', $new_item)) {
1094						$applicationids = [];
1095						foreach ($new_item['applications'] as $applicationid) {
1096							if (array_key_exists($applicationid, $app_links[$new_item['hostid']])) {
1097								$applicationids[] =  $app_links[$new_item['hostid']][$applicationid];
1098							}
1099						}
1100						$new_item['applications'] = $applicationids;
1101					}
1102				}
1103				unset($new_item);
1104			}
1105		}
1106
1107		return $this->prepareDependentItems($tpl_items, $new_items, $hostids);
1108	}
1109
1110	/**
1111	 * Update relations for inherited dependent items to master items.
1112	 *
1113	 * @param array      $tpl_items
1114	 * @param int        $tpl_items[<itemid>]['type']
1115	 * @param string     $tpl_items[<itemid>]['master_itemid']
1116	 * @param array      $new_items
1117	 * @param string     $new_items[<itemid>]['hostid']
1118	 * @param int        $new_items[<itemid>]['type']
1119	 * @param string     $new_items[<itemid>]['templateid']
1120	 * @param array|null $hostids
1121	 *
1122	 * @return array an array of synchronized inherited items.
1123	 */
1124	private function prepareDependentItems(array $tpl_items, array $new_items, array $hostids = null) {
1125		$tpl_master_itemids = [];
1126
1127		foreach ($tpl_items as $tpl_item) {
1128			if ($tpl_item['type'] == ITEM_TYPE_DEPENDENT) {
1129				$tpl_master_itemids[$tpl_item['master_itemid']] = true;
1130			}
1131		}
1132
1133		if ($tpl_master_itemids) {
1134			$sql = 'SELECT i.itemid,i.hostid,i.templateid'.
1135				' FROM items i'.
1136				' WHERE '.dbConditionId('i.templateid', array_keys($tpl_master_itemids));
1137			if ($hostids !== null) {
1138				$sql .= ' AND '.dbConditionId('i.hostid', $hostids);
1139			}
1140			$db_items = DBselect($sql);
1141
1142			$master_links = [];
1143
1144			while ($db_item = DBfetch($db_items)) {
1145				$master_links[$db_item['templateid']][$db_item['hostid']] = $db_item['itemid'];
1146			}
1147
1148			foreach ($new_items as &$new_item) {
1149				if ($new_item['type'] == ITEM_TYPE_DEPENDENT) {
1150					$tpl_item = $tpl_items[$new_item['templateid']];
1151
1152					if (array_key_exists('master_itemid', $tpl_item)) {
1153						$new_item['master_itemid'] = $master_links[$tpl_item['master_itemid']][$new_item['hostid']];
1154					}
1155				}
1156			}
1157			unset($new_item);
1158		}
1159
1160		return $new_items;
1161	}
1162
1163	/**
1164	 * Validate item pre-processing.
1165	 *
1166	 * @param array  $item                                             An array of single item data.
1167	 * @param array  $item['preprocessing']                            An array of item pre-processing data.
1168	 * @param string $item['preprocessing'][]['type']                  The preprocessing option type. Possible values:
1169	 *                                                                  1 - ZBX_PREPROC_MULTIPLIER;
1170	 *                                                                  2 - ZBX_PREPROC_RTRIM;
1171	 *                                                                  3 - ZBX_PREPROC_LTRIM;
1172	 *                                                                  4 - ZBX_PREPROC_TRIM;
1173	 *                                                                  5 - ZBX_PREPROC_REGSUB;
1174	 *                                                                  6 - ZBX_PREPROC_BOOL2DEC;
1175	 *                                                                  7 - ZBX_PREPROC_OCT2DEC;
1176	 *                                                                  8 - ZBX_PREPROC_HEX2DEC;
1177	 *                                                                  9 - ZBX_PREPROC_DELTA_VALUE;
1178	 *                                                                  10 - ZBX_PREPROC_DELTA_SPEED;
1179	 *                                                                  11 - ZBX_PREPROC_XPATH;
1180	 *                                                                  12 - ZBX_PREPROC_JSONPATH;
1181	 *                                                                  13 - ZBX_PREPROC_VALIDATE_RANGE;
1182	 *                                                                  14 - ZBX_PREPROC_VALIDATE_REGEX;
1183	 *                                                                  15 - ZBX_PREPROC_VALIDATE_NOT_REGEX;
1184	 *                                                                  16 - ZBX_PREPROC_ERROR_FIELD_JSON;
1185	 *                                                                  17 - ZBX_PREPROC_ERROR_FIELD_XML;
1186	 *                                                                  18 - ZBX_PREPROC_ERROR_FIELD_REGEX;
1187	 *                                                                  19 - ZBX_PREPROC_THROTTLE_VALUE;
1188	 *                                                                  20 - ZBX_PREPROC_THROTTLE_TIMED_VALUE;
1189	 *                                                                  21 - ZBX_PREPROC_SCRIPT;
1190	 *                                                                  22 - ZBX_PREPROC_PROMETHEUS_PATTERN;
1191	 *                                                                  23 - ZBX_PREPROC_PROMETHEUS_TO_JSON;
1192	 *                                                                  24 - ZBX_PREPROC_CSV_TO_JSON;
1193	 *                                                                  25 - ZBX_PREPROC_STR_REPLACE.
1194	 * @param string $item['preprocessing'][]['params']                Additional parameters used by preprocessing
1195	 *                                                                 option. Multiple parameters are separated by LF
1196	 *                                                                 (\n) character.
1197	 * @param string $item['preprocessing'][]['error_handler']         Action type used in case of preprocessing step
1198	 *                                                                 failure. Possible values:
1199	 *                                                                  0 - ZBX_PREPROC_FAIL_DEFAULT;
1200	 *                                                                  1 - ZBX_PREPROC_FAIL_DISCARD_VALUE;
1201	 *                                                                  2 - ZBX_PREPROC_FAIL_SET_VALUE;
1202	 *                                                                  3 - ZBX_PREPROC_FAIL_SET_ERROR.
1203	 * @param string $item['preprocessing'][]['error_handler_params']  Error handler parameters.
1204	 */
1205	protected function validateItemPreprocessing(array $item) {
1206		if (array_key_exists('preprocessing', $item)) {
1207			if (!is_array($item['preprocessing'])) {
1208				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1209			}
1210
1211			$type_validator = new CLimitedSetValidator(['values' => $this::$supported_preprocessing_types]);
1212
1213			$error_handler_validator = new CLimitedSetValidator([
1214				'values' => [ZBX_PREPROC_FAIL_DEFAULT, ZBX_PREPROC_FAIL_DISCARD_VALUE, ZBX_PREPROC_FAIL_SET_VALUE,
1215					ZBX_PREPROC_FAIL_SET_ERROR
1216				]
1217			]);
1218
1219			$prometheus_pattern_parser = new CPrometheusPatternParser(['usermacros' => true,
1220				'lldmacros' => ($this instanceof CItemPrototype)
1221			]);
1222			$prometheus_output_parser = new CPrometheusOutputParser(['usermacros' => true,
1223				'lldmacros' => ($this instanceof CItemPrototype)
1224			]);
1225
1226			$required_fields = ['type', 'params', 'error_handler', 'error_handler_params'];
1227			$delta = false;
1228			$throttling = false;
1229			$prometheus = false;
1230
1231			foreach ($item['preprocessing'] as $preprocessing) {
1232				$missing_keys = array_diff($required_fields, array_keys($preprocessing));
1233
1234				if ($missing_keys) {
1235					self::exception(ZBX_API_ERROR_PARAMETERS,
1236						_s('Item pre-processing is missing parameters: %1$s', implode(', ', $missing_keys))
1237					);
1238				}
1239
1240				if (is_array($preprocessing['type'])) {
1241					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1242				}
1243				elseif ($preprocessing['type'] === '' || $preprocessing['type'] === null
1244						|| $preprocessing['type'] === false) {
1245					self::exception(ZBX_API_ERROR_PARAMETERS,
1246						_s('Incorrect value for field "%1$s": %2$s.', 'type', _('cannot be empty'))
1247					);
1248				}
1249
1250				if (!$type_validator->validate($preprocessing['type'])) {
1251					self::exception(ZBX_API_ERROR_PARAMETERS,
1252						_s('Incorrect value for field "%1$s": %2$s.', 'type',
1253							_s('unexpected value "%1$s"', $preprocessing['type'])
1254						)
1255					);
1256				}
1257
1258				$preprocessing['params'] = str_replace("\r\n", "\n", $preprocessing['params']);
1259
1260				switch ($preprocessing['type']) {
1261					case ZBX_PREPROC_MULTIPLIER:
1262						// Check if custom multiplier is a valid number.
1263						$params = $preprocessing['params'];
1264
1265						if (is_array($params)) {
1266							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1267						}
1268						elseif ($params === '' || $params === null || $params === false) {
1269							self::exception(ZBX_API_ERROR_PARAMETERS,
1270								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty'))
1271							);
1272						}
1273
1274						if (is_numeric($params)) {
1275							break;
1276						}
1277
1278						$types = ['usermacros' => true];
1279
1280						if ($this instanceof CItemPrototype) {
1281							$types['lldmacros'] = true;
1282						}
1283
1284						if (!(new CMacrosResolverGeneral)->getMacroPositions($params, $types)) {
1285							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1286								'params', _('a numeric value is expected')
1287							));
1288						}
1289						break;
1290
1291					case ZBX_PREPROC_RTRIM:
1292					case ZBX_PREPROC_LTRIM:
1293					case ZBX_PREPROC_TRIM:
1294					case ZBX_PREPROC_XPATH:
1295					case ZBX_PREPROC_JSONPATH:
1296					case ZBX_PREPROC_VALIDATE_REGEX:
1297					case ZBX_PREPROC_VALIDATE_NOT_REGEX:
1298					case ZBX_PREPROC_ERROR_FIELD_JSON:
1299					case ZBX_PREPROC_ERROR_FIELD_XML:
1300					case ZBX_PREPROC_SCRIPT:
1301						// Check 'params' if not empty.
1302						if (is_array($preprocessing['params'])) {
1303							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1304						}
1305						elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null
1306								|| $preprocessing['params'] === false) {
1307							self::exception(ZBX_API_ERROR_PARAMETERS,
1308								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty'))
1309							);
1310						}
1311						break;
1312
1313					case ZBX_PREPROC_REGSUB:
1314					case ZBX_PREPROC_ERROR_FIELD_REGEX:
1315					case ZBX_PREPROC_STR_REPLACE:
1316						// Check if 'params' are not empty and if second parameter contains (after \n) is not empty.
1317						if (is_array($preprocessing['params'])) {
1318							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1319						}
1320						elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null
1321								|| $preprocessing['params'] === false) {
1322							self::exception(ZBX_API_ERROR_PARAMETERS,
1323								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty'))
1324							);
1325						}
1326
1327						$params = explode("\n", $preprocessing['params']);
1328
1329						if ($params[0] === '') {
1330							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1331								'params', _('first parameter is expected')
1332							));
1333						}
1334
1335						if (($preprocessing['type'] == ZBX_PREPROC_REGSUB
1336								|| $preprocessing['type'] == ZBX_PREPROC_ERROR_FIELD_REGEX)
1337								&& (!array_key_exists(1, $params) || $params[1] === '')) {
1338							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1339								'params', _('second parameter is expected')
1340							));
1341						}
1342						break;
1343
1344					case ZBX_PREPROC_VALIDATE_RANGE:
1345						if (is_array($preprocessing['params'])) {
1346							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1347						}
1348						elseif (trim($preprocessing['params']) === '' || $preprocessing['params'] === null
1349								|| $preprocessing['params'] === false) {
1350							self::exception(ZBX_API_ERROR_PARAMETERS,
1351								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty'))
1352							);
1353						}
1354
1355						$params = explode("\n", $preprocessing['params']);
1356
1357						if ($params[0] !== '' && !is_numeric($params[0])
1358								&& (new CUserMacroParser())->parse($params[0]) != CParser::PARSE_SUCCESS
1359								&& (!($this instanceof CItemPrototype)
1360									|| ((new CLLDMacroFunctionParser())->parse($params[0]) != CParser::PARSE_SUCCESS
1361										&& (new CLLDMacroParser())->parse($params[0]) != CParser::PARSE_SUCCESS))) {
1362							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1363								'params', _('a numeric value is expected')
1364							));
1365						}
1366
1367						if ($params[1] !== '' && !is_numeric($params[1])
1368								&& (new CUserMacroParser())->parse($params[1]) != CParser::PARSE_SUCCESS
1369								&& (!($this instanceof CItemPrototype)
1370									|| ((new CLLDMacroFunctionParser())->parse($params[1]) != CParser::PARSE_SUCCESS
1371										&& (new CLLDMacroParser())->parse($params[1]) != CParser::PARSE_SUCCESS))) {
1372							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1373								'params', _('a numeric value is expected')
1374							));
1375						}
1376
1377						if (is_numeric($params[0]) && is_numeric($params[1]) && $params[0] > $params[1]) {
1378							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1379								'Incorrect value for field "%1$s": %2$s.',
1380								'params',
1381								_s('"%1$s" value must be less than or equal to "%2$s" value', _('min'), _('max'))
1382							));
1383						}
1384						break;
1385
1386					case ZBX_PREPROC_BOOL2DEC:
1387					case ZBX_PREPROC_OCT2DEC:
1388					case ZBX_PREPROC_HEX2DEC:
1389					case ZBX_PREPROC_THROTTLE_VALUE:
1390						// Check if 'params' is empty, because it must be empty.
1391						if (is_array($preprocessing['params'])) {
1392							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1393						}
1394						elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null
1395								&& $preprocessing['params'] !== false) {
1396							self::exception(ZBX_API_ERROR_PARAMETERS,
1397								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty'))
1398							);
1399						}
1400
1401						if ($preprocessing['type'] == ZBX_PREPROC_THROTTLE_VALUE) {
1402							if ($throttling) {
1403								self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one throttling step is allowed.'));
1404							}
1405							else {
1406								$throttling = true;
1407							}
1408						}
1409						break;
1410
1411					case ZBX_PREPROC_DELTA_VALUE:
1412					case ZBX_PREPROC_DELTA_SPEED:
1413						// Check if 'params' is empty, because it must be empty.
1414						if (is_array($preprocessing['params'])) {
1415							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1416						}
1417						elseif ($preprocessing['params'] !== '' && $preprocessing['params'] !== null
1418								&& $preprocessing['params'] !== false) {
1419							self::exception(ZBX_API_ERROR_PARAMETERS,
1420								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('should be empty'))
1421							);
1422						}
1423
1424						// Check if one of the deltas (Delta per second or Delta value) already exists.
1425						if ($delta) {
1426							self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one change step is allowed.'));
1427						}
1428						else {
1429							$delta = true;
1430						}
1431						break;
1432
1433					case ZBX_PREPROC_THROTTLE_TIMED_VALUE:
1434						$api_input_rules = [
1435							'type' => API_TIME_UNIT,
1436							'flags' => ($this instanceof CItem)
1437								? API_NOT_EMPTY | API_ALLOW_USER_MACRO
1438								: API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO,
1439							'in' => '1:'.ZBX_MAX_TIMESHIFT
1440						];
1441
1442						if (!CApiInputValidator::validate($api_input_rules, $preprocessing['params'], 'params',
1443								$error)) {
1444							self::exception(ZBX_API_ERROR_PARAMETERS, $error);
1445						}
1446
1447						if ($throttling) {
1448							self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one throttling step is allowed.'));
1449						}
1450						else {
1451							$throttling = true;
1452						}
1453						break;
1454
1455					case ZBX_PREPROC_PROMETHEUS_PATTERN:
1456					case ZBX_PREPROC_PROMETHEUS_TO_JSON:
1457						if ($prometheus) {
1458							self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one Prometheus step is allowed.'));
1459						}
1460
1461						$prometheus = true;
1462
1463						if (is_array($preprocessing['params'])) {
1464							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1465						}
1466
1467						if ($preprocessing['type'] == ZBX_PREPROC_PROMETHEUS_PATTERN) {
1468							if ($preprocessing['params'] === '' || $preprocessing['params'] === null
1469									|| $preprocessing['params'] === false) {
1470								self::exception(ZBX_API_ERROR_PARAMETERS,
1471									_s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty'))
1472								);
1473							}
1474
1475							$params = explode("\n", $preprocessing['params']);
1476
1477							if ($params[0] === '') {
1478								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1479									'params', _('first parameter is expected')
1480								));
1481							}
1482
1483							if ($prometheus_pattern_parser->parse($params[0]) != CParser::PARSE_SUCCESS) {
1484								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1485									'params', _('invalid Prometheus pattern')
1486								));
1487							}
1488
1489							if ($params[1] !== ''
1490									&& $prometheus_output_parser->parse($params[1]) != CParser::PARSE_SUCCESS) {
1491								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1492									'params', _('invalid Prometheus output')
1493								));
1494							}
1495						}
1496						// Prometheus to JSON can be empty and has only one parameter.
1497						elseif ($preprocessing['params'] !== '') {
1498							if ($prometheus_pattern_parser->parse($preprocessing['params']) != CParser::PARSE_SUCCESS) {
1499								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1500									'params', _('invalid Prometheus pattern')
1501								));
1502							}
1503						}
1504						break;
1505
1506					case ZBX_PREPROC_CSV_TO_JSON:
1507						if (is_array($preprocessing['params'])) {
1508							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1509						}
1510						elseif ($preprocessing['params'] === '' || $preprocessing['params'] === null
1511								|| $preprocessing['params'] === false) {
1512							self::exception(ZBX_API_ERROR_PARAMETERS,
1513								_s('Incorrect value for field "%1$s": %2$s.', 'params', _('cannot be empty'))
1514							);
1515						}
1516
1517						$params = explode("\n", $preprocessing['params']);
1518
1519						$params_cnt = count($params);
1520						if ($params_cnt > 3) {
1521							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1522						}
1523						elseif ($params_cnt == 1) {
1524							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1525								'params', _('second parameter is expected')
1526							));
1527						}
1528						elseif ($params_cnt == 2) {
1529							self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1530								'params', _('third parameter is expected')
1531							));
1532						}
1533						else {
1534							// Correct amount of parameters, but check if they are valid.
1535
1536							if (mb_strlen($params[0]) > 1) {
1537								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1538									'params', _('value of first parameter is too long')
1539								));
1540							}
1541
1542							if (mb_strlen($params[1]) > 1) {
1543								self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1544									'params', _('value of second parameter is too long')
1545								));
1546							}
1547
1548							$with_header_row_validator = new CLimitedSetValidator([
1549								'values' => [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER]
1550							]);
1551
1552							if (!$with_header_row_validator->validate($params[2])) {
1553								self::exception(ZBX_API_ERROR_PARAMETERS,
1554									_s('Incorrect value for field "%1$s": %2$s.', 'params',
1555										_s('value of third parameter must be one of %1$s',
1556											implode(', ', [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER])
1557										)
1558									)
1559								);
1560							}
1561						}
1562						break;
1563				}
1564
1565				switch ($preprocessing['type']) {
1566					case ZBX_PREPROC_RTRIM:
1567					case ZBX_PREPROC_LTRIM:
1568					case ZBX_PREPROC_TRIM:
1569					case ZBX_PREPROC_THROTTLE_VALUE:
1570					case ZBX_PREPROC_THROTTLE_TIMED_VALUE:
1571					case ZBX_PREPROC_SCRIPT:
1572					case ZBX_PREPROC_STR_REPLACE:
1573						if (is_array($preprocessing['error_handler'])) {
1574							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1575						}
1576						elseif ($preprocessing['error_handler'] != ZBX_PREPROC_FAIL_DEFAULT) {
1577							self::exception(ZBX_API_ERROR_PARAMETERS,
1578								_s('Incorrect value for field "%1$s": %2$s.', 'error_handler',
1579									_s('unexpected value "%1$s"', $preprocessing['error_handler'])
1580								)
1581							);
1582						}
1583
1584						if (is_array($preprocessing['error_handler_params'])) {
1585							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1586						}
1587						elseif ($preprocessing['error_handler_params'] !== ''
1588								&& $preprocessing['error_handler_params'] !== null
1589								&& $preprocessing['error_handler_params'] !== false) {
1590							self::exception(ZBX_API_ERROR_PARAMETERS,
1591								_s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params',
1592									_('should be empty')
1593								)
1594							);
1595						}
1596						break;
1597
1598					default:
1599						if (is_array($preprocessing['error_handler'])) {
1600							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1601						}
1602						elseif (!$error_handler_validator->validate($preprocessing['error_handler'])) {
1603							self::exception(ZBX_API_ERROR_PARAMETERS,
1604								_s('Incorrect value for field "%1$s": %2$s.', 'error_handler',
1605									_s('unexpected value "%1$s"', $preprocessing['error_handler'])
1606								)
1607							);
1608						}
1609
1610						if (is_array($preprocessing['error_handler_params'])) {
1611							self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1612						}
1613						elseif (($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DEFAULT
1614									|| $preprocessing['error_handler'] == ZBX_PREPROC_FAIL_DISCARD_VALUE)
1615								&& $preprocessing['error_handler_params'] !== ''
1616								&& $preprocessing['error_handler_params'] !== null
1617								&& $preprocessing['error_handler_params'] !== false) {
1618							self::exception(ZBX_API_ERROR_PARAMETERS,
1619								_s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params',
1620									_('should be empty')
1621								)
1622							);
1623						}
1624						elseif ($preprocessing['error_handler'] == ZBX_PREPROC_FAIL_SET_ERROR
1625								&& ($preprocessing['error_handler_params'] === ''
1626									|| $preprocessing['error_handler_params'] === null
1627									|| $preprocessing['error_handler_params'] === false)) {
1628							self::exception(ZBX_API_ERROR_PARAMETERS,
1629								_s('Incorrect value for field "%1$s": %2$s.', 'error_handler_params',
1630									_('cannot be empty')
1631								)
1632							);
1633						}
1634				}
1635			}
1636		}
1637	}
1638
1639	/**
1640	 * Method validates preprocessing steps independently from other item properties.
1641	 *
1642	 * @param array  $preprocessing_steps    An array of item pre-processing step details.
1643	 *                                       See self::validateItemPreprocessing for details.
1644	 *
1645	 * @return bool|string
1646	 */
1647	public function validateItemPreprocessingSteps(array $preprocessing_steps) {
1648		try {
1649			$this->validateItemPreprocessing(['preprocessing' => $preprocessing_steps]);
1650
1651			return true;
1652		}
1653		catch (APIException $error) {
1654			return $error->getMessage();
1655		}
1656	}
1657
1658	/**
1659	 * Insert item pre-processing data into DB.
1660	 *
1661	 * @param array $items                     An array of items.
1662	 * @param array $items[]['preprocessing']  An array of item pre-processing data.
1663	 */
1664	protected function createItemPreprocessing(array $items) {
1665		$item_preproc = [];
1666
1667		foreach ($items as $item) {
1668			if (array_key_exists('preprocessing', $item)) {
1669				$step = 1;
1670
1671				foreach ($item['preprocessing'] as $preprocessing) {
1672					$item_preproc[] = [
1673						'itemid' => $item['itemid'],
1674						'step' => $step++,
1675						'type' => $preprocessing['type'],
1676						'params' => $preprocessing['params'],
1677						'error_handler' => $preprocessing['error_handler'],
1678						'error_handler_params' => $preprocessing['error_handler_params']
1679					];
1680				}
1681			}
1682		}
1683
1684		if ($item_preproc) {
1685			DB::insertBatch('item_preproc', $item_preproc);
1686		}
1687	}
1688
1689	/**
1690	 * Update item pre-processing data in DB. Delete old records and create new ones.
1691	 *
1692	 * @param array  $items
1693	 * @param string $items[]['itemid']
1694	 * @param array  $items[]['preprocessing']
1695	 * @param int    $items[]['preprocessing'][]['type']
1696	 * @param string $items[]['preprocessing'][]['params']
1697	 * @param int    $items[]['preprocessing'][]['error_handler']
1698	 * @param string $items[]['preprocessing'][]['error_handler_params']
1699	 */
1700	protected function updateItemPreprocessing(array $items) {
1701		$item_preprocs = [];
1702
1703		foreach ($items as $item) {
1704			if (array_key_exists('preprocessing', $item)) {
1705				$item_preprocs[$item['itemid']] = [];
1706				$step = 1;
1707
1708				foreach ($item['preprocessing'] as $item_preproc) {
1709					$item_preprocs[$item['itemid']][$step++] = [
1710						'type' => $item_preproc['type'],
1711						'params' => $item_preproc['params'],
1712						'error_handler' => $item_preproc['error_handler'],
1713						'error_handler_params' => $item_preproc['error_handler_params']
1714					];
1715				}
1716			}
1717		}
1718
1719		if (!$item_preprocs) {
1720			return;
1721		}
1722
1723		$ins_item_preprocs = [];
1724		$upd_item_preprocs = [];
1725		$del_item_preprocids = [];
1726
1727		$options = [
1728			'output' => ['item_preprocid', 'itemid', 'step', 'type', 'params', 'error_handler', 'error_handler_params'],
1729			'filter' => ['itemid' => array_keys($item_preprocs)]
1730		];
1731		$db_item_preprocs = DBselect(DB::makeSql('item_preproc', $options));
1732
1733		while ($db_item_preproc = DBfetch($db_item_preprocs)) {
1734			if (array_key_exists($db_item_preproc['step'], $item_preprocs[$db_item_preproc['itemid']])) {
1735				$item_preproc = $item_preprocs[$db_item_preproc['itemid']][$db_item_preproc['step']];
1736				$upd_item_preproc = [];
1737
1738				if ($item_preproc['type'] != $db_item_preproc['type']) {
1739					$upd_item_preproc['type'] = $item_preproc['type'];
1740				}
1741				if ($item_preproc['params'] !== $db_item_preproc['params']) {
1742					$upd_item_preproc['params'] = $item_preproc['params'];
1743				}
1744				if ($item_preproc['error_handler'] != $db_item_preproc['error_handler']) {
1745					$upd_item_preproc['error_handler'] = $item_preproc['error_handler'];
1746				}
1747				if ($item_preproc['error_handler_params'] !== $db_item_preproc['error_handler_params']) {
1748					$upd_item_preproc['error_handler_params'] = $item_preproc['error_handler_params'];
1749				}
1750
1751				if ($upd_item_preproc) {
1752					$upd_item_preprocs[] = [
1753						'values' => $upd_item_preproc,
1754						'where' => ['item_preprocid' => $db_item_preproc['item_preprocid']]
1755					];
1756				}
1757				unset($item_preprocs[$db_item_preproc['itemid']][$db_item_preproc['step']]);
1758			}
1759			else {
1760				$del_item_preprocids[] = $db_item_preproc['item_preprocid'];
1761			}
1762		}
1763
1764		foreach ($item_preprocs as $itemid => $preprocs) {
1765			foreach ($preprocs as $step => $preproc) {
1766				$ins_item_preprocs[] = [
1767					'itemid' => $itemid,
1768					'step' => $step
1769				] + $preproc;
1770			}
1771		}
1772
1773		if ($del_item_preprocids) {
1774			DB::delete('item_preproc', ['item_preprocid' => $del_item_preprocids]);
1775		}
1776
1777		if ($upd_item_preprocs) {
1778			DB::update('item_preproc', $upd_item_preprocs);
1779		}
1780
1781		if ($ins_item_preprocs) {
1782			DB::insertBatch('item_preproc', $ins_item_preprocs);
1783		}
1784	}
1785
1786	/**
1787	 * Check if any item from list already exists.
1788	 * If items have item ids it will check for existing item with different itemid.
1789	 *
1790	 * @throw APIException
1791	 *
1792	 * @param array $items
1793	 */
1794	protected function checkExistingItems(array $items) {
1795		$itemKeysByHostId = [];
1796		$itemIds = [];
1797		foreach ($items as $item) {
1798			if (!isset($itemKeysByHostId[$item['hostid']])) {
1799				$itemKeysByHostId[$item['hostid']] = [];
1800			}
1801			$itemKeysByHostId[$item['hostid']][] = $item['key_'];
1802
1803			if (isset($item['itemid'])) {
1804				$itemIds[] = $item['itemid'];
1805			}
1806		}
1807
1808		$sqlWhere = [];
1809		foreach ($itemKeysByHostId as $hostId => $keys) {
1810			$sqlWhere[] = '(i.hostid='.zbx_dbstr($hostId).' AND '.dbConditionString('i.key_', $keys).')';
1811		}
1812
1813		if ($sqlWhere) {
1814			$sql = 'SELECT i.key_,h.host'.
1815					' FROM items i,hosts h'.
1816					' WHERE i.hostid=h.hostid AND ('.implode(' OR ', $sqlWhere).')';
1817
1818			// if we update existing items we need to exclude them from result.
1819			if ($itemIds) {
1820				$sql .= ' AND '.dbConditionInt('i.itemid', $itemIds, true);
1821			}
1822			$dbItems = DBselect($sql, 1);
1823			while ($dbItem = DBfetch($dbItems)) {
1824				self::exception(ZBX_API_ERROR_PARAMETERS,
1825					_s('Item with key "%1$s" already exists on "%2$s".', $dbItem['key_'], $dbItem['host']));
1826			}
1827		}
1828	}
1829
1830	protected function addRelatedObjects(array $options, array $result) {
1831		$result = parent::addRelatedObjects($options, $result);
1832
1833		// adding hosts
1834		if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) {
1835			$relationMap = $this->createRelationMap($result, 'itemid', 'hostid');
1836			$hosts = API::Host()->get([
1837				'hostids' => $relationMap->getRelatedIds(),
1838				'templated_hosts' => true,
1839				'output' => $options['selectHosts'],
1840				'nopermissions' => true,
1841				'preservekeys' => true
1842			]);
1843			$result = $relationMap->mapMany($result, $hosts, 'hosts');
1844		}
1845
1846		// adding preprocessing
1847		if ($options['selectPreprocessing'] !== null && $options['selectPreprocessing'] != API_OUTPUT_COUNT) {
1848			$db_item_preproc = API::getApiService()->select('item_preproc', [
1849				'output' => $this->outputExtend($options['selectPreprocessing'], ['itemid', 'step']),
1850				'filter' => ['itemid' => array_keys($result)]
1851			]);
1852
1853			CArrayHelper::sort($db_item_preproc, ['step']);
1854
1855			foreach ($result as &$item) {
1856				$item['preprocessing'] = [];
1857			}
1858			unset($item);
1859
1860			foreach ($db_item_preproc as $step) {
1861				$itemid = $step['itemid'];
1862				unset($step['item_preprocid'], $step['itemid'], $step['step']);
1863
1864				if (array_key_exists($itemid, $result)) {
1865					$result[$itemid]['preprocessing'][] = $step;
1866				}
1867			}
1868		}
1869
1870		return $result;
1871	}
1872
1873	/**
1874	 * Validate items with type ITEM_TYPE_DEPENDENT for create or update operation.
1875	 *
1876	 * @param array  $items
1877	 * @param string $items[]['itemid']         (mandatory for updated items and item prototypes)
1878	 * @param string $items[]['hostid']
1879	 * @param int    $items[]['type']
1880	 * @param string $items[]['master_itemid']  (mandatory for ITEM_TYPE_DEPENDENT)
1881	 * @param int    $items[]['flags']          (mandatory for items)
1882	 *
1883	 * @throws APIException for invalid data.
1884	 */
1885	protected function validateDependentItems(array $items) {
1886		$dep_items = [];
1887		$upd_itemids = [];
1888
1889		foreach ($items as $item) {
1890			if ($item['type'] == ITEM_TYPE_DEPENDENT) {
1891				if ($this instanceof CDiscoveryRule || $this instanceof CItemPrototype
1892						|| $item['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) {
1893					$dep_items[] = $item;
1894				}
1895
1896				if (array_key_exists('itemid', $item)) {
1897					$upd_itemids[] = $item['itemid'];
1898				}
1899			}
1900		}
1901
1902		if (!$dep_items) {
1903			return;
1904		}
1905
1906		if ($this instanceof CItemPrototype && $upd_itemids) {
1907			$db_links = DBselect(
1908				'SELECT id.itemid,id.parent_itemid AS ruleid'.
1909				' FROM item_discovery id'.
1910				' WHERE '.dbConditionId('id.itemid', $upd_itemids)
1911			);
1912
1913			$links = [];
1914
1915			while ($db_link = DBfetch($db_links)) {
1916				$links[$db_link['itemid']] = $db_link['ruleid'];
1917			}
1918
1919			foreach ($dep_items as &$dep_item) {
1920				if (array_key_exists('itemid', $dep_item)) {
1921					$dep_item['ruleid'] = $links[$dep_item['itemid']];
1922				}
1923			}
1924			unset($dep_item);
1925		}
1926
1927		$master_itemids = [];
1928
1929		foreach ($dep_items as $dep_item) {
1930			$master_itemids[$dep_item['master_itemid']] = true;
1931		}
1932
1933		$master_items = [];
1934
1935		// Fill relations array by master items (item prototypes). Discovery rule should not be master item.
1936		do {
1937			if ($this instanceof CItemPrototype) {
1938				$db_master_items = DBselect(
1939					'SELECT i.itemid,i.hostid,i.master_itemid,i.flags,id.parent_itemid AS ruleid'.
1940					' FROM items i'.
1941						' LEFT JOIN item_discovery id'.
1942							' ON i.itemid=id.itemid'.
1943					' WHERE '.dbConditionId('i.itemid', array_keys($master_itemids)).
1944						' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_PROTOTYPE])
1945				);
1946			}
1947			// CDiscoveryRule, CItem
1948			else {
1949				$db_master_items = DBselect(
1950					'SELECT i.itemid,i.hostid,i.master_itemid'.
1951					' FROM items i'.
1952					' WHERE '.dbConditionId('i.itemid', array_keys($master_itemids)).
1953						' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL])
1954				);
1955			}
1956
1957			while ($db_master_item = DBfetch($db_master_items)) {
1958				$master_items[$db_master_item['itemid']] = $db_master_item;
1959
1960				unset($master_itemids[$db_master_item['itemid']]);
1961			}
1962
1963			if ($master_itemids) {
1964				reset($master_itemids);
1965
1966				self::exception(ZBX_API_ERROR_PERMISSIONS,
1967					_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
1968						_s('Item "%1$s" does not exist or you have no access to this item', key($master_itemids))
1969					)
1970				);
1971			}
1972
1973			$master_itemids = [];
1974
1975			foreach ($master_items as $master_item) {
1976				if ($master_item['master_itemid'] != 0
1977						&& !array_key_exists($master_item['master_itemid'], $master_items)) {
1978					$master_itemids[$master_item['master_itemid']] = true;
1979				}
1980			}
1981		} while ($master_itemids);
1982
1983		foreach ($dep_items as $dep_item) {
1984			$master_item = $master_items[$dep_item['master_itemid']];
1985
1986			if ($dep_item['hostid'] != $master_item['hostid']) {
1987				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1988					'master_itemid', _('hostid of dependent item and master item should match')
1989				));
1990			}
1991
1992			if ($this instanceof CItemPrototype && $master_item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE
1993					&& $dep_item['ruleid'] != $master_item['ruleid']) {
1994				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1995					'master_itemid', _('ruleid of dependent item and master item should match')
1996				));
1997			}
1998
1999			if (array_key_exists('itemid', $dep_item)) {
2000				$master_itemid = $dep_item['master_itemid'];
2001
2002				while ($master_itemid != 0) {
2003					if ($master_itemid == $dep_item['itemid']) {
2004						self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
2005							'master_itemid', _('circular item dependency is not allowed')
2006						));
2007					}
2008
2009					$master_itemid = $master_items[$master_itemid]['master_itemid'];
2010				}
2011			}
2012		}
2013
2014		// Fill relations array by dependent items (item prototypes).
2015		$root_itemids = [];
2016
2017		foreach ($master_items as $master_item) {
2018			if ($master_item['master_itemid'] == 0) {
2019				$root_itemids[] = $master_item['itemid'];
2020			}
2021		}
2022
2023		$dependent_items = [];
2024
2025		foreach ($dep_items as $dep_item) {
2026			if (array_key_exists('itemid', $dep_item)) {
2027				$dependent_items[$dep_item['master_itemid']][] = $dep_item['itemid'];
2028			}
2029		}
2030
2031		$master_itemids = $root_itemids;
2032
2033		do {
2034			$sql = 'SELECT i.master_itemid,i.itemid'.
2035				' FROM items i'.
2036				' WHERE '.dbConditionId('i.master_itemid', $master_itemids);
2037			if ($upd_itemids) {
2038				$sql .= ' AND '.dbConditionId('i.itemid', $upd_itemids, true); // Exclude updated items.
2039			}
2040
2041			$db_items = DBselect($sql);
2042
2043			while ($db_item = DBfetch($db_items)) {
2044				$dependent_items[$db_item['master_itemid']][] = $db_item['itemid'];
2045			}
2046
2047			$_master_itemids = $master_itemids;
2048			$master_itemids = [];
2049
2050			foreach ($_master_itemids as $master_itemid) {
2051				if (array_key_exists($master_itemid, $dependent_items)) {
2052					$master_itemids = array_merge($master_itemids, $dependent_items[$master_itemid]);
2053				}
2054			}
2055		} while ($master_itemids);
2056
2057		foreach ($dep_items as $dep_item) {
2058			if (!array_key_exists('itemid', $dep_item)) {
2059				$dependent_items[$dep_item['master_itemid']][] = false;
2060			}
2061		}
2062
2063		foreach ($root_itemids as $root_itemid) {
2064			self::checkDependencyDepth($dependent_items, $root_itemid);
2065		}
2066	}
2067
2068	/**
2069	 * Validate depth and amount of elements in the tree of the dependent items.
2070	 *
2071	 * @param array  $dependent_items
2072	 * @param string $dependent_items[<master_itemid>][]  List if the dependent item IDs ("false" for new items)
2073	 *                                                    by master_itemid.
2074	 * @param string $root_itemid                         ID of the item being checked.
2075	 * @param int    $level                               Current dependency level.
2076	 *
2077	 * @throws APIException for invalid data.
2078	 */
2079	private static function checkDependencyDepth(array $dependent_items, $root_itemid, $level = 0) {
2080		$count = 0;
2081
2082		if (array_key_exists($root_itemid, $dependent_items)) {
2083			if (++$level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) {
2084				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
2085					'master_itemid', _('maximum number of dependency levels reached')
2086				));
2087			}
2088
2089			foreach ($dependent_items[$root_itemid] as $master_itemid) {
2090				$count++;
2091
2092				if ($master_itemid !== false) {
2093					$count += self::checkDependencyDepth($dependent_items, $master_itemid, $level);
2094				}
2095			}
2096
2097			if ($count > ZBX_DEPENDENT_ITEM_MAX_COUNT) {
2098				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
2099					'master_itemid', _('maximum dependent items count reached')
2100				));
2101			}
2102		}
2103
2104		return $count;
2105	}
2106
2107	/**
2108	 * Converts headers field text to hash with header name as key.
2109	 *
2110	 * @param string $headers  Headers string, one header per line, line delimiter "\r\n".
2111	 *
2112	 * @return array
2113	 */
2114	protected function headersStringToArray($headers) {
2115		$result = [];
2116
2117		foreach (explode("\r\n", $headers) as $header) {
2118			$header = explode(': ', $header, 2);
2119
2120			if (count($header) == 2) {
2121				$result[$header[0]] = $header[1];
2122			}
2123		}
2124
2125		return $result;
2126	}
2127
2128	/**
2129	 * Converts headers fields hash to string.
2130	 *
2131	 * @param array $headers  Array of headers where key is header name.
2132	 *
2133	 * @return string
2134	 */
2135	protected function headersArrayToString(array $headers) {
2136		$result = [];
2137
2138		foreach ($headers as $k => $v) {
2139			$result[] = $k.': '.$v;
2140		}
2141
2142		return implode("\r\n", $result);
2143	}
2144
2145	/**
2146	 * Validate item with type ITEM_TYPE_HTTPAGENT.
2147	 *
2148	 * @param array $item     Array of item fields.
2149	 * @param array $db_item  Array of item database fields for update action or empty array for create action.
2150	 *
2151	 * @throws APIException for invalid data.
2152	 */
2153	protected function validateHTTPCheck(array $item, array $db_item) {
2154		$rules = [
2155			'timeout' => [
2156				'type' => API_TIME_UNIT, 'flags' => ($this instanceof CItemPrototype)
2157					? API_NOT_EMPTY | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO
2158					: API_NOT_EMPTY | API_ALLOW_USER_MACRO,
2159				'in' => '1:'.SEC_PER_MIN
2160			],
2161			'url' => [
2162				'type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY,
2163				'length' => DB::getFieldLength('items', 'url')
2164			],
2165			'status_codes' => [
2166				'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'status_codes')
2167			],
2168			'follow_redirects' => [
2169				'type' => API_INT32,
2170				'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON])
2171			],
2172			'post_type' => [
2173				'type' => API_INT32,
2174				'in' => implode(',', [ZBX_POSTTYPE_RAW, ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML])
2175			],
2176			'http_proxy' => [
2177				'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'http_proxy')
2178			],
2179			'headers' => [
2180				'type' => API_STRINGS_UTF8
2181			],
2182			'retrieve_mode' => [
2183				'type' => API_INT32,
2184				'in' => implode(',', [
2185					HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS,
2186					HTTPTEST_STEP_RETRIEVE_MODE_BOTH
2187				])
2188			],
2189			'request_method' => [
2190				'type' => API_INT32,
2191				'in' => implode(',', [
2192					HTTPCHECK_REQUEST_GET, HTTPCHECK_REQUEST_POST, HTTPCHECK_REQUEST_PUT, HTTPCHECK_REQUEST_HEAD
2193				])
2194			],
2195			'output_format' => [
2196				'type' => API_INT32,
2197				'in' => implode(',', [HTTPCHECK_STORE_RAW, HTTPCHECK_STORE_JSON])
2198			],
2199			'allow_traps' => [
2200				'type' => API_INT32,
2201				'in' => implode(',', [HTTPCHECK_ALLOW_TRAPS_OFF, HTTPCHECK_ALLOW_TRAPS_ON])
2202			],
2203			'ssl_cert_file' => [
2204				'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_cert_file')
2205			],
2206			'ssl_key_file' => [
2207				'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_key_file')
2208			],
2209			'ssl_key_password' => [
2210				'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'ssl_key_password')
2211			],
2212			'verify_peer' => [
2213				'type' => API_INT32,
2214				'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON])
2215			],
2216			'verify_host' => [
2217				'type' => API_INT32,
2218				'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON])
2219			],
2220			'authtype' => [
2221				'type' => API_INT32,
2222				'in' => implode(',', [
2223					HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS
2224				])
2225			]
2226		];
2227
2228		$data = $item + $db_item;
2229
2230		if (array_key_exists('authtype', $data)
2231				&& ($data['authtype'] == HTTPTEST_AUTH_BASIC || $data['authtype'] == HTTPTEST_AUTH_NTLM
2232					|| $data['authtype'] == HTTPTEST_AUTH_KERBEROS)) {
2233			$rules += [
2234				'username' => [ 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'username')],
2235				'password' => [ 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'password')]
2236			];
2237		}
2238
2239		// Strict validation for 'retrieve_mode' only for create action.
2240		if (array_key_exists('request_method', $data) && $data['request_method'] == HTTPCHECK_REQUEST_HEAD
2241				&& array_key_exists('retrieve_mode', $item)) {
2242			$rules['retrieve_mode']['in'] = (string) HTTPTEST_STEP_RETRIEVE_MODE_HEADERS;
2243		}
2244
2245		if (array_key_exists('post_type', $data)
2246				&& ($data['post_type'] == ZBX_POSTTYPE_JSON || $data['post_type'] == ZBX_POSTTYPE_XML)) {
2247			$rules['posts'] = [
2248				'type' => API_STRING_UTF8,
2249				'length' => DB::getFieldLength('items', 'posts')
2250			];
2251		}
2252
2253		if (array_key_exists('templateid', $data) && $data['templateid']) {
2254			$rules['interfaceid'] = [
2255				'type' => API_ID, 'flags' => API_REQUIRED | API_NOT_EMPTY
2256			];
2257		}
2258
2259		if (array_key_exists('trapper_hosts', $item) && $item['trapper_hosts'] !== ''
2260				&& (!array_key_exists('allow_traps', $data) || $data['allow_traps'] == HTTPCHECK_ALLOW_TRAPS_OFF)) {
2261			self::exception(ZBX_API_ERROR_PARAMETERS,
2262				_s('Incorrect value for field "%1$s": %2$s.', 'trapper_hosts', _('should be empty'))
2263			);
2264		}
2265
2266		// Keep values only for fields with defined validation rules.
2267		$data = array_intersect_key($data, $rules);
2268
2269		if (!CApiInputValidator::validate(['type' => API_OBJECT, 'fields' => $rules], $data, '', $error)) {
2270			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
2271		}
2272
2273		if (array_key_exists('query_fields', $item)) {
2274			if (!is_array($item['query_fields'])) {
2275				self::exception(ZBX_API_ERROR_PARAMETERS,
2276					_s('Invalid parameter "%1$s": %2$s.', 'query_fields', _('an array is expected'))
2277				);
2278			}
2279
2280			foreach ($item['query_fields'] as $v) {
2281				if (!is_array($v) || count($v) > 1 || key($v) === '') {
2282					self::exception(ZBX_API_ERROR_PARAMETERS,
2283						_s('Invalid parameter "%1$s": %2$s.', 'query_fields', _('nonempty key and value pair expected'))
2284					);
2285				}
2286			}
2287
2288			if (strlen(json_encode($item['query_fields'])) > DB::getFieldLength('items', 'query_fields')) {
2289				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 'query_fields',
2290					_('cannot convert to JSON, result value too long')
2291				));
2292			}
2293		}
2294
2295		if (array_key_exists('headers', $item)) {
2296			if (!is_array($item['headers'])) {
2297				self::exception(ZBX_API_ERROR_PARAMETERS,
2298					_s('Invalid parameter "%1$s": %2$s.', 'headers', _('an array is expected'))
2299				);
2300			}
2301
2302			foreach ($item['headers'] as $k => $v) {
2303				if (trim($k) === '' || !is_string($v) || $v === '') {
2304					self::exception(ZBX_API_ERROR_PARAMETERS,
2305						_s('Invalid parameter "%1$s": %2$s.', 'headers', _('nonempty key and value pair expected'))
2306					);
2307				}
2308			}
2309		}
2310
2311		if (array_key_exists('status_codes', $item) && $item['status_codes']) {
2312			$ranges_parser = new CRangesParser([
2313				'usermacros' => true,
2314				'lldmacros' => ($this instanceof CItemPrototype)
2315			]);
2316
2317			if ($ranges_parser->parse($item['status_codes']) != CParser::PARSE_SUCCESS) {
2318				self::exception(ZBX_API_ERROR_PARAMETERS,
2319					_s('Incorrect value "%1$s" for "%2$s" field.', $item['status_codes'], 'status_codes')
2320				);
2321			}
2322		}
2323
2324		if ((array_key_exists('post_type', $item) || array_key_exists('posts', $item))
2325				&& ($data['post_type'] == ZBX_POSTTYPE_JSON || $data['post_type'] == ZBX_POSTTYPE_XML)) {
2326			$posts = array_key_exists('posts', $data) ? $data['posts'] : '';
2327			libxml_use_internal_errors(true);
2328
2329			if ($data['post_type'] == ZBX_POSTTYPE_XML
2330					&& simplexml_load_string($posts, null, LIBXML_IMPORT_FLAGS) === false) {
2331				$errors = libxml_get_errors();
2332				libxml_clear_errors();
2333
2334				if (!$errors) {
2335					self::exception(ZBX_API_ERROR_PARAMETERS,
2336						_s('Invalid parameter "%1$s": %2$s.', 'posts', _('XML is expected'))
2337					);
2338				}
2339				else {
2340					$error = reset($errors);
2341					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 'posts',
2342						_s('%1$s [Line: %2$s | Column: %3$s]', '('.$error->code.') '.trim($error->message),
2343						$error->line, $error->column
2344					)));
2345				}
2346			}
2347
2348			if ($data['post_type'] == ZBX_POSTTYPE_JSON) {
2349				if (trim($posts, " \r\n") === '') {
2350					self::exception(ZBX_API_ERROR_PARAMETERS,
2351						_s('Invalid parameter "%1$s": %2$s.', 'posts', _('JSON is expected'))
2352					);
2353				}
2354
2355				$types = [
2356					'usermacros' => true,
2357					'macros_n' => [
2358						'{HOST.IP}', '{HOST.CONN}', '{HOST.DNS}', '{HOST.HOST}', '{HOST.NAME}', '{ITEM.ID}',
2359						'{ITEM.KEY}'
2360					]
2361				];
2362
2363				if ($this instanceof CItemPrototype) {
2364					$types['lldmacros'] = true;
2365				}
2366
2367				$matches = (new CMacrosResolverGeneral)->getMacroPositions($posts, $types);
2368
2369				$shift = 0;
2370
2371				foreach ($matches as $pos => $substr) {
2372					$posts = substr_replace($posts, '1', $pos + $shift, strlen($substr));
2373					$shift = $shift + 1 - strlen($substr);
2374				}
2375
2376				json_decode($posts);
2377
2378				if (json_last_error()) {
2379					self::exception(ZBX_API_ERROR_PARAMETERS,
2380						_s('Invalid parameter "%1$s": %2$s.', 'posts', _('JSON is expected'))
2381					);
2382				}
2383			}
2384		}
2385	}
2386}
2387