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 * @var CView $this
24 */
25insert_javascript_for_visibilitybox();
26?>
27<script type="text/x-jquery-tmpl" id="lldoverride-row-templated">
28	<?= (new CRow([
29			'',
30			(new CSpan('1:'))->setAttribute('data-row-num', ''),
31			(new CCol((new CLink('#{name}', 'javascript:lldoverrides.overrides.open(#{no});')))),
32			'#{stop_verbose}',
33			(new CCol((new CButton(null, _('Remove')))
34				->addClass(ZBX_STYLE_BTN_LINK)
35				->addClass('element-table-remove')
36				->setEnabled(false)
37			))->addClass(ZBX_STYLE_NOWRAP)
38		]))->toString()
39	?>
40</script>
41<script type="text/x-jquery-tmpl" id="lldoverride-row">
42	<?= (new CRow([
43			(new CCol((new CDiv())->addClass(ZBX_STYLE_DRAG_ICON)))
44				->addClass(ZBX_STYLE_TD_DRAG_ICON)
45				->setWidth('15'),
46			(new CCol((new CSpan('1:'))->setAttribute('data-row-num', '')))
47				->setWidth('15'),
48			(new CCol((new CLink('#{name}', 'javascript:lldoverrides.overrides.open(#{no});'))))
49				->setWidth('350'),
50			(new CCol('#{stop_verbose}'))
51				->setWidth('100'),
52			(new CCol((new CButton(null, _('Remove')))
53				->addClass(ZBX_STYLE_BTN_LINK)
54				->addClass('element-table-remove')
55			))
56				->addClass(ZBX_STYLE_NOWRAP)
57				->setWidth('50')
58		]))
59			->addClass('sortable')
60			->toString()
61	?>
62</script>
63<script type="text/x-jquery-tmpl" id="override-filters-row">
64	<?=
65		(new CRow([[
66				new CSpan('#{formulaId}'),
67				new CVar('overrides_filters[#{rowNum}][formulaid]', '#{formulaId}')
68			],
69			(new CTextBox('overrides_filters[#{rowNum}][macro]', '', false,
70					DB::getFieldLength('lld_override_condition', 'macro')))
71				->setWidth(ZBX_TEXTAREA_MACRO_WIDTH)
72				->addClass(ZBX_STYLE_UPPERCASE)
73				->addClass('macro')
74				->setAttribute('placeholder', '{#MACRO}')
75				->setAttribute('data-formulaid', '#{formulaId}'),
76			(new CSelect('overrides_filters[#{rowNum}][operator]'))
77				->setValue(CONDITION_OPERATOR_REGEXP)
78				->addOption(new CSelectOption(CONDITION_OPERATOR_REGEXP, _('matches')))
79				->addOption(new CSelectOption(CONDITION_OPERATOR_NOT_REGEXP, _('does not match'))),
80			(new CTextBox('overrides_filters[#{rowNum}][value]', '', false,
81					DB::getFieldLength('lld_override_condition', 'value')))
82				->setWidth(ZBX_TEXTAREA_MACRO_VALUE_WIDTH)
83				->setAttribute('placeholder', _('regular expression')),
84			(new CCol(
85				(new CButton('overrides_filters#{rowNum}_remove', _('Remove')))
86					->addClass(ZBX_STYLE_BTN_LINK)
87					->addClass('element-table-remove')
88			))->addClass(ZBX_STYLE_NOWRAP)
89		]))
90			->addClass('form_row')
91			->toString()
92	?>
93</script>
94<script type="text/x-jquery-tmpl" id="lldoverride-operation-row-templated">
95	<?= (new CRow([
96			['#{condition_object} #{condition_operator} ', italic('#{value}')],
97			(new CCol(
98				(new CButton(null, _('View')))
99					->addClass(ZBX_STYLE_BTN_LINK)
100					->addClass('element-table-open')
101					->onClick('lldoverrides.operations.open(#{no});')
102			))->addClass(ZBX_STYLE_NOWRAP)
103		]))->toString()
104	?>
105</script>
106<script type="text/x-jquery-tmpl" id="lldoverride-operation-row">
107	<?= (new CRow([
108			['#{condition_object} #{condition_operator} ', italic('#{value}')],
109			(new CHorList([
110				(new CButton(null, _('Edit')))
111					->addClass(ZBX_STYLE_BTN_LINK)
112					->addClass('element-table-open')
113					->onClick('lldoverrides.operations.open(#{no});'),
114				(new CButton(null, _('Remove')))
115					->addClass(ZBX_STYLE_BTN_LINK)
116					->addClass('element-table-remove')
117			]))->addClass(ZBX_STYLE_NOWRAP)
118		]))->toString()
119	?>
120</script>
121<script type="text/x-jquery-tmpl" id="lldoverride-custom-intervals-row">
122	<?= (new CRow([
123			(new CRadioButtonList('opperiod[delay_flex][#{rowNum}][type]', 0))
124				->addValue(_('Flexible'), ITEM_DELAY_FLEXIBLE)
125				->addValue(_('Scheduling'), ITEM_DELAY_SCHEDULING)
126				->setModern(true),
127			[
128				(new CTextBox('opperiod[delay_flex][#{rowNum}][delay]'))
129					->setAttribute('placeholder', ZBX_ITEM_FLEXIBLE_DELAY_DEFAULT),
130				(new CTextBox('opperiod[delay_flex][#{rowNum}][schedule]'))
131					->setAttribute('placeholder', ZBX_ITEM_SCHEDULING_DEFAULT)
132					->setAttribute('style', 'display: none;')
133			],
134			(new CTextBox('opperiod[delay_flex][#{rowNum}][period]'))
135				->setAttribute('placeholder', ZBX_DEFAULT_INTERVAL),
136			(new CButton('opperiod[delay_flex][#{rowNum}][remove]', _('Remove')))
137				->addClass(ZBX_STYLE_BTN_LINK)
138				->addClass('element-table-remove')
139		]))
140			->addClass('form_row')
141			->toString()
142	?>
143</script>
144<script type="text/x-jquery-tmpl" id="lldoverride-tag-row">
145	<?= renderTagTableRow('#{rowNum}', '', '', ['field_name' => 'optag', 'add_post_js' => false]) ?>
146</script>
147<script type="text/javascript">
148	jQuery(function($) {
149		window.lldoverrides = {
150			templated:                           <?= $data['limited'] ? 1 : 0 ?>,
151			ZBX_STYLE_DRAG_ICON:                 <?= zbx_jsvalue(ZBX_STYLE_DRAG_ICON) ?>,
152			ZBX_STYLE_TD_DRAG_ICON:              <?= zbx_jsvalue(ZBX_STYLE_TD_DRAG_ICON) ?>,
153			ZBX_STYLE_DISABLED:                  <?= zbx_jsvalue(ZBX_STYLE_DISABLED) ?>,
154			msg: {
155				yes:                        <?= json_encode(_('Yes')) ?>,
156				no:                         <?= json_encode(_('No')) ?>,
157				item_prototype:             <?= json_encode(_('Item prototype')) ?>,
158				trigger_prototype:          <?= json_encode(_('Trigger prototype')) ?>,
159				graph_prototype:            <?= json_encode(_('Graph prototype')) ?>,
160				host_prototype:             <?= json_encode(_('Host prototype')) ?>,
161				equals:                     <?= json_encode(_('equals')) ?>,
162				does_not_equal:             <?= json_encode(_('does not equal')) ?>,
163				contains:                   <?= json_encode(_('contains')) ?>,
164				does_not_contain:           <?= json_encode(_('does not contain')) ?>,
165				matches:                    <?= json_encode(_('matches')) ?>,
166				does_not_match:             <?= json_encode(_('does not match')) ?>
167			}
168		};
169
170		window.lldoverrides.override_row_template = new Template(jQuery(lldoverrides.templated
171			? '#lldoverride-row-templated'
172			: '#lldoverride-row'
173		).html());
174
175		window.lldoverrides.operations_row_template = new Template(jQuery(lldoverrides.templated
176			? '#lldoverride-operation-row-templated'
177			: '#lldoverride-operation-row'
178		).html());
179
180		window.lldoverrides.overrides = new Overrides($('#overridesTab'),
181			<?= json_encode(array_values($data['overrides'])) ?>
182		);
183		window.lldoverrides.actions = ['opstatus', 'opdiscover', 'opperiod', 'ophistory', 'optrends', 'opseverity',
184			'optag', 'optemplate', 'opinventory'
185		];
186
187		window.lldoverrides.$form = $('form[name="itemForm"]').on('submit', function(e) {
188			var hidden_form = this.querySelector('#hidden-form');
189
190			hidden_form && hidden_form.remove();
191			hidden_form = document.createElement('div');
192			hidden_form.id = 'hidden-form';
193
194			hidden_form.appendChild(lldoverrides.overrides.toFragment());
195
196			this.appendChild(hidden_form);
197		});
198	});
199
200	/**
201	 * Returns common $.sortable options.
202	 *
203	 * @return {object}
204	 */
205	function sortableOpts() {
206		return {
207			items: 'tbody tr.sortable',
208			axis: 'y',
209			containment: 'parent',
210			cursor: 'grabbing',
211			handle: 'div.' + lldoverrides.ZBX_STYLE_DRAG_ICON,
212			tolerance: 'pointer',
213			opacity: 0.6,
214			start: function(e, ui) {
215				ui.placeholder.height(ui.item.height());
216			}
217		};
218	}
219
220	/**
221	 * Writes data index as new nodes attribute. Bind remove event.
222	 */
223	function dynamicRowsBindNewRow($el) {
224		$el.on('dynamic_rows.beforeadd', function(e, dynamic_rows) {
225			e.new_node.setAttribute('data-index', e.data_index);
226			e.new_node.querySelector('.element-table-remove')
227				.addEventListener('click', dynamic_rows.removeRow.bind(dynamic_rows, e.data_index));
228
229			// Because IE does not have NodeList.prototype.forEach method.
230			Array.prototype.forEach.call(e.new_node.querySelectorAll('input'), function(input_node) {
231				input_node.addEventListener('keyup', function(e) {
232					$el.trigger('dynamic_rows.updated', dynamic_rows);
233				});
234			});
235		});
236	}
237
238	/**
239	 * Implements disabling sortable if less than two data rows.
240	 */
241	function dynamicRowsBindSortableDisable($el) {
242		$el.on('dynamic_rows.updated', function(e, dynamic_rows) {
243			if (dynamic_rows.length < 2) {
244				dynamic_rows.$element.sortable('option', 'disabled', true);
245				dynamic_rows.$element.find('.' + lldoverrides.ZBX_STYLE_DRAG_ICON)
246					.addClass(lldoverrides.ZBX_STYLE_DISABLED);
247			}
248			else {
249				dynamic_rows.$element.sortable('option', 'disabled', false);
250				dynamic_rows.$element.find('.' + lldoverrides.ZBX_STYLE_DRAG_ICON)
251					.removeClass(lldoverrides.ZBX_STYLE_DISABLED);
252			}
253		});
254	}
255
256	/**
257	 * A helper method for creating hidden input nodes.
258	 *
259	 * @param {string} name
260	 * @param {string} value
261	 * @param {string} prefix
262	 *
263	 * @return {object}  Return input element node.
264	 */
265	function hiddenInput(name, value, prefix) {
266		var input = window.document.createElement('input');
267
268		input.type = 'hidden';
269		input.value = value;
270		input.name = prefix ? prefix + '[' + name + ']' : name;
271
272		return input;
273	}
274
275	/**
276	 * @param {object} $element
277	 * @param {object} options
278	 * @param {array}  data
279	 */
280	function DynamicRows($element, options, data) {
281		if (!(options.add_before instanceof Node)) {
282			throw 'Error: options.add_before must be instanceof Node.';
283		}
284
285		if (!(options.template instanceof Template)) {
286			throw 'Error: options.template must be instanceof Template.';
287		}
288
289		this.data = {};
290		this.$element = $element;
291		this.options = jQuery.extend({}, {
292			// Please note, this option does not work if data_index option is in use.
293			ensure_min_rows: 0,
294			// If this option is used, it represents key of data object whose value will be used as data_index.
295			data_index: null
296		}, options);
297
298		this.data_index = 0;
299		this.length = 0;
300		data && this.setData(data);
301	}
302
303	/**
304	 * All events are dispatched on $element. Second argument is instance, first argument is event object.
305	 *
306	 * @param {string} evt_key
307	 * @param {object} evt_data  Optional object to be merged into event data.
308	 *
309	 * @return {object}  Returns a jQuery.Event object.
310	 */
311	DynamicRows.prototype.dispatch = function(evt_key, evt_data) {
312		var evt = jQuery.Event('dynamic_rows.' + evt_key, evt_data);
313
314		this.$element.trigger(evt, [this]);
315
316		return evt;
317	}
318
319	/**
320	 * Adds a row before the given row.
321	 *
322	 * @param {object} data        Data to be passed into template.
323	 * @param {number} data_index  Optional index, if data with given index exists, then in place update will happen.
324	 *                             In case of update all events dispatched as if add new was performed.
325	 */
326	DynamicRows.prototype.addRow = function(row_data, data_index) {
327		if (this.options.disabled) {
328			return;
329		}
330
331		if (!data_index) {
332			data_index = this.options.data_index
333				? row_data[this.options.data_index]
334				: ++this.data_index;
335		}
336
337		var new_row = {
338			node: this.createRowNode(row_data),
339			data: row_data || {}
340		};
341
342		var evt_before_add = this.dispatch('beforeadd', {
343			new_data: new_row.data,
344			new_node: new_row.node,
345			add_before: this.options.add_before,
346			data_index: data_index
347		});
348
349		if (evt_before_add.isDefaultPrevented()) {
350			return;
351		}
352
353		if (this.data[data_index]) {
354			evt_before_add.add_before.parentNode.replaceChild(evt_before_add.new_node, this.data[data_index].node);
355		}
356		else {
357			evt_before_add.add_before.parentNode.insertBefore(evt_before_add.new_node, evt_before_add.add_before);
358			this.length++;
359		}
360
361		this.data[data_index] = new_row;
362
363		this.dispatch('updated');
364	};
365
366	/**
367	* Replaces current data with new one.
368	* Be aware that min rows are ensured and events are triggered only for add.
369	* Removing happens outside this API, to not to call.
370	*
371	* @param {array} data  Array of data for row templates.
372	*
373	* @return {object}  Returns the DynamicRows object.
374	*/
375	DynamicRows.prototype.setData = function(data) {
376		if (!(data  instanceof Array)) {
377			throw 'Expected Array.';
378		}
379
380		for (var i in this.data) {
381			this.unlinkIndex(i);
382		}
383
384		data.forEach(function(obj) {
385			this.addRow(obj);
386		}.bind(this));
387
388		this.ensureMinRows();
389
390		return this;
391	};
392
393	/**
394	 * Adds empty rows if needed.
395	 */
396	DynamicRows.prototype.ensureMinRows = function() {
397		var rows_to_add = this.options.ensure_min_rows - this.length;
398		while (rows_to_add > 0) {
399			rows_to_add--;
400			this.addRow();
401		}
402	};
403
404	/**
405	 * Renders Node from template.
406	 *
407	 * @param {object} data  Data to be passed into template.
408	 *
409	 * @return {object}
410	 */
411	DynamicRows.prototype.createRowNode = function(data) {
412		var evt = this.dispatch('beforerender', {view_data: data});
413		var html_str = this.options.template.evaluate(evt.view_data);
414
415		return jQuery(html_str).get(0);
416	};
417
418
419	/**
420	 * Removes data at given index. Method is to be used by plugin internally, does not dispatch events.
421	 *
422	 * @param {number} data_index
423	 *
424	 * @return {object}  Object that just got removed.
425	 */
426	DynamicRows.prototype.unlinkIndex = function(data_index) {
427		this.data[data_index].node.remove();
428		var ref = this.data[data_index];
429
430		delete this.data[data_index];
431		this.length--;
432
433		return ref;
434	}
435
436	/**
437	 * Removes the given row.
438	 *
439	 * @param {number} data_index
440	 */
441	DynamicRows.prototype.removeRow = function(data_index) {
442		if (this.options.disabled) {
443			return;
444		}
445
446		var removed_row = this.unlinkIndex(data_index);
447
448		this.dispatch('afterremove', {removed_row: removed_row, data_index: data_index});
449		this.dispatch('updated');
450
451		this.ensureMinRows();
452	};
453
454	/**
455	 * Represents overrides tab in discovery rules layout.
456	 *
457	 * @param {object} $tab
458	 * @param {array}  overrides  Initial overrides objects data array.
459	 */
460	function Overrides($tab, overrides) {
461		this.data = {};
462		this.new_id = 0;
463		this.sort_index = [];
464
465		overrides.forEach(function(override, no) {
466			this.data[no + 1] = new Override(override);
467			this.sort_index.push(no + 1);
468		}.bind(this));
469
470		this.$container = jQuery('.lld-overrides-table', $tab);
471		this.$container.find('.element-table-add').on('click', this.openNew.bind(this));
472
473		this.$container.on('dynamic_rows.beforerender', function(e, dynamic_rows) {
474			e.view_data.stop_verbose = (e.view_data.stop === '1') ? lldoverrides.msg.yes : lldoverrides.msg.no;
475		});
476
477		this.overrides_dynamic_rows = new DynamicRows(this.$container, {
478			add_before: this.$container.find('.element-table-add').closest('tr')[0],
479			template: lldoverrides.override_row_template
480		});
481
482		if (!lldoverrides.templated) {
483			this.$container.sortable(sortableOpts());
484			this.$container.sortable('option', 'update', this.onSortOrderChange.bind(this));
485			this.$container.on('dynamic_rows.afterremove', function(e, dynamic_rows) {
486				delete this.data[e.data_index];
487				this.onSortOrderChange();
488			}.bind(this));
489
490			dynamicRowsBindSortableDisable(this.$container);
491			dynamicRowsBindNewRow(this.$container);
492		}
493		else {
494			this.$container.on('dynamic_rows.beforeadd', function(e, dynamic_rows) {
495				e.new_node.setAttribute('data-index', e.data_index);
496			});
497		}
498
499		this.renderData();
500	}
501
502	/**
503	 * The parts of form that are easier to maintain in functional objects are transformed into hidden input fields.
504	 *
505	 * @return {object}  Returns DocumentFragment object.
506	 */
507	Overrides.prototype.toFragment = function() {
508		var frag = document.createDocumentFragment(),
509			iter_step = 0;
510
511		this.sort_index.forEach(function(id) {
512			var override = this.data[id],
513				prefix_override = 'overrides[' + (iter_step++) + ']',
514				prefix_filter = prefix_override + '[filter]',
515				iter_filters = 0,
516				iter_operations = 0;
517
518			frag.appendChild(hiddenInput('step', iter_step, prefix_override));
519			frag.appendChild(hiddenInput('name', override.data.name, prefix_override));
520			frag.appendChild(hiddenInput('stop', override.data.stop, prefix_override));
521
522			if (override.data.overrides_filters.length > 0) {
523				frag.appendChild(hiddenInput('evaltype', override.data.overrides_evaltype, prefix_filter));
524
525				if (override.data.overrides_evaltype == <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) {
526					frag.appendChild(hiddenInput('formula', override.data.overrides_formula, prefix_filter));
527				}
528
529				override.data.overrides_filters.forEach(function(override_filter) {
530					var prefix = prefix_filter + '[conditions][' + (iter_filters++) + ']';
531					frag.appendChild(hiddenInput('formulaid', override_filter.formulaid, prefix));
532					frag.appendChild(hiddenInput('macro', override_filter.macro, prefix));
533					frag.appendChild(hiddenInput('value', override_filter.value, prefix));
534					frag.appendChild(hiddenInput('operator', override_filter.operator, prefix));
535				});
536			}
537
538			override.data.operations.forEach(function(operation) {
539				var prefix = prefix_override + '[operations][' + (iter_operations++) + ']';
540				frag.appendChild(hiddenInput('operationobject', operation.operationobject, prefix));
541				frag.appendChild(hiddenInput('operator', operation.operator, prefix));
542				frag.appendChild(hiddenInput('value', operation.value, prefix));
543
544				if ('opstatus' in operation) {
545					frag.appendChild(hiddenInput('status', operation.opstatus.status, prefix + '[opstatus]'));
546				}
547				if ('opdiscover' in operation) {
548					frag.appendChild(hiddenInput('discover', operation.opdiscover.discover, prefix + '[opdiscover]'));
549				}
550				if ('opperiod' in operation) {
551					frag.appendChild(hiddenInput('delay', operation.opperiod.delay, prefix + '[opperiod]'));
552				}
553				if ('ophistory' in operation) {
554					frag.appendChild(hiddenInput('history', operation.ophistory.history, prefix + '[ophistory]'));
555				}
556				if ('optrends' in operation) {
557					frag.appendChild(hiddenInput('trends', operation.optrends.trends, prefix + '[optrends]'));
558				}
559				if ('opseverity' in operation) {
560					frag.appendChild(hiddenInput('severity', operation.opseverity.severity, prefix + '[opseverity]'));
561				}
562				if ('optag' in operation) {
563					var iter_tags = 0;
564
565					operation.optag.forEach(function(tag) {
566						var prefix_tag = prefix + '[optag][' + (iter_tags++) + ']';
567						frag.appendChild(hiddenInput('tag', tag.tag, prefix_tag));
568
569						if (('value' in tag) && 'value' !== '') {
570							frag.appendChild(hiddenInput('value', tag.value, prefix_tag));
571						}
572					});
573				}
574				if ('optemplate' in operation) {
575					var iter_templates = 0;
576
577					operation.optemplate.forEach(function(template) {
578						var prefix_template = prefix + '[optemplate][' + (iter_templates++) + ']';
579						frag.appendChild(hiddenInput('templateid', template.templateid, prefix_template));
580					});
581				}
582				if ('opinventory' in operation) {
583					frag.appendChild(hiddenInput('inventory_mode', operation.opinventory.inventory_mode,
584						prefix + '[opinventory]'
585					));
586				}
587			});
588		}.bind(this));
589
590		return frag;
591	};
592
593	/**
594	 * This method maintains property for iterating overrides in the order that rows have in DOM at the moment,
595	 * also updates visual counter in DOM for override rows.
596	 */
597	Overrides.prototype.onSortOrderChange = function() {
598		var order = [];
599		this.$container.find('[data-index]').each(function(index) {
600			this.querySelector('[data-row-num]').innerText = (index + 1) + ':';
601			order.push(this.attributes.getNamedItem('data-index').value);
602		});
603		this.sort_index = order;
604	};
605
606	/**
607	 * Used to validate override names with server, on PopUp form validate event.
608	 *
609	 * @return {array}  Array of strings.
610	 */
611	Overrides.prototype.getOverrideNames = function() {
612		var names = [];
613
614		for (var no in this.data) {
615			names.push(this.data[no].data.name);
616		}
617
618		return names;
619	};
620
621	/**
622	 * This method hydrates the parsed html PopUp form with data from specific override.
623	 *
624	 * @param {number} no  Override index.
625	 */
626	Overrides.prototype.onStepOverlayReadyCb = function(no) {
627		var override_ref = this.data[no] ? this.data[no] : this.new_override;
628		this.edit_form = new OverrideEditForm(jQuery('#lldoverride_form'), override_ref);
629	};
630
631	/**
632	 * Creates new override id and opens form for it.
633	 */
634	Overrides.prototype.openNew = function() {
635		this.new_id -= 1;
636
637		this.new_override = new Override({no: this.new_id});
638		this.new_override.open(this.new_id, this.$container.find('.element-table-add'));
639	};
640
641	/**
642	 * Renders overrides in DOM.
643	 */
644	Overrides.prototype.renderData = function() {
645		this.sort_index.forEach(function(data_index) {
646			this.overrides_dynamic_rows.addRow(this.data[data_index].data, data_index);
647		}.bind(this));
648
649		this.onSortOrderChange();
650	}
651
652	/**
653	 * Opens popup for a override.
654	 *
655	 * @param {number} no
656	 */
657	Overrides.prototype.open = function(no) {
658		this.data[no].open(no, this.$container.find('[data-index="' + no + '"] a'));
659	};
660
661	/**
662	 * This object represents a override of web scenario.
663	 *
664	 * @param {object} data  Optional override initial data.
665	 */
666	function Override(data) {
667		var defaults = {
668			name: '',
669			stop: '0',
670			filter: {
671				'evaltype': '0',
672				'formula': '',
673				'conditions': []
674			},
675			operations: []
676		};
677		this.data = jQuery.extend(true, defaults, data);
678
679		this.data.no = this.data.step;
680		this.data.overrides_evaltype = this.data.filter.evaltype;
681		this.data.overrides_formula = this.data.filter.formula;
682		this.data.overrides_filters = this.data.filter.conditions;
683		delete this.data.filter;
684
685		/*
686		 * Used to add proper letter, when creating new dynamic row for filter. If no filters are configured,
687		 * one empty row is created by View.
688		 */
689		this.filter_counter = (this.data.overrides_filters.length > 0) ? this.data.overrides_filters.length : 1;
690	}
691
692	/**
693	 * Merges old data with new data.
694	 */
695	Override.prototype.update = function(data) {
696		jQuery.extend(this.data, data);
697	};
698
699	/**
700	 * Opens override popup - edit or create form.
701	 * Note: a callback this.onStepOverlayReadyCb is called from within popup form once it is parsed.
702	 *
703	 * @param {number} step     Override index.
704	 * @param {object} refocus  A node to set focus to, when popup is closed.
705	 */
706	Override.prototype.open = function(no, refocus) {
707		return PopUp('popup.lldoverride', {
708			no:                 no,
709			templated:          lldoverrides.templated,
710			name:               this.data.name,
711			old_name:           this.data.name,
712			stop:               this.data.stop,
713			overrides_evaltype: this.data.overrides_evaltype,
714			overrides_formula:  this.data.overrides_formula,
715			overrides_filters:  this.data.overrides_filters,
716			operations:         this.data.operations,
717			overrides_names:    lldoverrides.overrides.getOverrideNames()
718		}, null, refocus);
719	};
720
721	/**
722	 * Represents popup form.
723	 *
724	 * @param {object} $form
725	 * @param {object} override_ref  Reference to override instance from Overrides object.
726	 */
727	function OverrideEditForm($form, override_ref) {
728		this.$form = $form;
729		this.override = override_ref;
730
731		// Initiate Filters dynamic rows and evaltype.
732		this.filterDynamicRows();
733		this.operations = new Operations(this.$form, this.override.data.operations);
734		// This will be used for link on edit button.
735		window.lldoverrides.operations = this.operations;
736	}
737
738	OverrideEditForm.prototype.updateExpression = function() {
739		var filters = [];
740
741		jQuery('#overrides_filters .macro').each(function(index, macroInput) {
742			macroInput = jQuery(macroInput);
743			macroInput.val(macroInput.val().toUpperCase());
744
745			filters.push({
746				id: macroInput.data('formulaid'),
747				type: macroInput.val()
748			});
749		});
750
751		jQuery('#overrides_expression').html(getConditionFormula(filters, +jQuery('#overrides-evaltype').val()));
752	};
753
754	OverrideEditForm.prototype.filterDynamicRows = function() {
755		var that = this;
756
757		jQuery('#overrides_filters')
758			.dynamicRows({
759				template: '#override-filters-row',
760				counter: this.override.filter_counter,
761				dataCallback: function(data) {
762					data.formulaId = num2letter(data.rowNum);
763					that.override.filter_counter++;
764
765					return data;
766				}
767			})
768			.bind('tableupdate.dynamicRows', function(event, options) {
769				jQuery('#overrideRow').toggle(jQuery(options.row, jQuery(this)).length > 1);
770
771				if (jQuery('#overrides-evaltype').val() != <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) {
772					that.updateExpression();
773				}
774			})
775			.on('change', '.macro', function() {
776				if (jQuery('#overrides-evaltype').val() != <?= CONDITION_EVAL_TYPE_EXPRESSION ?>) {
777					that.updateExpression();
778				}
779			})
780			.ready(function() {
781				jQuery('#overrideRow').toggle(jQuery('.form_row', jQuery('#overrides_filters')).length > 1);
782				overlays_stack.end().centerDialog();
783			});
784
785		jQuery('#overrides-evaltype').change(function() {
786			var show_formula = (jQuery(this).val() == <?= CONDITION_EVAL_TYPE_EXPRESSION ?>);
787
788			jQuery('#overrides_expression').toggle(!show_formula);
789			jQuery('#overrides_formula').toggle(show_formula);
790			if (!show_formula) {
791				that.updateExpression();
792			}
793
794			overlays_stack.end().centerDialog();
795		});
796
797		jQuery('#overrides-evaltype').trigger('change');
798	};
799
800	/**
801	 * This method is bound via popup button attribute. It posts serialized version of current form to be validated.
802	 * Note that we do not bother posting dynamic fields, since they are not validated at this point.
803	 *
804	 * @param {object} overlay
805	 */
806	OverrideEditForm.prototype.validate = function(overlay) {
807		var url = new Curl(this.$form.attr('action'));
808		url.setArgument('validate', 1);
809
810		this.$form.trimValues(['input[type="text"]']);
811		this.$form.parent().find('.msg-bad, .msg-good').remove();
812
813		var form_data = this.$form.serializeJSON();
814		if (Object.keys(form_data.overrides_filters).length <= 1) {
815			delete form_data.overrides_formula;
816			delete form_data.overrides_evaltype;
817		}
818
819		overlay.setLoading();
820		overlay.xhr = jQuery.ajax({
821			url: url.getUrl(),
822			data: form_data,
823			dataType: 'json',
824			type: 'post'
825		})
826		.always(function() {
827			overlay.unsetLoading();
828		})
829		.done(function(ret) {
830			if (typeof ret.errors !== 'undefined') {
831				return jQuery(ret.errors).insertBefore(this.$form);
832			}
833
834			if (!lldoverrides.overrides.data[ret.params.no]) {
835				lldoverrides.overrides.sort_index.push(ret.params.no);
836				lldoverrides.overrides.data[ret.params.no] = this.override;
837			}
838
839			this.operations.sort_index.forEach(function(data_index) {
840				ret.params.operations.push(this.operations.data[data_index].data);
841			}.bind(this));
842
843			lldoverrides.overrides.data[ret.params.no].update(ret.params);
844			lldoverrides.overrides.renderData();
845
846			overlayDialogueDestroy(overlay.dialogueid);
847		}.bind(this));
848	};
849
850	function Operations($form, operations) {
851		var that = this;
852		this.data = {};
853		this.new_id = 0;
854		this.sort_index = [];
855
856		operations.forEach(function(operation, no) {
857			this.data[no + 1] = new Operation(operation, no + 1);
858			this.sort_index.push(no + 1);
859		}.bind(this));
860
861		this.$container = jQuery('.lld-overrides-operations-table', $form);
862		this.$container.find('.element-table-add').on('click', this.openNew.bind(this));
863
864		this.$container.on('dynamic_rows.beforerender', function(e, dynamic_rows) {
865			e.view_data.condition_object = that.operationobjectName(e.view_data.operationobject);
866			e.view_data.condition_operator = that.operatorName(e.view_data.operator);
867		});
868
869		this.operations_dynamic_rows = new DynamicRows(this.$container, {
870			add_before: this.$container.find('.element-table-add').closest('tr')[0],
871			template: lldoverrides.operations_row_template
872		});
873
874		if (!lldoverrides.templated) {
875			this.$container.on('dynamic_rows.afterremove', function(e, dynamic_rows) {
876				delete this.data[e.data_index];
877
878				var index = this.sort_index.indexOf(e.data_index);
879				if (index > -1) {
880					this.sort_index.splice(index, 1);
881				}
882			}.bind(this));
883
884			dynamicRowsBindNewRow(this.$container);
885		}
886		else {
887			this.$container.on('dynamic_rows.beforeadd', function(e, dynamic_rows) {
888				e.new_node.setAttribute('data-index', e.data_index);
889			});
890		}
891
892		this.renderData();
893	};
894
895	Operations.prototype.operationobjectName = function(operationobject) {
896		var operationobject_name = '';
897		if (operationobject === '<?= OPERATION_OBJECT_ITEM_PROTOTYPE ?>') {
898			operationobject_name = window.lldoverrides.msg.item_prototype;
899		}
900		else if (operationobject === '<?= OPERATION_OBJECT_TRIGGER_PROTOTYPE ?>') {
901			operationobject_name = window.lldoverrides.msg.trigger_prototype;
902		}
903		else if (operationobject === '<?= OPERATION_OBJECT_GRAPH_PROTOTYPE ?>') {
904			operationobject_name = window.lldoverrides.msg.graph_prototype;
905		}
906		else if (operationobject === '<?= OPERATION_OBJECT_HOST_PROTOTYPE ?>') {
907			operationobject_name = window.lldoverrides.msg.host_prototype;
908		}
909
910		return operationobject_name;
911	};
912
913	Operations.prototype.operatorName = function(operator) {
914		var operator_name = '';
915		if (operator === '<?= CONDITION_OPERATOR_EQUAL ?>') {
916			operator_name = window.lldoverrides.msg.equals;
917		}
918		else if (operator === '<?= CONDITION_OPERATOR_NOT_EQUAL ?>') {
919			operator_name = window.lldoverrides.msg.does_not_equal;
920		}
921		else if (operator === '<?= CONDITION_OPERATOR_LIKE ?>') {
922			operator_name = window.lldoverrides.msg.contains;
923		}
924		else if (operator === '<?= CONDITION_OPERATOR_NOT_LIKE ?>') {
925			operator_name = window.lldoverrides.msg.does_not_contain;
926		}
927		else if (operator === '<?= CONDITION_OPERATOR_REGEXP ?>') {
928			operator_name = window.lldoverrides.msg.matches;
929		}
930		else if (operator === '<?= CONDITION_OPERATOR_NOT_REGEXP ?>') {
931			operator_name = window.lldoverrides.msg.does_not_match;
932		}
933
934		return operator_name;
935	};
936
937	/**
938	 * This method hydrates the parsed html PopUp form with data from specific override.
939	 *
940	 * @param {number} no  Override index.
941	 */
942	Operations.prototype.onOperationOverlayReadyCb = function(no) {
943		var operation_ref = this.data[no] ? this.data[no] : this.new_operation;
944		this.edit_form = new OperationEditForm(jQuery('#lldoperation_form'), operation_ref);
945	};
946
947	/**
948	 * Creates new override id and opens form for it.
949	 */
950	Operations.prototype.openNew = function() {
951		this.new_id -= 1;
952
953		this.new_operation = new Operation({no: this.new_id});
954		this.new_operation.open(this.new_id, this.$container.find('.element-table-add'));
955	};
956
957	/**
958	 * Renders overrides in DOM.
959	 */
960	Operations.prototype.renderData = function() {
961		this.sort_index.forEach(function(data_index) {
962			this.operations_dynamic_rows.addRow(this.data[data_index].data, data_index);
963		}.bind(this));
964	}
965
966	/**
967	 * Opens popup for a override.
968	 *
969	 * @param {number} no
970	 */
971	Operations.prototype.open = function(no) {
972		this.data[no].open(no, this.$container.find('[data-index="' + no + '"] .element-table-open'));
973	};
974
975	function Operation(data, no) {
976		this.data = data;
977		this.data.no = no;
978	}
979
980	/**
981	 * Replaces data with new one.
982	 */
983	Operation.prototype.update = function(data) {
984		this.data = data;
985	};
986
987	/**
988	 * Opens override popup - edit or create form.
989	 * Note: a callback this.onStepOverlayReadyCb is called from within popup form once it is parsed.
990	 *
991	 * @param {number}  step     Override index.
992	 * @param {object}  refocus  A node to set focus to, when popup is closed.
993	 */
994	Operation.prototype.open = function(no, refocus) {
995		var params = {
996			no:                 no,
997			templated:          lldoverrides.templated,
998			operationobject:    this.data.operationobject,
999			operator:           this.data.operator,
1000			value:              this.data.value
1001		};
1002
1003		window.lldoverrides.actions.forEach(function(action) {
1004			if (action in this.data) {
1005				params[action] = this.data[action];
1006			}
1007		}.bind(this));
1008
1009		return PopUp('popup.lldoperation', params, null, refocus);
1010	};
1011
1012	/**
1013	 * Represents popup form.
1014	 *
1015	 * @param {object} $form
1016	 * @param {object} operation_ref  Reference to override instance from Overrides object.
1017	 */
1018	function OperationEditForm($form, operation_ref) {
1019		this.$form = $form;
1020		this.operation = operation_ref;
1021
1022		var that = this,
1023			$custom_intervals = jQuery('#lld_overrides_custom_intervals', this.$form);
1024
1025		$custom_intervals.on('click', 'input[type="radio"]', function() {
1026			var rowNum = jQuery(this).attr('id').split('_')[3];
1027
1028			if (jQuery(this).val() == <?= ITEM_DELAY_FLEXIBLE; ?>) {
1029				jQuery('#opperiod_delay_flex_' + rowNum + '_schedule', $custom_intervals).hide();
1030				jQuery('#opperiod_delay_flex_' + rowNum + '_delay', $custom_intervals).show();
1031				jQuery('#opperiod_delay_flex_' + rowNum + '_period', $custom_intervals).show();
1032			}
1033			else {
1034				jQuery('#opperiod_delay_flex_' + rowNum + '_delay', $custom_intervals).hide();
1035				jQuery('#opperiod_delay_flex_' + rowNum + '_period', $custom_intervals).hide();
1036				jQuery('#opperiod_delay_flex_' + rowNum + '_schedule', $custom_intervals).show();
1037			}
1038		});
1039
1040		$custom_intervals.dynamicRows({
1041			template: '#lldoverride-custom-intervals-row'
1042		});
1043
1044		jQuery('#ophistory_history_mode', this.$form)
1045			.change(function() {
1046				if (jQuery('[name="ophistory[history_mode]"][value=' + <?= ITEM_STORAGE_OFF ?> + ']').is(':checked')) {
1047					jQuery('#ophistory_history', that.$form).prop('disabled', true).hide();
1048				}
1049				else {
1050					jQuery('#ophistory_history', that.$form).prop('disabled', false).show();
1051				}
1052			})
1053			.trigger('change');
1054
1055		jQuery('#optrends_trends_mode', this.$form)
1056			.change(function() {
1057				if (jQuery('[name="optrends[trends_mode]"][value=' + <?= ITEM_STORAGE_OFF ?> + ']').is(':checked')) {
1058					jQuery('#optrends_trends', that.$form).prop('disabled', true).hide();
1059				}
1060				else {
1061					jQuery('#optrends_trends', that.$form).prop('disabled', false).show();
1062				}
1063			})
1064			.trigger('change');
1065
1066		jQuery('#tags-table .<?= ZBX_STYLE_TEXTAREA_FLEXIBLE ?>', this.$form).textareaFlexible();
1067		jQuery('#tags-table', this.$form)
1068			.dynamicRows({template: '#lldoverride-tag-row'})
1069			.on('click', 'button.element-table-add', function() {
1070				jQuery('#tags-table .<?= ZBX_STYLE_TEXTAREA_FLEXIBLE ?>', this.$form).textareaFlexible();
1071			});
1072
1073		// Override actions available per override object.
1074		var available_actions = {
1075			'<?= OPERATION_OBJECT_ITEM_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'opperiod', 'ophistory', 'optrends'],
1076			'<?= OPERATION_OBJECT_TRIGGER_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'opseverity', 'optag'],
1077			'<?= OPERATION_OBJECT_GRAPH_PROTOTYPE ?>': ['opdiscover'],
1078			'<?= OPERATION_OBJECT_HOST_PROTOTYPE ?>': ['opstatus', 'opdiscover', 'optemplate', 'opinventory']
1079		};
1080
1081		jQuery('#operationobject', this.$form)
1082			.change(function() {
1083				window.lldoverrides.actions.forEach(function(action) {
1084					if (available_actions[this.value].indexOf(action) !== -1) {
1085						that.showActionRow(action + '_row');
1086					}
1087					else {
1088						that.hideActionRow(action + '_row');
1089					}
1090				}.bind(this));
1091			});
1092	};
1093
1094	OperationEditForm.prototype.initHideActionRows = function() {
1095		jQuery('#operationobject', this.$form).trigger('change');
1096	};
1097
1098	OperationEditForm.prototype.showActionRow = function(row_id) {
1099		var obj = document.getElementById(row_id);
1100		if (is_null(obj)) {
1101			throw 'Cannot find action row with id [' + row_id + ']';
1102		}
1103
1104		// Show it only if it was previously hidden.
1105		if (obj.originalObject) {
1106			obj.parentNode.replaceChild(obj.originalObject, obj);
1107		}
1108	};
1109
1110	OperationEditForm.prototype.hideActionRow = function(row_id) {
1111		var obj = document.getElementById(row_id);
1112		if (is_null(obj)) {
1113			throw 'Cannot find action row with id [' + row_id +']';
1114		}
1115
1116		// Hide it only if it was previously visible.
1117		if (!('originalObject' in obj)) {
1118			try {
1119				var new_obj = document.createElement('li');
1120				new_obj.setAttribute('id', obj.id);
1121			}
1122			catch(e) {
1123				throw 'Cannot create new element';
1124			}
1125
1126			new_obj.originalObject = obj;
1127			obj.parentNode.replaceChild(new_obj, obj);
1128		}
1129	};
1130
1131	/**
1132	 * This method is bound via popup button attribute. It posts serialized version of current form to be validated.
1133	 * Note that we do not bother posting dynamic fields, since they are not validated at this point.
1134	 *
1135	 * @param {object} overlay
1136	 */
1137	OperationEditForm.prototype.validate = function(overlay) {
1138		var url = new Curl(this.$form.attr('action'));
1139		url.setArgument('validate', 1);
1140
1141		this.$form.trimValues(['input[type="text"]', 'textarea']);
1142		this.$form.parent().find('.msg-bad, .msg-good').remove();
1143
1144		overlay.setLoading();
1145		overlay.xhr = jQuery.ajax({
1146			url: url.getUrl(),
1147			data: this.$form.serialize(),
1148			dataType: 'json',
1149			type: 'post'
1150		})
1151		.always(function() {
1152			overlay.unsetLoading();
1153		})
1154		.done(function(ret) {
1155			if (typeof ret.errors !== 'undefined') {
1156				return jQuery(ret.errors).insertBefore(this.$form);
1157			}
1158
1159			if (!lldoverrides.operations.data[ret.params.no]) {
1160				lldoverrides.operations.sort_index.push(ret.params.no);
1161				lldoverrides.operations.data[ret.params.no] = this.operation;
1162			}
1163
1164			lldoverrides.operations.data[ret.params.no].update(ret.params);
1165			lldoverrides.operations.renderData();
1166
1167			overlayDialogueDestroy(overlay.dialogueid);
1168		}.bind(this));
1169	};
1170</script>
1171