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
22require_once dirname(__FILE__).'/include/config.inc.php';
23require_once dirname(__FILE__).'/include/forms.inc.php';
24require_once dirname(__FILE__).'/include/correlation.inc.php';
25
26$page['title'] = _('Event correlation rules');
27$page['file'] = 'correlation.php';
28$page['scripts'] = ['multiselect.js'];
29
30require_once dirname(__FILE__).'/include/page_header.php';
31// VAR									TYPE	OPTIONAL	FLAGS	VALIDATION	EXCEPTION
32$fields = [
33	'correlationid' =>					[T_ZBX_INT, O_OPT, P_SYS,	DB_ID,		'isset({form}) && {form} == "update"'],
34	'name' =>							[T_ZBX_STR, O_OPT, null,	NOT_EMPTY,	'isset({add}) || isset({update})',
35											_('Name')
36										],
37	'description' =>					[T_ZBX_STR, O_OPT, null,	null,		null],
38	'evaltype' =>						[T_ZBX_INT, O_OPT, null,
39											IN([CONDITION_EVAL_TYPE_AND_OR, CONDITION_EVAL_TYPE_AND,
40												CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_EXPRESSION
41											]),
42											'isset({add}) || isset({update})'
43										],
44	'formula' =>						[T_ZBX_STR, O_OPT, null,   null,		'isset({add}) || isset({update})'],
45	'status' =>							[T_ZBX_INT, O_OPT, null,
46											IN([ZBX_CORRELATION_ENABLED, ZBX_CORRELATION_DISABLED]),
47											null
48										],
49	'g_correlationid' =>				[T_ZBX_INT, O_OPT, null,	DB_ID,		'isset({action})'],
50	'conditions' =>						[null,		O_OPT,	null,	null,		null],
51	'new_condition' =>					[null,		O_OPT,	null,	null,		'isset({add_condition})'],
52	'operations' =>						[null,		O_OPT,	null,	null,		null],
53	'edit_operationid' =>				[T_ZBX_STR, O_OPT,	P_ACT,	null,		null],
54	'new_operation' =>					[null,		O_OPT,	null,	null,		null],
55	// actions
56	'action' =>							[T_ZBX_STR, O_OPT, P_SYS|P_ACT,
57											IN('"correlation.massdelete","correlation.massdisable","correlation.massenable"'),
58											null
59										],
60	'add_condition' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
61	'add_operation' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
62	'add' =>							[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
63	'update' =>							[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
64	'delete' =>							[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
65	'cancel' =>							[T_ZBX_STR, O_OPT, P_SYS,		null,	null],
66	'form' =>							[T_ZBX_STR, O_OPT, P_SYS,		null,	null],
67	'form_refresh' =>					[T_ZBX_INT, O_OPT, null,		null,	null],
68	// filter
69	'filter_set' =>						[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
70	'filter_rst' =>						[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
71	'filter_name' =>					[T_ZBX_STR, O_OPT, null,	null,		null],
72	'filter_status' =>					[T_ZBX_INT, O_OPT, null,
73											IN([-1, ZBX_CORRELATION_ENABLED, ZBX_CORRELATION_DISABLED]),
74											null
75										],
76	// sort and sortorder
77	'sort' =>							[T_ZBX_STR, O_OPT, P_SYS, IN('"name","status"'),						null],
78	'sortorder' =>						[T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'),	null]
79];
80
81check_fields($fields);
82
83$correlationid = getRequest('correlationid');
84
85if ($correlationid !== null) {
86	$correlation = API::Correlation()->get([
87		'output' => [],
88		'correlationids' => [$correlationid],
89		'editable' => true
90	]);
91
92	if (!$correlation) {
93		access_deny();
94	}
95}
96
97if (hasRequest('action')) {
98	$correlations = API::Correlation()->get([
99		'output' => [],
100		'correlationids' => getRequest('g_correlationid'),
101		'editable' => true
102	]);
103
104	if (count($correlations) != count(getRequest('g_correlationid'))) {
105		uncheckTableRows(null, zbx_objectValues($correlations, 'correlationid'));
106	}
107}
108
109if (hasRequest('add') || hasRequest('update')) {
110	$correlation = [
111		'name' => getRequest('name'),
112		'description' => getRequest('description'),
113		'status' => getRequest('status', ZBX_CORRELATION_DISABLED),
114		'operations' => getRequest('operations', [])
115	];
116
117	$filter = [
118		'conditions' => getRequest('conditions', []),
119		'evaltype' => getRequest('evaltype')
120	];
121
122	if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
123		if (count($filter['conditions']) > 1) {
124			$filter['formula'] = getRequest('formula');
125		}
126		else {
127			// If only one or no conditions are left, reset the evaltype to "and/or" and clear the formula.
128			$filter['formula'] = '';
129			$filter['evaltype'] = CONDITION_EVAL_TYPE_AND_OR;
130		}
131	}
132	$correlation['filter'] = $filter;
133
134	if (hasRequest('update')) {
135		$correlation['correlationid'] = $correlationid;
136
137		$result = API::Correlation()->update($correlation);
138
139		$messageSuccess = _('Correlation updated');
140		$messageFailed = _('Cannot update correlation');
141	}
142	else {
143		$result = API::Correlation()->create($correlation);
144
145		$messageSuccess = _('Correlation added');
146		$messageFailed = _('Cannot add correlation');
147	}
148
149	if ($result) {
150		uncheckTableRows();
151		unset($_REQUEST['form']);
152	}
153	show_messages($result, $messageSuccess, $messageFailed);
154}
155elseif (hasRequest('delete') && hasRequest('correlationid')) {
156	$result = API::Correlation()->delete([getRequest('correlationid')]);
157
158	if ($result) {
159		unset($_REQUEST['form'], $_REQUEST['correlationid']);
160		uncheckTableRows();
161	}
162	show_messages($result, _('Correlation deleted'), _('Cannot delete correlation'));
163}
164elseif (hasRequest('add_condition') && hasRequest('new_condition')) {
165	$new_condition = getRequest('new_condition');
166
167	if ($new_condition) {
168		$conditions = getRequest('conditions', []);
169
170		// Add formulaid to new condition, so we can sort conditions.
171		$used_formulaids = zbx_objectValues($conditions, 'formulaid');
172		$new_condition['formulaid'] = CConditionHelper::getNextFormulaId($used_formulaids);
173		$used_formulaids[] = $new_condition['formulaid'];
174
175		// Check existing conditions and remove duplicate condition values.
176		$valid_conditions = [];
177		foreach ($conditions as $condition) {
178			$valid_conditions[] = $condition;
179
180			// Check if still exists in loop and if type is the same. Remove same values.
181			if (isset($new_condition) && $new_condition['type'] == $condition['type']) {
182				switch ($new_condition['type']) {
183					case ZBX_CORR_CONDITION_OLD_EVENT_TAG:
184					case ZBX_CORR_CONDITION_NEW_EVENT_TAG:
185						if ($new_condition['tag'] === $condition['tag']) {
186							unset($new_condition);
187						}
188						break;
189
190					case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
191						foreach ($new_condition['groupids'] as $i => $groupid) {
192							if ($condition['groupid'] == $groupid) {
193								unset($new_condition['groupids'][$i]);
194							}
195						}
196
197						// If no group IDs are left, remove condition (adding will be skipped).
198						if (!$new_condition['groupids']) {
199							unset($new_condition);
200						}
201						break;
202
203					case ZBX_CORR_CONDITION_EVENT_TAG_PAIR:
204						if ($new_condition['oldtag'] === $condition['oldtag']
205								&& $new_condition['newtag'] === $condition['newtag']) {
206							unset($new_condition);
207						}
208						break;
209
210					case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE:
211					case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
212						if ($new_condition['tag'] === $condition['tag']
213								&& $new_condition['value'] === $condition['value']) {
214							unset($new_condition);
215						}
216						break;
217				}
218			}
219		}
220
221		// Check if new condition is valid (tags cannot be empty) and host group IDs must be valid.
222		if (isset($new_condition)) {
223			switch ($new_condition['type']) {
224				case ZBX_CORR_CONDITION_OLD_EVENT_TAG:
225				case ZBX_CORR_CONDITION_NEW_EVENT_TAG:
226					if ($new_condition['tag'] === '') {
227						error(_s('Incorrect value for field "%1$s": %2$s.', 'tag', _('cannot be empty')));
228						show_error_message(_('Cannot add correlation condition'));
229					}
230					else {
231						$valid_conditions[] = $new_condition;
232					}
233					break;
234
235				case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP:
236					if (!$new_condition['groupids']) {
237						error(_s('Incorrect value for field "%1$s": %2$s.', 'groupid', _('cannot be empty')));
238						show_error_message(_('Cannot add correlation condition'));
239					}
240					else {
241						foreach ($new_condition['groupids'] as $groupid) {
242							if ($groupid == 0) {
243								error(_s('Incorrect value for field "%1$s": %2$s.', 'groupid', _('cannot be empty')));
244								show_error_message(_('Cannot add correlation condition'));
245							}
246							else {
247								$valid_conditions[] = [
248									'type' => $new_condition['type'],
249									'operator' => $new_condition['operator'],
250									'formulaid' => $new_condition['formulaid'],
251									'groupid' => $groupid
252								];
253
254								$new_condition['formulaid'] = CConditionHelper::getNextFormulaId($used_formulaids);
255								$used_formulaids[] = $new_condition['formulaid'];
256							}
257						}
258					}
259					break;
260
261				case ZBX_CORR_CONDITION_EVENT_TAG_PAIR:
262					if ($new_condition['oldtag'] === '') {
263						error(_s('Incorrect value for field "%1$s": %2$s.', 'oldtag', _('cannot be empty')));
264						show_error_message(_('Cannot add correlation condition'));
265					}
266					elseif ($new_condition['newtag'] === '') {
267						error(_s('Incorrect value for field "%1$s": %2$s.', 'newtag', _('cannot be empty')));
268						show_error_message(_('Cannot add correlation condition'));
269					}
270					else {
271						$valid_conditions[] = $new_condition;
272						unset($_REQUEST['new_condition']['oldtag']);
273						unset($_REQUEST['new_condition']['newtag']);
274					}
275					break;
276
277				case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE:
278				case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
279					if ($new_condition['tag'] === '') {
280						error(_s('Incorrect value for field "%1$s": %2$s.', 'tag', _('cannot be empty')));
281						show_error_message(_('Cannot add correlation condition'));
282					}
283					else {
284						$valid_conditions[] = $new_condition;
285						unset($_REQUEST['new_condition']['value']);
286					}
287					break;
288			}
289		}
290
291		$_REQUEST['conditions'] = $valid_conditions;
292	}
293}
294elseif (hasRequest('add_operation') && hasRequest('new_operation')) {
295	$new_operation = getRequest('new_operation');
296	$result = true;
297
298	$_REQUEST['operations'] = getRequest('operations', []);
299
300	$uniqOperations = [
301		ZBX_CORR_OPERATION_CLOSE_OLD => 0,
302		ZBX_CORR_OPERATION_CLOSE_NEW => 0
303	];
304
305	if (array_key_exists($new_operation['type'], $uniqOperations)) {
306		$uniqOperations[$new_operation['type']]++;
307
308		foreach ($_REQUEST['operations'] as $operationId => $operation) {
309			if (array_key_exists($operation['type'], $uniqOperations)
310					&& (!array_key_exists('id', $new_operation) || bccomp($new_operation['id'], $operationId) != 0)) {
311				$uniqOperations[$operation['type']]++;
312			}
313		}
314
315		if ($uniqOperations[$new_operation['type']] > 1) {
316			$result = false;
317			error(_s('Operation "%s" already exists.', corrOperationTypes($new_operation['type'])));
318			show_messages();
319		}
320	}
321
322	if ($result) {
323		if (array_key_exists('id', $new_operation)) {
324			$_REQUEST['operations'][$new_operation['id']] = $new_operation;
325		}
326		else {
327			$_REQUEST['operations'][] = $new_operation;
328		}
329
330		CArrayHelper::sort($_REQUEST['operations'], ['type']);
331	}
332
333	unset($_REQUEST['new_operation']);
334}
335elseif (hasRequest('action')
336		&& str_in_array(getRequest('action'), ['correlation.massenable', 'correlation.massdisable'])) {
337
338	$enable = (getRequest('action') === 'correlation.massenable');
339	$status = $enable ? ZBX_CORRELATION_ENABLED : ZBX_CORRELATION_DISABLED;
340
341	$correlations_to_update = [];
342	foreach (getRequest('g_correlationid') as $g_correlationid) {
343		$correlations_to_update[] = [
344			'correlationid' => $g_correlationid,
345			'status' => $status
346		];
347	}
348
349	$result = API::Correlation()->update($correlations_to_update);
350	$updated = 0;
351
352	if ($result) {
353		$updated = count($result['correlationids']);
354	}
355
356	$messageSuccess = $enable
357		? _n('Correlation enabled', 'Correlations enabled', $updated)
358		: _n('Correlation disabled', 'Correlations disabled', $updated);
359	$messageFailed = $enable
360		? _n('Cannot enable correlation', 'Cannot enable correlations', $updated)
361		: _n('Cannot disable correlation', 'Cannot disable correlations', $updated);
362
363	if ($result) {
364		uncheckTableRows();
365	}
366	show_messages($result, $messageSuccess, $messageFailed);
367}
368elseif (hasRequest('action') && getRequest('action') === 'correlation.massdelete') {
369	$result = API::Correlation()->delete(getRequest('g_correlationid'));
370
371	if ($result) {
372		uncheckTableRows();
373	}
374	show_messages($result, _('Selected correlations deleted'), _('Cannot delete selected correlations'));
375}
376
377/*
378 * Display
379 */
380show_messages();
381
382$config = select_config();
383
384if (hasRequest('form')) {
385	$data = [
386		'form' => getRequest('form'),
387		'correlationid' => $correlationid,
388		'new_condition' => getRequest('new_condition', []),
389		'new_operation' => getRequest('new_operation', ['type' => null]),
390		'config' => $config
391	];
392
393	if ($correlationid !== null) {
394		$data['correlation'] = API::Correlation()->get([
395			'output' => ['correlationid', 'name', 'description', 'status'],
396			'correlationids' => [$correlationid],
397			'selectFilter' => ['formula', 'conditions', 'evaltype', 'eval_formula'],
398			'selectOperations' => ['type'],
399			'editable' => true
400		]);
401		$data['correlation'] = reset($data['correlation']);
402	}
403
404	if (isset($data['correlation']['correlationid']) && !hasRequest('form_refresh')) {
405		CArrayHelper::sort($data['correlation']['operations'], ['type']);
406	}
407	else {
408		$data['correlation']['name'] = getRequest('name');
409		$data['correlation']['description'] = getRequest('description');
410		$data['correlation']['status'] = getRequest('status', hasRequest('form_refresh') ? 1 : 0);
411		$data['correlation']['operations'] = getRequest('operations', []);
412		$data['correlation']['filter']['evaltype'] = getRequest('evaltype');
413		$data['correlation']['filter']['formula'] = getRequest('formula');
414		$data['correlation']['filter']['conditions'] = getRequest('conditions', []);
415	}
416
417	$data['allowedConditions'] = corrConditionTypes();
418	$data['allowedOperations'] = corrOperationTypes();
419
420	if (!hasRequest('add_condition')) {
421		$data['correlation']['filter']['conditions'] = CConditionHelper::sortConditionsByFormulaId(
422			$data['correlation']['filter']['conditions']
423		);
424	}
425
426	if ($data['new_condition']) {
427		switch ($data['new_condition']['type']) {
428			case ZBX_CORR_CONDITION_EVENT_TAG_PAIR:
429				if (!array_key_exists('oldtag', $data['new_condition'])) {
430					$data['new_condition']['oldtag'] = '';
431				}
432
433				if (!array_key_exists('newtag', $data['new_condition'])) {
434					$data['new_condition']['newtag'] = '';
435				}
436				break;
437
438			case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE:
439			case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE:
440				if (!array_key_exists('value', $data['new_condition'])) {
441					$data['new_condition']['value'] = '';
442				}
443				break;
444		}
445	}
446	else {
447		$data['new_condition'] = [
448			'type' => ZBX_CORR_CONDITION_OLD_EVENT_TAG,
449			'operator' => CONDITION_OPERATOR_EQUAL,
450			'tag' => ''
451		];
452	}
453
454	// Render view.
455	$correlationView = new CView('configuration.correlation.edit', $data);
456	$correlationView->render();
457	$correlationView->show();
458}
459else {
460	$sortField = getRequest('sort', CProfile::get('web.'.$page['file'].'.sort', 'name'));
461	$sortOrder = getRequest('sortorder', CProfile::get('web.'.$page['file'].'.sortorder', ZBX_SORT_UP));
462
463	CProfile::update('web.'.$page['file'].'.sort', $sortField, PROFILE_TYPE_STR);
464	CProfile::update('web.'.$page['file'].'.sortorder', $sortOrder, PROFILE_TYPE_STR);
465
466	// filter
467	if (hasRequest('filter_set')) {
468		CProfile::update('web.correlation.filter_name', getRequest('filter_name', ''), PROFILE_TYPE_STR);
469		CProfile::update('web.correlation.filter_status', getRequest('filter_status', -1), PROFILE_TYPE_INT);
470	}
471	elseif (hasRequest('filter_rst')) {
472		CProfile::delete('web.correlation.filter_name');
473		CProfile::delete('web.correlation.filter_status');
474	}
475
476	$filter = [
477		'name' => CProfile::get('web.correlation.filter_name', ''),
478		'status' => CProfile::get('web.correlation.filter_status', -1)
479	];
480
481	$data = [
482		'sort' => $sortField,
483		'sortorder' => $sortOrder,
484		'filter' => $filter,
485		'config' => $config,
486		'profileIdx' => 'web.correlation.filter',
487		'active_tab' => CProfile::get('web.correlation.filter.active', 1)
488	];
489
490	$data['correlations'] = API::Correlation()->get([
491		'output' => ['correlationid', 'name', 'description', 'status'],
492		'search' => [
493			'name' => ($filter['name'] === '') ? null : $filter['name']
494		],
495		'filter' => [
496			'status' => ($filter['status'] == -1) ? null : $filter['status']
497		],
498		'selectFilter' => ['formula', 'conditions', 'evaltype', 'eval_formula'],
499		'selectOperations' => ['type'],
500		'editable' => true,
501		'sortfield' => $sortField,
502		'limit' => $config['search_limit'] + 1
503	]);
504
505	// sorting && paging
506	order_result($data['correlations'], $sortField, $sortOrder);
507	$data['paging'] = getPagingLine($data['correlations'], $sortOrder, new CUrl('correlation.php'));
508
509	// Render view.
510	$correlationView = new CView('configuration.correlation.list', $data);
511	$correlationView->render();
512	$correlationView->show();
513}
514
515require_once dirname(__FILE__).'/include/page_footer.php';
516