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 correlations.
24 */
25class CCorrelation extends CApiService {
26
27	protected $tableName = 'correlation';
28	protected $tableAlias = 'c';
29	protected $sortColumns = ['correlationid', 'name', 'status'];
30
31	/**
32	 * Set correlation default options in addition to global options.
33	 */
34	public function __construct() {
35		parent::__construct();
36
37		$this->getOptions = array_merge($this->getOptions, [
38			'selectFilter'		=> null,
39			'selectOperations'	=> null,
40			'correlationids'	=> null,
41			'editable'			=> false,
42			'sortfield'			=> '',
43			'sortorder'			=> ''
44		]);
45	}
46
47	/**
48	 * Get correlation data.
49	 *
50	 * @param array $options
51	 *
52	 * @return array|string
53	 */
54	public function get($options = []) {
55		$options = zbx_array_merge($this->getOptions, $options);
56
57		if ($options['editable'] && self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
58			return ($options['countOutput'] && !$options['groupCount']) ? '0' : [];
59		}
60
61		$res = DBselect($this->createSelectQuery($this->tableName(), $options), $options['limit']);
62
63		$result = [];
64		while ($row = DBfetch($res)) {
65			if ($options['countOutput']) {
66				if ($options['groupCount']) {
67					$result[] = $row;
68				}
69				else {
70					$result = $row['rowscount'];
71				}
72			}
73			else {
74				$result[$row[$this->pk()]] = $row;
75			}
76		}
77
78		if ($options['countOutput']) {
79			return $result;
80		}
81
82		if ($result) {
83			$result = $this->addRelatedObjects($options, $result);
84
85			foreach ($result as &$correlation) {
86				// Unset the fields that are returned in the filter.
87				unset($correlation['formula'], $correlation['evaltype']);
88
89				if ($options['selectFilter'] !== null) {
90					$filter = $this->unsetExtraFields(
91						[$correlation['filter']],
92						['conditions', 'formula', 'evaltype'],
93						$options['selectFilter']
94					);
95					$filter = reset($filter);
96
97					if (array_key_exists('conditions', $filter)) {
98						foreach ($filter['conditions'] as &$condition) {
99							unset($condition['correlationid'], $condition['corr_conditionid']);
100						}
101						unset($condition);
102					}
103
104					$correlation['filter'] = $filter;
105				}
106			}
107			unset($correlation);
108		}
109
110		// removing keys (hash -> array)
111		if (!$options['preservekeys']) {
112			$result = zbx_cleanHashes($result);
113		}
114
115		return $result;
116	}
117
118	/**
119	 * Add correlations.
120	 *
121	 * @param array  $correlations											An array of correlations.
122	 * @param string $correlations[]['name']								Correlation name.
123	 * @param string $correlations[]['description']							Correlation description (optional).
124	 * @param int    $correlations[]['status']								Correlation status (optional).
125	 *																		Possible values are:
126	 *																			0 - ZBX_CORRELATION_ENABLED;
127	 *																			1 - ZBX_CORRELATION_DISABLED.
128	 * @param array	 $correlations[]['filter']								Correlation filter that contains evaluation
129	 *																		method, formula and conditions.
130	 * @param int    $correlations[]['filter']['evaltype']					Correlation condition evaluation method.
131	 *																		Possible values are:
132	 *																			0 - CONDITION_EVAL_TYPE_AND_OR;
133	 *																			1 - CONDITION_EVAL_TYPE_AND;
134	 *																			2 - CONDITION_EVAL_TYPE_OR;
135	 *																			3 - CONDITION_EVAL_TYPE_EXPRESSION.
136	 * @param string $correlations[]['filter']['formula']					User-defined expression to be used for
137	 *																		evaluating conditions of filters with a
138	 *																		custom expression. Optional, but required
139	 *																		when evaluation method is:
140	 *																			3 - CONDITION_EVAL_TYPE_EXPRESSION.
141	 * @param array  $correlations[]['filter']['conditions']					An array of correlation conditions.
142	 * @param int    $correlations[]['filter']['conditions'][]['type']		Condition type.
143	 *																		Possible values are:
144	 *																			0 - ZBX_CORR_CONDITION_OLD_EVENT_TAG;
145	 *																			1 - ZBX_CORR_CONDITION_NEW_EVENT_TAG;
146	 *																			2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP;
147	 *																			3 - ZBX_CORR_CONDITION_EVENT_TAG_PAIR;
148	 *																			4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE;
149	 *																			5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE.
150	 * @param string $correlations[]['filter']['conditions'][]['formulaid']	Condition formula ID. Optional, but required
151	 *																		when evaluation method is:
152	 *																			3 - CONDITION_EVAL_TYPE_EXPRESSION.
153	 * @param string $correlations[]['filter']['conditions'][]['tag']		Correlation condition tag.
154	 * @param int	 $correlations[]['filter']['conditions'][]['operator']	Correlation condition operator. Optional,
155	 *																		but required when "type" is one of the following:
156	 *																			2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP;
157	 *																			4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE;
158	 *																			5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE.
159	 *																		Possible values depend on type:
160	 *																		for type ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
161	 *																			0 - CONDITION_OPERATOR_EQUAL
162	 *																			1 - CONDITION_OPERATOR_NOT_EQUAL
163	 *																		for types ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE
164	 *																		or ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
165	 *																			0 - CONDITION_OPERATOR_EQUAL;
166	 *																			1 - CONDITION_OPERATOR_NOT_EQUAL;
167	 *																			2 - CONDITION_OPERATOR_LIKE;
168	 *																			3 - CONDITION_OPERATOR_NOT_LIKE.
169	 * @param string $correlations[]['filter']['conditions'][]['groupid']	Correlation host group ID. Optional, but
170	 *																		required when "type" is:
171	 *																			2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP.
172	 * @param string $correlations[]['filter']['conditions'][]['newtag']	Correlation condition new (current) tag.
173	 * @param string $correlations[]['filter']['conditions'][]['oldtag']	Correlation condition old (target/matching)
174	 *																		tag.
175	 * @param string $correlations[]['filter']['conditions'][]['value']		Correlation condition tag value (optional).
176	 * @param array	 $correlations[]['operations']							An array of correlation operations.
177	 * @param int	 $correlations[]['operations'][]['type']				Correlation operation type.
178	 *																		Possible values are:
179	 *																			0 - ZBX_CORR_OPERATION_CLOSE_OLD;
180	 *																			1 - ZBX_CORR_OPERATION_CLOSE_NEW.
181	 *
182	 * @return array
183	 */
184	public function create($correlations) {
185		$correlations = zbx_toArray($correlations);
186
187		$this->validateCreate($correlations);
188
189		foreach ($correlations as &$correlation) {
190			$correlation['evaltype'] = $correlation['filter']['evaltype'];
191			unset($correlation['formula']);
192		}
193		unset($correlation);
194
195		// Insert correlations into DB, get back array with new correlation IDs.
196		$correlations = DB::save('correlation', $correlations);
197		$correlations = zbx_toHash($correlations, 'correlationid');
198
199		$conditions_to_create = [];
200		$operations_to_create = [];
201
202		// Collect conditions and operations to be created and set appropriate correlation ID.
203		foreach ($correlations as $correlationid => &$correlation) {
204			foreach ($correlation['filter']['conditions'] as $condition) {
205				$condition['correlationid'] = $correlationid;
206				$conditions_to_create[] = $condition;
207			}
208
209			foreach ($correlation['operations'] as $operation) {
210				$operation['correlationid'] = $correlationid;
211				$operations_to_create[] = $operation;
212			}
213		}
214		unset($correlation);
215
216		$conditions = $this->addConditions($conditions_to_create);
217
218		// Group back created correlation conditions by correlation ID to be used for updating correlation formula.
219		$conditions_for_correlations = [];
220		foreach ($conditions as $condition) {
221			$conditions_for_correlations[$condition['correlationid']][$condition['corr_conditionid']] = $condition;
222		}
223
224		// Update "formula" field if evaluation method is a custom expression.
225		foreach ($correlations as $correlationid => $correlation) {
226			if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
227				$this->updateFormula($correlationid, $correlation['filter']['formula'],
228					$conditions_for_correlations[$correlationid]
229				);
230			}
231		}
232
233		DB::save('corr_operation', $operations_to_create);
234
235		return ['correlationids' => array_keys($correlations)];
236	}
237
238	/**
239	 * Update correlations.
240	 *
241	 * @param array  $correlations											An array of correlations.
242	 * @param string $correlations[]['name']								Correlation name (optional).
243	 * @param string $correlations[]['description']							Correlation description (optional).
244	 * @param int	 $correlations[]['status']								Correlation status (optional).
245	 *																		Possible values are:
246	 *																			0 - ZBX_CORRELATION_ENABLED;
247	 *																			1 - ZBX_CORRELATION_DISABLED.
248	 * @param array	 $correlations[]['filter']								Correlation filter that contains evaluation
249	 *																		method, formula and conditions.
250	 * @param int	 $correlations[]['filter']['evaltype']					Correlation condition evaluation
251	 *																		method (optional).
252	 *																		Possible values are:
253	 *																			0 - CONDITION_EVAL_TYPE_AND_OR;
254	 *																			1 - CONDITION_EVAL_TYPE_AND;
255	 *																			2 - CONDITION_EVAL_TYPE_OR;
256	 *																			3 - CONDITION_EVAL_TYPE_EXPRESSION.
257	 * @param string $correlations[]['filter']['formula']					User-defined expression to be used for
258	 *																		evaluating conditions of filters with a
259	 *																		custom expression. Optional, but required
260	 *																		when evaluation method is changed to
261	 *																		CONDITION_EVAL_TYPE_EXPRESSION (or remains the same)
262	 *																		and new conditions are set.
263	 * @param array  $correlations[]['filter']['conditions']				An array of correlation conditions (optional).
264	 * @param int	 $correlations[]['filter']['conditions'][]['type']		Condition type. Optional, but required when
265	 *																		new conditions are set.
266	 *																		Possible values are:
267	 *																			0 - ZBX_CORR_CONDITION_OLD_EVENT_TAG;
268	 *																			1 - ZBX_CORR_CONDITION_NEW_EVENT_TAG;
269	 *																			2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP;
270	 *																			3 - ZBX_CORR_CONDITION_EVENT_TAG_PAIR;
271	 *																			4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE;
272	 *																			5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE.
273	 * @param string $correlations[]['filter']['conditions'][]['formulaid']	Condition formula ID. Optional, but required
274	 *																		when evaluation method is changed to
275	 *																		CONDITION_EVAL_TYPE_EXPRESSION (or remains the same)
276	 *																		and new conditions are set.
277	 * @param string $correlations[]['filter']['conditions'][]['tag']		Correlation condition tag.
278	 * @param int	 $correlations[]['filter']['conditions'][]['operator']	Correlation condition operator. Optional,
279	 *																		but required when "type" is changed to one
280	 *																		of the following:
281	 *																			2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP;
282	 *																			4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE;
283	 *																			5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE.
284	 *																		Possible values depend on type:
285	 *																		for type ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
286	 *																			0 - CONDITION_OPERATOR_EQUAL
287	 *																			1 - CONDITION_OPERATOR_NOT_EQUAL
288	 *																		for types ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE
289	 *																		or ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
290	 *																			0 - CONDITION_OPERATOR_EQUAL;
291	 *																			1 - CONDITION_OPERATOR_NOT_EQUAL;
292	 *																			2 - CONDITION_OPERATOR_LIKE;
293	 *																			3 - CONDITION_OPERATOR_NOT_LIKE.
294	 * @param string $correlations[]['filter']['conditions'][]['groupid']	Correlation host group ID. Optional, but
295	 *																		required when "type" is changed to:
296	 *																			2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP.
297	 * @param string $correlations[]['filter']['conditions'][]['newtag']	Correlation condition new (current) tag.
298	 * @param string $correlations[]['filter']['conditions'][]['oldtag']	Correlation condition old (target/matching)
299	 *																		tag.
300	 * @param string $correlations[]['filter']['conditions'][]['value']		Correlation condition tag value (optional).
301	 * @param array  $correlations[]['operations']							An array of correlation operations (optional).
302	 * @param int	 $correlations[]['operations'][]['type']				Correlation operation type (optional).
303	 *																		Possible values are:
304	 *																			0 - ZBX_CORR_OPERATION_CLOSE_OLD;
305	 *																			1 - ZBX_CORR_OPERATION_CLOSE_NEW.
306	 *
307	 * @return array
308	 */
309	public function update($correlations) {
310		$correlations = zbx_toArray($correlations);
311		$db_correlations = [];
312
313		$this->validateUpdate($correlations, $db_correlations);
314
315		$correlations_to_update = [];
316		$conditions_to_create = [];
317		$conditions_to_delete = [];
318		$operations_to_create = [];
319		$operations_to_delete = [];
320
321		foreach ($correlations as $correlation) {
322			$correlationid = $correlation['correlationid'];
323
324			unset($correlation['evaltype'], $correlation['formula'], $correlation['correlationid']);
325
326			$db_correlation = $db_correlations[$correlationid];
327
328			// Remove fields that have not been changed for correlations.
329			if (array_key_exists('name', $correlation) && $correlation['name'] === $db_correlation['name']) {
330				unset($correlation['name']);
331			}
332
333			if (array_key_exists('description', $correlation)
334					&& $correlation['description'] === $db_correlation['description']) {
335				unset($correlation['description']);
336			}
337
338			if (array_key_exists('status', $correlation) && $correlation['status'] == $db_correlation['status']) {
339				unset($correlation['status']);
340			}
341
342			$evaltype_changed = false;
343
344			// If the filter is set, something might have changed.
345			if (array_key_exists('filter', $correlation)) {
346				// Delete old correlation conditions and create new conditions.
347				if (array_key_exists('conditions', $correlation['filter'])) {
348					$conditions_to_delete[$correlationid] = true;
349
350					foreach ($correlation['filter']['conditions'] as $condition) {
351						$condition['correlationid'] = $correlationid;
352						$conditions_to_create[] = $condition;
353					}
354				}
355
356				// Check if evaltype has changed.
357				if (array_key_exists('evaltype', $correlation['filter'])) {
358					if ($correlation['filter']['evaltype'] != $db_correlation['filter']['evaltype']) {
359						// Clear formula field evaluation method if evaltype has changed.
360						$correlation['evaltype'] = $correlation['filter']['evaltype'];
361
362						if ($correlation['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
363							$correlation['formula'] = '';
364						}
365					}
366				}
367			}
368
369			// Delete old correlation operations and create new operations.
370			if (array_key_exists('operations', $correlation)) {
371				$operations_to_delete[$correlationid] = true;
372
373				foreach ($correlation['operations'] as $operation) {
374					$operation['correlationid'] = $correlationid;
375					$operations_to_create[] = $operation;
376				}
377			}
378
379			// Add values only if something is set for update.
380			if (array_key_exists('name', $correlation)
381					|| array_key_exists('description', $correlation)
382					|| array_key_exists('status', $correlation)
383					|| array_key_exists('evaltype', $correlation)) {
384				$correlations_to_update[] = [
385					'values' => $correlation,
386					'where' => ['correlationid' => $correlationid]
387				];
388			}
389		}
390
391		// Update correlations.
392		if ($correlations_to_update) {
393			DB::update('correlation', $correlations_to_update);
394		}
395
396		// Update conditions. Delete the old ones and create new ones.
397		if ($conditions_to_delete) {
398			DB::delete('corr_condition', ['correlationid' => array_keys($conditions_to_delete)]);
399		}
400
401		if ($conditions_to_create) {
402			$conditions = $this->addConditions($conditions_to_create);
403
404			// Group back created correlation conditions by correlation ID to be used for updating correlation formula.
405			$conditions_for_correlations = [];
406			foreach ($conditions as $condition) {
407				$conditions_for_correlations[$condition['correlationid']][$condition['corr_conditionid']] = $condition;
408			}
409
410			// Update "formula" field if evaluation method is a custom expression.
411			foreach ($correlations as $correlation) {
412				if (array_key_exists('filter', $correlation)) {
413					$db_correlation = $db_correlations[$correlation['correlationid']];
414
415					// Check if evaluation method has changed.
416					if (array_key_exists('evaltype', $correlation['filter'])) {
417						if ($correlation['filter']['evaltype'] != $db_correlation['filter']['evaltype']) {
418							// The new evaluation method will be saved to DB.
419							$correlation['evaltype'] = $correlation['filter']['evaltype'];
420
421							if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
422								// When evaluation method has changed, update the custom formula.
423								$this->updateFormula($correlation['correlationid'], $correlation['filter']['formula'],
424									$conditions_for_correlations[$correlation['correlationid']]
425								);
426							}
427						}
428						else {
429							/*
430							 * The evaluation method has not been changed, but it has been set and it's a custom
431							 * expression. The formula needs to be updated in this case.
432							 */
433							if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
434								$this->updateFormula($correlation['correlationid'], $correlation['filter']['formula'],
435									$conditions_for_correlations[$correlation['correlationid']]
436								);
437							}
438						}
439					}
440					elseif ($db_correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
441						/*
442						 * The evaluation method has not been changed and was not set. It's read from DB, but it's still
443						 * a custom expression and there are new conditions, so the formula needs to be updated.
444						 */
445						$this->updateFormula($correlation['correlationid'], $correlation['filter']['formula'],
446							$conditions_for_correlations[$correlation['correlationid']]
447						);
448					}
449				}
450			}
451		}
452
453		// Update operations. Delete the old ones and create new ones.
454		if ($operations_to_delete) {
455			DB::delete('corr_operation', ['correlationid' => array_keys($operations_to_delete)]);
456		}
457
458		if ($operations_to_create) {
459			DB::save('corr_operation', $operations_to_create);
460		}
461
462		return ['correlationids' => zbx_objectValues($correlations, 'correlationid')];
463	}
464
465	/**
466	 * Delete correlations.
467	 *
468	 * @param array $correlationids					An array of correlation IDs.
469	 *
470	 * @return array
471	 */
472	public function delete(array $correlationids) {
473		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
474			self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
475		}
476
477		$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
478		if (!CApiInputValidator::validate($api_input_rules, $correlationids, '/', $error)) {
479			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
480		}
481
482		$db_correlations = $this->get([
483			'output' => ['correlationid', 'name'],
484			'correlationids' => $correlationids,
485			'preservekeys' => true
486		]);
487
488		foreach ($correlationids as $correlationid) {
489			if (!array_key_exists($correlationid, $db_correlations)) {
490				self::exception(ZBX_API_ERROR_PERMISSIONS,
491					_('No permissions to referred object or it does not exist!')
492				);
493			}
494		}
495
496		DB::delete('correlation', ['correlationid' => $correlationids]);
497
498		$this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_CORRELATION, $db_correlations);
499
500		return ['correlationids' => $correlationids];
501	}
502
503	/**
504	 * Validates the input parameters for the create() method.
505	 *
506	 * @param array $correlations
507	 *
508	 * @throws APIException if the input is invalid.
509	 */
510	protected function validateCreate(array $correlations) {
511		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
512			self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only super admins can create correlations.'));
513		}
514
515		if (!$correlations) {
516			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
517		}
518
519		$required_fields = ['name', 'filter', 'operations'];
520
521		// Validate required fields and check if "name" is not empty.
522		foreach ($correlations as $correlation) {
523			if (!is_array($correlation)) {
524				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
525			}
526
527			// Check required parameters.
528			$missing_keys = array_diff($required_fields, array_keys($correlation));
529
530			if ($missing_keys) {
531				self::exception(ZBX_API_ERROR_PARAMETERS,
532					_s('Correlation is missing parameters: %1$s', implode(', ', $missing_keys))
533				);
534			}
535
536			// Validate "name" field.
537			if (is_array($correlation['name'])) {
538				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
539			}
540			elseif ($correlation['name'] === '' || $correlation['name'] === null || $correlation['name'] === false) {
541				self::exception(ZBX_API_ERROR_PARAMETERS, _('Correlation name cannot be empty.'));
542			}
543		}
544
545		// Check for duplicate names.
546		$duplicate = CArrayHelper::findDuplicate($correlations, 'name');
547		if ($duplicate) {
548			self::exception(ZBX_API_ERROR_PARAMETERS,
549				_s('Duplicate "%1$s" value "%2$s" for correlation.', 'name', $duplicate['name'])
550			);
551		}
552
553		// Check if correlation already exists.
554		$db_correlations = API::getApiService()->select('correlation', [
555			'output' => ['name'],
556			'filter' => ['name' => zbx_objectValues($correlations, 'name')],
557			'limit' => 1
558		]);
559
560		if ($db_correlations) {
561			self::exception(ZBX_API_ERROR_PARAMETERS,
562				_s('Correlation "%1$s" already exists.', $correlations[0]['name'])
563			);
564		}
565
566		// Set all necessary validators and parser before cycling each correlation.
567		$status_validator = new CLimitedSetValidator([
568			'values' => [ZBX_CORRELATION_ENABLED, ZBX_CORRELATION_DISABLED]
569		]);
570
571		$filter_evaltype_validator = new CLimitedSetValidator([
572			'values' => [CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_AND, CONDITION_EVAL_TYPE_AND_OR,
573				CONDITION_EVAL_TYPE_EXPRESSION
574			]
575		]);
576
577		$filter_condition_type_validator = new CLimitedSetValidator([
578			'values' => [ZBX_CORR_CONDITION_OLD_EVENT_TAG, ZBX_CORR_CONDITION_NEW_EVENT_TAG,
579				ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP,	ZBX_CORR_CONDITION_EVENT_TAG_PAIR,
580				ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE,	ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE
581			]
582		]);
583
584		$filter_condition_hg_operator_validator = new CLimitedSetValidator([
585			'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL]
586		]);
587
588		$filter_condition_tagval_operator_validator = new CLimitedSetValidator([
589			'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE,
590				CONDITION_OPERATOR_NOT_LIKE
591			]
592		]);
593
594		$filter_operations_validator = new CLimitedSetValidator([
595			'values' => [ZBX_CORR_OPERATION_CLOSE_OLD, ZBX_CORR_OPERATION_CLOSE_NEW]
596		]);
597
598		$parser = new CConditionFormula();
599
600		$groupids = [];
601
602		foreach ($correlations as $correlation) {
603			// Validate "status" field (optional).
604			if (array_key_exists('status', $correlation) && !$status_validator->validate($correlation['status'])) {
605				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
606					'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
607					$correlation['status'],
608					'status',
609					$correlation['name']
610				));
611			}
612
613			// Validate "filter" field.
614			if (!is_array($correlation['filter'])) {
615				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
616			}
617
618			// Validate "evaltype" field.
619			if (!array_key_exists('evaltype', $correlation['filter'])) {
620				self::exception(ZBX_API_ERROR_PARAMETERS,
621					_s('Incorrect type of calculation for correlation "%1$s".', $correlation['name'])
622				);
623			}
624			elseif (!$filter_evaltype_validator->validate($correlation['filter']['evaltype'])) {
625				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
626					'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
627					$correlation['filter']['evaltype'],
628					'evaltype',
629					$correlation['name']
630				));
631			}
632
633			// Check if conditions exist and that array is not empty.
634			if (!array_key_exists('conditions', $correlation['filter'])) {
635				self::exception(ZBX_API_ERROR_PARAMETERS,
636					_s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name'])
637				);
638			}
639
640			// Validate condition operators and other parameters depending on type.
641			$groupids = $this->validateConditions($correlation, $filter_condition_type_validator,
642				$filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator
643			);
644
645			// Validate custom expressions and formula.
646			if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
647				// Check formula.
648				if (!array_key_exists('formula', $correlation['filter'])) {
649					self::exception(ZBX_API_ERROR_PARAMETERS,
650						_s('No "%1$s" given for correlation "%2$s".', 'formula', $correlation['name'])
651					);
652				}
653
654				$this->validateFormula($correlation, $parser);
655
656				// Check condition formula IDs.
657				$this->validateConditionFormulaIDs($correlation, $parser);
658			}
659
660			// Validate operations.
661			$this->validateOperations($correlation, $filter_operations_validator);
662		}
663
664		// Validate collected group IDs if at least one of correlation conditions was "New event host group".
665		if ($groupids) {
666			$groups_count = API::HostGroup()->get([
667				'countOutput' => true,
668				'groupids' => array_keys($groupids)
669			]);
670
671			if ($groups_count != count($groupids)) {
672				self::exception(ZBX_API_ERROR_PERMISSIONS,
673					_('No permissions to referred object or it does not exist!')
674				);
675			}
676		}
677	}
678
679	/**
680	 * Validates the input parameters for the update() method.
681	 *
682	 * @param array $correlations
683	 * @param array $db_correlations
684	 *
685	 * @throws APIException if the input is invalid.
686	 */
687	protected function validateUpdate(array $correlations, array &$db_correlations) {
688		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
689			self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only super admins can update correlations.'));
690		}
691
692		if (!$correlations) {
693			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
694		}
695
696		// Validate given IDs.
697		$this->checkObjectIds($correlations, 'correlationid',
698			_('No "%1$s" given for correlation.'),
699			_('Empty correlation ID.'),
700			_('Incorrect correlation ID.')
701		);
702
703		$db_correlations = $this->get([
704			'output' => ['correlationid', 'name', 'description', 'status'],
705			'selectFilter' => ['formula', 'eval_formula', 'evaltype', 'conditions'],
706			'selectOperations' => ['type'],
707			'correlationids' => zbx_objectValues($correlations, 'correlationid'),
708			'preservekeys' => true
709		]);
710
711		$check_names = [];
712
713		foreach ($correlations as $correlation) {
714			// Check if this correlation exists.
715			if (!array_key_exists($correlation['correlationid'], $db_correlations)) {
716				self::exception(ZBX_API_ERROR_PERMISSIONS,
717					_('No permissions to referred object or it does not exist!')
718				);
719			}
720
721			// Validate "name" field (optional).
722			if (array_key_exists('name', $correlation)) {
723				if (is_array($correlation['name'])) {
724					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
725				}
726				elseif ($correlation['name'] === '' || $correlation['name'] === null || $correlation['name'] === false) {
727					self::exception(ZBX_API_ERROR_PARAMETERS, _('Correlation name cannot be empty.'));
728				}
729
730				if ($db_correlations[$correlation['correlationid']]['name'] !== $correlation['name']) {
731					$check_names[] = $correlation;
732				}
733			}
734		}
735
736		// Check only if names have changed.
737		if ($check_names) {
738			// Check for duplicate names.
739			$duplicate = CArrayHelper::findDuplicate($check_names, 'name');
740			if ($duplicate) {
741				self::exception(ZBX_API_ERROR_PARAMETERS,
742					_s('Duplicate "%1$s" value "%2$s" for correlation.', 'name', $duplicate['name'])
743				);
744			}
745
746			// Check if correlation already exists.
747			$db_correlation_names = API::getApiService()->select('correlation', [
748				'output' => ['correlationid', 'name'],
749				'filter' => ['name' => zbx_objectValues($check_names, 'name')]
750			]);
751			$db_correlation_names = zbx_toHash($db_correlation_names, 'name');
752
753			foreach ($check_names as $correlation) {
754				if (array_key_exists($correlation['name'], $db_correlation_names)
755						&& bccomp($db_correlation_names[$correlation['name']]['correlationid'],
756							$correlation['correlationid']) != 0) {
757					self::exception(ZBX_API_ERROR_PARAMETERS,
758						_s('Correlation "%1$s" already exists.', $correlation['name'])
759					);
760				}
761			}
762		}
763
764		// Set all necessary validators and parser before cycling each correlation.
765		$status_validator = new CLimitedSetValidator([
766			'values' => [ZBX_CORRELATION_ENABLED, ZBX_CORRELATION_DISABLED]
767		]);
768
769		$filter_evaltype_validator = new CLimitedSetValidator([
770			'values' => [CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_AND, CONDITION_EVAL_TYPE_AND_OR,
771				CONDITION_EVAL_TYPE_EXPRESSION
772			]
773		]);
774
775		$filter_condition_type_validator = new CLimitedSetValidator([
776			'values' => [ZBX_CORR_CONDITION_OLD_EVENT_TAG, ZBX_CORR_CONDITION_NEW_EVENT_TAG,
777				ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP,	ZBX_CORR_CONDITION_EVENT_TAG_PAIR,
778				ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE,	ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE
779			]
780		]);
781
782		$filter_condition_hg_operator_validator = new CLimitedSetValidator([
783			'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL]
784		]);
785
786		$filter_condition_tagval_operator_validator = new CLimitedSetValidator([
787			'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE,
788				CONDITION_OPERATOR_NOT_LIKE
789			]
790		]);
791
792		$filter_operations_validator = new CLimitedSetValidator([
793			'values' => [ZBX_CORR_OPERATION_CLOSE_OLD, ZBX_CORR_OPERATION_CLOSE_NEW]
794		]);
795
796		$parser = new CConditionFormula();
797
798		$groupids = [];
799
800		// Populate "name" field, if not set.
801		$correlations = $this->extendFromObjects(zbx_toHash($correlations, 'correlationid'), $db_correlations,
802			['name']
803		);
804
805		foreach ($correlations as $correlation) {
806			$db_correlation = $db_correlations[$correlation['correlationid']];
807
808			// Validate "status" field (optional).
809			if (array_key_exists('status', $correlation) && !$status_validator->validate($correlation['status'])) {
810				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
811					'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
812					$correlation['status'],
813					'status',
814					$correlation['name']
815				));
816			}
817
818			// Validate "filter" field. If filter is set, then something else must exist.
819			if (array_key_exists('filter', $correlation)) {
820				if (!is_array($correlation['filter']) || !$correlation['filter']) {
821					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
822				}
823
824				$evaltype_changed = false;
825
826				// Validate "evaltype" field.
827				if (array_key_exists('evaltype', $correlation['filter'])) {
828					// Check if evaltype has changed.
829					if ($correlation['filter']['evaltype'] != $db_correlation['filter']['evaltype']) {
830						if (!$filter_evaltype_validator->validate($correlation['filter']['evaltype'])) {
831							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
832								'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
833								$correlation['filter']['evaltype'],
834								'evaltype',
835								$correlation['name']
836							));
837						}
838
839						$evaltype_changed = true;
840					}
841				}
842				else {
843					// Populate "evaltype" field if not set, so we can later check for custom expressions.
844					$correlation['filter']['evaltype'] = $db_correlation['filter']['evaltype'];
845				}
846
847				if ($evaltype_changed) {
848					if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
849						if (!array_key_exists('formula', $correlation['filter'])) {
850							self::exception(ZBX_API_ERROR_PARAMETERS,
851								_s('No "%1$s" given for correlation "%2$s".', 'formula', $correlation['name'])
852							);
853						}
854
855						$this->validateFormula($correlation, $parser);
856
857						if (!array_key_exists('conditions', $correlation['filter'])) {
858							self::exception(ZBX_API_ERROR_PARAMETERS,
859								_s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name'])
860							);
861						}
862
863						$groupids = $this->validateConditions($correlation, $filter_condition_type_validator,
864							$filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator
865						);
866
867						$this->validateConditionFormulaIDs($correlation, $parser);
868					}
869					else {
870						if (!array_key_exists('conditions', $correlation['filter'])) {
871							self::exception(ZBX_API_ERROR_PARAMETERS,
872								_s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name'])
873							);
874						}
875
876						$groupids = $this->validateConditions($correlation, $filter_condition_type_validator,
877							$filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator
878						);
879					}
880				}
881				else {
882					if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
883						if (array_key_exists('formula', $correlation['filter'])) {
884							$this->validateFormula($correlation, $parser);
885
886							if (!array_key_exists('conditions', $correlation['filter'])) {
887								self::exception(ZBX_API_ERROR_PARAMETERS,
888									_s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name'])
889								);
890							}
891
892							$groupids = $this->validateConditions($correlation, $filter_condition_type_validator,
893								$filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator
894							);
895
896							$this->validateConditionFormulaIDs($correlation, $parser);
897						}
898						elseif (array_key_exists('conditions', $correlation['filter'])) {
899							self::exception(ZBX_API_ERROR_PARAMETERS,
900								_s('No "%1$s" given for correlation "%2$s".', 'formula', $correlation['name'])
901							);
902						}
903					}
904					elseif (array_key_exists('conditions', $correlation['filter'])) {
905						$groupids = $this->validateConditions($correlation, $filter_condition_type_validator,
906							$filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator
907						);
908					}
909				}
910			}
911
912			// Validate operations (optional).
913			if (array_key_exists('operations', $correlation)) {
914				$this->validateOperations($correlation, $filter_operations_validator);
915			}
916		}
917
918		// Validate collected group IDs if at least one of correlation conditions was "New event host group".
919		if ($groupids) {
920			$groups_count = API::HostGroup()->get([
921				'countOutput' => true,
922				'groupids' => array_keys($groupids)
923			]);
924
925			if ($groups_count != count($groupids)) {
926				self::exception(ZBX_API_ERROR_PERMISSIONS,
927					_('No permissions to referred object or it does not exist!')
928				);
929			}
930		}
931	}
932
933	/**
934	 * Converts a formula with letters to a formula with IDs and updates it.
935	 *
936	 * @param string 	$correlationid
937	 * @param string 	$formula_with_letters		Formula with letters.
938	 * @param array 	$conditions
939	 */
940	protected function updateFormula($correlationid, $formula_with_letters, array $conditions) {
941		$formulaid_to_conditionid = [];
942
943		foreach ($conditions as $condition) {
944			$formulaid_to_conditionid[$condition['formulaid']] = $condition['corr_conditionid'];
945		}
946		$formula = CConditionHelper::replaceLetterIds($formula_with_letters, $formulaid_to_conditionid);
947
948		DB::updateByPk('correlation', $correlationid, ['formula' => $formula]);
949	}
950
951	/**
952	 * Validate correlation conditions. Check the "conditions" array, check the "type" field and other fields that
953	 * depend on it. As a result return host group IDs that need to be validated afterwards. Otherwise don't return
954	 * anything, just throw an error.
955	 *
956	 * @param array					$correlation											One correlation containing the conditions.
957	 * @param string				$correlation['name']									Correlation name for error messages.
958	 * @param array					$correlation['filter']									Correlation filter array containing	the conditions.
959	 * @param array					$correlation['filter']['conditions']					An array of correlation conditions.
960	 * @param int					$correlation['filter']['conditions'][]['type']			Condition type.
961	 *																						Possible values are:
962	 *																							0 - ZBX_CORR_CONDITION_OLD_EVENT_TAG;
963	 *																							1 - ZBX_CORR_CONDITION_NEW_EVENT_TAG;
964	 *																							2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP;
965	 *																							3 - ZBX_CORR_CONDITION_EVENT_TAG_PAIR;
966	 *																							4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE;
967	 *																							5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE.
968	 * @param int					$correlation['filter']['conditions'][]['operator']		Correlation condition operator.
969	 *																						Possible values when "type"
970	 *																						is one of the following:
971	 *																							2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP;
972	 *																							4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE;
973	 *																							5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE.
974	 *																						Possible values depend on type:
975	 *																							for type ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
976	 *																								0 - CONDITION_OPERATOR_EQUAL
977	 *																								1 - CONDITION_OPERATOR_NOT_EQUAL
978	 *																							for types ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE
979	 *																							or ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
980	 *																								0 - CONDITION_OPERATOR_EQUAL;
981	 *																								1 - CONDITION_OPERATOR_NOT_EQUAL;
982	 *																								2 - CONDITION_OPERATOR_LIKE;
983	 *																								3 - CONDITION_OPERATOR_NOT_LIKE.
984	 * @param string				$correlations['filter']['conditions'][]['groupid']		Correlation host group ID.
985	 *																						Required when "type" is:
986	 *																							2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP.
987	 * @param CLimitedSetValidator	$filter_condition_type_validator						Validator for conditype type.
988	 * @param CLimitedSetValidator	$filter_condition_hg_operator_validator					Validator for host group operator.
989	 * @param CLimitedSetValidator	$filter_condition_tagval_operator_validator				Validator for tag value operator.
990	 *
991	 * @throws APIException if the input is invalid.
992	 *
993	 * @return array
994	 */
995	protected function validateConditions(array $correlation, CLimitedSetValidator $filter_condition_type_validator,
996			CLimitedSetValidator $filter_condition_hg_operator_validator,
997			CLimitedSetValidator $filter_condition_tagval_operator_validator) {
998		if (!$correlation['filter']['conditions']) {
999			self::exception(ZBX_API_ERROR_PARAMETERS,
1000				_s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name'])
1001			);
1002		}
1003
1004		if (!is_array($correlation['filter']['conditions'])) {
1005			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1006		}
1007
1008		$groupids = [];
1009		$formulaIds = [];
1010		$conditions = [];
1011
1012		foreach ($correlation['filter']['conditions'] as $condition) {
1013			if (!array_key_exists('type', $condition)) {
1014				self::exception(ZBX_API_ERROR_PARAMETERS,
1015					_s('No condition type given for correlation "%1$s".', $correlation['name'])
1016				);
1017			}
1018			elseif (is_array($condition['type'])) {
1019				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1020			}
1021			elseif (!$filter_condition_type_validator->validate($condition['type'])) {
1022				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1023					'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
1024					$condition['type'],
1025					'type',
1026					$correlation['name']
1027				));
1028			}
1029
1030			switch ($condition['type']) {
1031				case ZBX_CORR_CONDITION_OLD_EVENT_TAG:
1032				case ZBX_CORR_CONDITION_NEW_EVENT_TAG:
1033					if (!array_key_exists('tag', $condition)) {
1034						self::exception(ZBX_API_ERROR_PARAMETERS,
1035							_s('No "%1$s" given for correlation "%2$s".', 'tag', $correlation['name'])
1036						);
1037					}
1038					elseif (is_array($condition['tag'])) {
1039						self::exception(ZBX_API_ERROR_PARAMETERS,
1040							_('Incorrect arguments passed to function.')
1041						);
1042					}
1043					elseif ($condition['tag'] === '' || $condition['tag'] === null || $condition['tag'] === false) {
1044						self::exception(ZBX_API_ERROR_PARAMETERS,
1045							_s('Incorrect value for field "%1$s": %2$s.', 'tag', _('cannot be empty'))
1046						);
1047					}
1048					break;
1049
1050				case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
1051					if (array_key_exists('operator', $condition)) {
1052						if (is_array($condition['operator'])) {
1053							self::exception(ZBX_API_ERROR_PARAMETERS,
1054								_('Incorrect arguments passed to function.')
1055							);
1056						}
1057						elseif (!$filter_condition_hg_operator_validator->validate($condition['operator'])) {
1058							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1059								'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
1060								$condition['operator'],
1061								'operator',
1062								$correlation['name']
1063							));
1064						}
1065					}
1066
1067					if (!array_key_exists('groupid', $condition)) {
1068						self::exception(ZBX_API_ERROR_PARAMETERS,
1069							_s('No "%1$s" given for correlation "%2$s".', 'groupid', $correlation['name'])
1070						);
1071					}
1072					elseif (is_array($condition['groupid'])) {
1073						self::exception(ZBX_API_ERROR_PARAMETERS,
1074							_('Incorrect arguments passed to function.')
1075						);
1076					}
1077					elseif ($condition['groupid'] === '' || $condition['groupid'] === null
1078							|| $condition['groupid'] === false) {
1079						self::exception(ZBX_API_ERROR_PARAMETERS,
1080							_s('Incorrect value for field "%1$s": %2$s.', 'groupid', _('cannot be empty'))
1081						);
1082					}
1083
1084					$groupids[$condition['groupid']] = true;
1085					break;
1086
1087				case ZBX_CORR_CONDITION_EVENT_TAG_PAIR:
1088					if (!array_key_exists('oldtag', $condition)) {
1089						self::exception(ZBX_API_ERROR_PARAMETERS,
1090							_s('No "%1$s" given for correlation "%2$s".', 'oldtag', $correlation['name'])
1091						);
1092					}
1093					elseif (is_array($condition['oldtag'])) {
1094						self::exception(ZBX_API_ERROR_PARAMETERS,
1095							_('Incorrect arguments passed to function.')
1096						);
1097					}
1098					elseif ($condition['oldtag'] === '' || $condition['oldtag'] === null
1099							|| $condition['oldtag'] === false) {
1100						self::exception(ZBX_API_ERROR_PARAMETERS,
1101							_s('Incorrect value for field "%1$s": %2$s.', 'oldtag', _('cannot be empty'))
1102						);
1103					}
1104
1105					if (!array_key_exists('newtag', $condition)) {
1106						self::exception(ZBX_API_ERROR_PARAMETERS,
1107							_s('No "%1$s" given for correlation "%2$s".', 'newtag', $correlation['name'])
1108						);
1109					}
1110					elseif (is_array($condition['newtag'])) {
1111						self::exception(ZBX_API_ERROR_PARAMETERS,
1112							_('Incorrect arguments passed to function.')
1113						);
1114					}
1115					elseif ($condition['newtag'] === '' || $condition['newtag'] === null
1116							|| $condition['newtag'] === false) {
1117						self::exception(ZBX_API_ERROR_PARAMETERS,
1118							_s('Incorrect value for field "%1$s": %2$s.', 'newtag', _('cannot be empty'))
1119						);
1120					}
1121					break;
1122
1123				case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE:
1124				case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
1125					if (!array_key_exists('tag', $condition)) {
1126						self::exception(ZBX_API_ERROR_PARAMETERS,
1127							_s('No "%1$s" given for correlation "%2$s".', 'tag', $correlation['name'])
1128						);
1129					}
1130					elseif (is_array($condition['tag'])) {
1131						self::exception(ZBX_API_ERROR_PARAMETERS,
1132							_('Incorrect arguments passed to function.')
1133						);
1134					}
1135					elseif ($condition['tag'] === '' || $condition['tag'] === null || $condition['tag'] === false) {
1136						self::exception(ZBX_API_ERROR_PARAMETERS,
1137							_s('Incorrect value for field "%1$s": %2$s.', 'tag', _('cannot be empty'))
1138						);
1139					}
1140
1141					if (array_key_exists('operator', $condition)) {
1142						if (is_array($condition['operator'])) {
1143							self::exception(ZBX_API_ERROR_PARAMETERS,
1144								_('Incorrect arguments passed to function.')
1145							);
1146						}
1147						elseif (!$filter_condition_tagval_operator_validator->validate($condition['operator'])) {
1148							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1149								'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
1150								$condition['operator'],
1151								'operator',
1152								$correlation['name']
1153							));
1154						}
1155					}
1156					break;
1157			}
1158
1159			if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1160				if (array_key_exists($condition['formulaid'], $formulaIds)) {
1161					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1162						'Duplicate "%1$s" value "%2$s" for correlation "%3$s".', 'formulaid', $condition['formulaid'],
1163							$correlation['name']
1164					));
1165				}
1166				else {
1167					$formulaIds[$condition['formulaid']] = true;
1168				}
1169			}
1170
1171			unset($condition['formulaid']);
1172			$conditions[] = $condition;
1173		}
1174
1175		if (count($conditions) != count(array_unique($conditions, SORT_REGULAR))) {
1176			self::exception(ZBX_API_ERROR_PARAMETERS,
1177				_s('Conditions duplicates for correlation "%1$s".', $correlation['name'])
1178			);
1179		}
1180
1181		return $groupids;
1182	}
1183
1184	/**
1185	 * Validate correlation filter "formula" field.
1186	 *
1187	 * @param array				$correlation						One correlation containing the filter, formula and name.
1188	 * @param string			$correlation['name']				Correlation name for error messages.
1189	 * @param array				$correlation['filter']				Correlation filter array containing the formula.
1190	 * @param string			$correlation['filter']['formula']	User-defined expression to be used for evaluating
1191	 *																conditions of filters with a custom expression.
1192	 * @param CConditionFormula $parser								Condition formula parser.
1193	 *
1194	 * @throws APIException if the input is invalid.
1195	 */
1196	protected function validateFormula(array $correlation, CConditionFormula $parser) {
1197		if (is_array($correlation['filter']['formula'])) {
1198			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1199		}
1200
1201		if (!$parser->parse($correlation['filter']['formula'])) {
1202			self::exception(ZBX_API_ERROR_PARAMETERS,
1203				_s('Incorrect custom expression "%2$s" for correlation "%1$s": %3$s.',
1204					$correlation['name'], $correlation['filter']['formula'], $parser->error
1205				)
1206			);
1207		}
1208	}
1209
1210	/**
1211	 * Validate correlation condition formula IDs. Check the "formulaid" field and that formula matches the conditions.
1212	 *
1213	 * @param array				$correlation										One correlation containing array of
1214	 *																				conditions and name.
1215	 * @param string			$correlation['name']								Correlation name for error messages.
1216	 * @param array				$correlation['filter']								Correlation filter array containing
1217	 *																				the conditions.
1218	 * @param array				$correlation['filter']['conditions']				An array of correlation conditions.
1219	 * @param string			$correlation['filter']['conditions'][]['formulaid']	Condition formula ID.
1220	 * @param CConditionFormula $parser												Condition formula parser.
1221	 *
1222	 * @throws APIException if the input is invalid.
1223	 */
1224	protected function validateConditionFormulaIDs(array $correlation, CConditionFormula $parser) {
1225		foreach ($correlation['filter']['conditions'] as $condition) {
1226			if (!array_key_exists('formulaid', $condition)) {
1227				self::exception(ZBX_API_ERROR_PARAMETERS,
1228					_s('No "%1$s" given for correlation "%2$s".', 'formulaid', $correlation['name'])
1229				);
1230			}
1231			elseif (is_array($condition['formulaid'])) {
1232				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1233			}
1234			elseif (!preg_match('/[A-Z]+/', $condition['formulaid'])) {
1235				self::exception(ZBX_API_ERROR_PARAMETERS,
1236					_s('Incorrect filter condition formula ID given for correlation "%1$s".', $correlation['name'])
1237				);
1238			}
1239		}
1240
1241		$conditions = zbx_toHash($correlation['filter']['conditions'], 'formulaid');
1242		$constants = array_unique(zbx_objectValues($parser->constants, 'value'));
1243
1244		foreach ($constants as $constant) {
1245			if (!array_key_exists($constant, $conditions)) {
1246				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1247					'Condition "%2$s" used in formula "%3$s" for correlation "%1$s" is not defined.',
1248					$correlation['name'], $constant, $correlation['filter']['formula']
1249				));
1250			}
1251
1252			unset($conditions[$constant]);
1253		}
1254
1255		// Check that the "conditions" array has no unused conditions.
1256		if ($conditions) {
1257			$condition = reset($conditions);
1258			self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1259				'Condition "%2$s" is not used in formula "%3$s" for correlation "%1$s".', $correlation['name'],
1260				$condition['formulaid'], $correlation['filter']['formula']
1261			));
1262		}
1263	}
1264
1265	/**
1266	 * Validate correlation operations. Check if "operations" is valid, if "type" is valid and there are no duplicate
1267	 * operations in correlation.
1268	 *
1269	 * @param array					$correlation						One correlation containing array of operations and name.
1270	 * @param string				$correlation['name']				Correlation name for error messages.
1271	 * @param array					$correlation['operations']			An array of correlation operations.
1272	 * @param int					$correlation['operations']['type']	Correlation operation type.
1273	 *																	Possible values are:
1274	 *																		0 - ZBX_CORR_OPERATION_CLOSE_OLD;
1275	 *																		1 - ZBX_CORR_OPERATION_CLOSE_NEW.
1276	 * @param CLimitedSetValidator	$filter_operations_validator		Operations validator.
1277	 *
1278	 * @throws APIException if the input is invalid.
1279	 */
1280	protected function validateOperations(array $correlation, CLimitedSetValidator $filter_operations_validator) {
1281		if (!is_array($correlation['operations'])) {
1282			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1283		}
1284		elseif (!$correlation['operations']) {
1285			self::exception(ZBX_API_ERROR_PARAMETERS,
1286				_s('No "%1$s" given for correlation "%2$s".', 'operations', $correlation['name'])
1287			);
1288		}
1289
1290		foreach ($correlation['operations'] as $operation) {
1291			if (!array_key_exists('type', $operation)) {
1292				self::exception(ZBX_API_ERROR_PARAMETERS,
1293					_s('No operation type given for correlation "%1$s".', $correlation['name'])
1294				);
1295			}
1296			elseif (is_array($operation['type'])) {
1297				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1298			}
1299
1300			if (!$filter_operations_validator->validate($operation['type'])) {
1301				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1302					'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".',
1303					$operation['type'],
1304					'type',
1305					$correlation['name']
1306				));
1307			}
1308		}
1309
1310		// Check that same operation types do not repeat.
1311		$duplicate = CArrayHelper::findDuplicate($correlation['operations'], 'type');
1312		if ($duplicate) {
1313			self::exception(ZBX_API_ERROR_PARAMETERS,
1314				_s('Duplicate "%1$s" value "%2$s" for correlation "%3$s".', 'type', $duplicate['type'],
1315					$correlation['name']
1316				)
1317			);
1318		}
1319	}
1320
1321	/**
1322	 * Insert correlation condition values to their corresponding DB tables.
1323	 *
1324	 * @param array $conditions		An array of conditions to create.
1325	 *
1326	 * @return array
1327	 */
1328	protected function addConditions(array $conditions) {
1329		$conditions = DB::save('corr_condition', $conditions);
1330
1331		$corr_condition_tags_to_create = [];
1332		$corr_condition_hostgroups_to_create = [];
1333		$corr_condition_tag_pairs_to_create = [];
1334		$corr_condition_tag_values_to_create = [];
1335
1336		foreach ($conditions as $condition) {
1337			switch ($condition['type']) {
1338				case ZBX_CORR_CONDITION_OLD_EVENT_TAG:
1339				case ZBX_CORR_CONDITION_NEW_EVENT_TAG:
1340					$corr_condition_tags_to_create[] = $condition;
1341					break;
1342
1343				case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
1344					$corr_condition_hostgroups_to_create[] = $condition;
1345					break;
1346
1347				case ZBX_CORR_CONDITION_EVENT_TAG_PAIR:
1348					$corr_condition_tag_pairs_to_create[] = $condition;
1349					break;
1350
1351				case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE:
1352				case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
1353					$corr_condition_tag_values_to_create[] = $condition;
1354					break;
1355			}
1356		}
1357
1358		if ($corr_condition_tags_to_create) {
1359			DB::insert('corr_condition_tag', $corr_condition_tags_to_create, false);
1360		}
1361
1362		if ($corr_condition_hostgroups_to_create) {
1363			DB::insert('corr_condition_group', $corr_condition_hostgroups_to_create, false);
1364		}
1365
1366		if ($corr_condition_tag_pairs_to_create) {
1367			DB::insert('corr_condition_tagpair', $corr_condition_tag_pairs_to_create, false);
1368		}
1369
1370		if ($corr_condition_tag_values_to_create) {
1371			DB::insert('corr_condition_tagvalue', $corr_condition_tag_values_to_create, false);
1372		}
1373
1374		return $conditions;
1375	}
1376
1377	/**
1378	 * Apply query output options.
1379	 *
1380	 * @param type $table_name
1381	 * @param type $table_alias
1382	 * @param array $options
1383	 * @param array $sql_parts
1384	 *
1385	 * @return array
1386	 */
1387	protected function applyQueryOutputOptions($table_name, $table_alias, array $options, array $sql_parts) {
1388		$sql_parts = parent::applyQueryOutputOptions($table_name, $table_alias, $options, $sql_parts);
1389
1390		if (!$options['countOutput']) {
1391			// Add filter fields.
1392			if ($this->outputIsRequested('formula', $options['selectFilter'])
1393					|| $this->outputIsRequested('eval_formula', $options['selectFilter'])
1394					|| $this->outputIsRequested('conditions', $options['selectFilter'])) {
1395
1396				$sql_parts = $this->addQuerySelect('c.formula', $sql_parts);
1397				$sql_parts = $this->addQuerySelect('c.evaltype', $sql_parts);
1398			}
1399
1400			if ($this->outputIsRequested('evaltype', $options['selectFilter'])) {
1401				$sql_parts = $this->addQuerySelect('c.evaltype', $sql_parts);
1402			}
1403		}
1404
1405		return $sql_parts;
1406	}
1407
1408	/**
1409	 * Extend result with requested objects.
1410	 *
1411	 * @param array $options
1412	 * @param array $result
1413	 *
1414	 * @return array
1415	 */
1416	protected function addRelatedObjects(array $options, array $result) {
1417		$result = parent::addRelatedObjects($options, $result);
1418
1419		$correlationids = array_keys($result);
1420
1421		// Adding formulas and conditions.
1422		if ($options['selectFilter'] !== null) {
1423			$formula_requested = $this->outputIsRequested('formula', $options['selectFilter']);
1424			$eval_formula_requested = $this->outputIsRequested('eval_formula', $options['selectFilter']);
1425			$conditions_requested = $this->outputIsRequested('conditions', $options['selectFilter']);
1426
1427			$filters = [];
1428
1429			if ($options['selectFilter']) {
1430				foreach ($result as $correlation) {
1431					$filters[$correlation['correlationid']] = [
1432						'evaltype' => $correlation['evaltype'],
1433						'formula' => array_key_exists('formula', $correlation) ? $correlation['formula'] : '',
1434						'conditions' => []
1435					];
1436				}
1437
1438				if ($formula_requested || $eval_formula_requested || $conditions_requested) {
1439					$sql = 'SELECT c.correlationid,c.corr_conditionid,c.type,ct.tag AS ct_tag,'.
1440								'cg.operator AS cg_operator,cg.groupid,ctp.oldtag,ctp.newtag,ctv.tag AS ctv_tag,'.
1441								'ctv.operator AS ctv_operator,ctv.value'.
1442							' FROM corr_condition c'.
1443							' LEFT JOIN corr_condition_tag ct ON ct.corr_conditionid = c.corr_conditionid'.
1444							' LEFT JOIN corr_condition_group cg ON cg.corr_conditionid = c.corr_conditionid'.
1445							' LEFT JOIN corr_condition_tagpair ctp ON ctp.corr_conditionid = c.corr_conditionid'.
1446							' LEFT JOIN corr_condition_tagvalue ctv ON ctv.corr_conditionid = c.corr_conditionid'.
1447							' WHERE '.dbConditionInt('c.correlationid', $correlationids);
1448
1449					$db_corr_conditions = DBselect($sql);
1450
1451					while ($row = DBfetch($db_corr_conditions)) {
1452						$fields = [
1453							'corr_conditionid' => $row['corr_conditionid'],
1454							'correlationid' => $row['correlationid'],
1455							'type' => $row['type']
1456						];
1457
1458						switch ($row['type']) {
1459							case ZBX_CORR_CONDITION_OLD_EVENT_TAG:
1460							case ZBX_CORR_CONDITION_NEW_EVENT_TAG:
1461								$fields['tag'] = $row['ct_tag'];
1462								break;
1463
1464							case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
1465								$fields['operator'] = $row['cg_operator'];
1466								$fields['groupid'] = $row['groupid'];
1467								break;
1468
1469							case ZBX_CORR_CONDITION_EVENT_TAG_PAIR:
1470								$fields['oldtag'] = $row['oldtag'];
1471								$fields['newtag'] = $row['newtag'];
1472								break;
1473
1474							case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE:
1475							case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
1476								$fields['tag'] = $row['ctv_tag'];
1477								$fields['operator'] = $row['ctv_operator'];
1478								$fields['value'] = $row['value'];
1479								break;
1480						}
1481
1482						$filters[$row['correlationid']]['conditions'][] = $fields;
1483					}
1484
1485					foreach ($filters as &$filter) {
1486						// In case of a custom expression, use the given formula.
1487						if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1488							$formula = $filter['formula'];
1489						}
1490						// In other cases generate the formula automatically.
1491						else {
1492							$conditions = $filter['conditions'];
1493							CArrayHelper::sort($conditions, ['type']);
1494							$conditions_for_formula = [];
1495
1496							foreach ($conditions as $condition) {
1497								$conditions_for_formula[$condition['corr_conditionid']] = $condition['type'];
1498							}
1499
1500							$formula = CConditionHelper::getFormula($conditions_for_formula, $filter['evaltype']);
1501						}
1502
1503						// Generate formulaids from the effective formula.
1504						$formulaids = CConditionHelper::getFormulaIds($formula);
1505
1506						foreach ($filter['conditions'] as &$condition) {
1507							$condition['formulaid'] = $formulaids[$condition['corr_conditionid']];
1508						}
1509						unset($condition);
1510
1511						// Generated a letter based formula only for actions with custom expressions.
1512						if ($formula_requested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
1513							$filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids);
1514						}
1515
1516						if ($eval_formula_requested) {
1517							$filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids);
1518						}
1519					}
1520					unset($filter);
1521				}
1522			}
1523			else {
1524				// In case no fields are actually selected in "filter", return empty array.
1525				foreach ($result as $correlation) {
1526					$filters[$correlation['correlationid']] = [];
1527				}
1528			}
1529
1530			// Add filters to the result.
1531			foreach ($result as &$correlation) {
1532				$correlation['filter'] = $filters[$correlation['correlationid']];
1533			}
1534			unset($correlation);
1535		}
1536
1537		// Adding operations.
1538		if ($options['selectOperations'] !== null && $options['selectOperations'] != API_OUTPUT_COUNT) {
1539			$operations = API::getApiService()->select('corr_operation', [
1540				'output' => $this->outputExtend($options['selectOperations'], [
1541					'correlationid', 'corr_operationid', 'type'
1542				]),
1543				'filter' => ['correlationid' => $correlationids],
1544				'preservekeys' => true
1545			]);
1546			$relation_map = $this->createRelationMap($operations, 'correlationid', 'corr_operationid');
1547
1548			foreach ($operations as &$operation) {
1549				unset($operation['correlationid'], $operation['corr_operationid']);
1550			}
1551			unset($operation);
1552
1553			$result = $relation_map->mapMany($result, $operations, 'operations');
1554		}
1555
1556		return $result;
1557	}
1558}
1559