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
22class CWidgetHelper {
23
24	/**
25	 * Create CForm for widget configuration form.
26	 *
27	 * @return CForm
28	 */
29	public static function createForm() {
30		return (new CForm('post'))
31			->cleanItems()
32			->setId('widget-dialogue-form')
33			->setName('widget_dialogue_form');
34	}
35
36	/**
37	 * Create CFormList for widget configuration form with default fields in it.
38	 *
39	 * @param string  $name
40	 * @param string  $type
41	 * @param int     $view_mode  ZBX_WIDGET_VIEW_MODE_NORMAL | ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER
42	 * @param array   $known_widget_types
43	 * @param CWidgetFieldSelect|null  $field_rf_rate
44	 *
45	 * @return CFormList
46	 */
47	public static function createFormList($name, $type, $view_mode, $known_widget_types, $field_rf_rate) {
48		$form_list = (new CFormList())
49			->addItem((new CListItem([
50					(new CDiv(new CLabel(_('Type'), 'label-type')))->addClass(ZBX_STYLE_TABLE_FORMS_TD_LEFT),
51					(new CDiv([
52						(new CDiv((new CCheckBox('show_header'))
53							->setLabel(_('Show header'))
54							->setLabelPosition(CCheckBox::LABEL_POSITION_LEFT)
55							->setId('show_header')
56							->setChecked($view_mode == ZBX_WIDGET_VIEW_MODE_NORMAL)
57						))->addClass(ZBX_STYLE_TABLE_FORMS_SECOND_COLUMN),
58						(new CSelect('type'))
59							->setFocusableElementId('label-type')
60							->setId('type')
61							->setValue($type)
62							->setAttribute('autofocus', 'autofocus')
63							->addOptions(CSelect::createOptionsFromArray($known_widget_types))
64					]))->addClass(ZBX_STYLE_TABLE_FORMS_TD_RIGHT)
65				]))->addClass('table-forms-row-with-second-field')
66			)
67			->addRow(_('Name'),
68				(new CTextBox('name', $name))
69					->setAttribute('placeholder', _('default'))
70					->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH)
71			)
72			->addItem(
73				(new CScriptTag('
74					$("z-select#type").on("change", () => ZABBIX.Dashboard.reloadWidgetProperties());
75
76					document
77						.getElementById("widget-dialogue-form")
78						.addEventListener("change", (e) => {
79							const is_trimmable = e.target.matches(
80								\'input[type="text"]:not([data-no-trim="1"]), textarea:not([data-no-trim="1"])\'
81							);
82
83							if (is_trimmable) {
84								e.target.value = e.target.value.trim();
85							}
86						}, {capture: true});
87				'))->setOnDocumentReady()
88			);
89
90		if ($field_rf_rate !== null) {
91			$form_list->addRow(self::getLabel($field_rf_rate), self::getSelect($field_rf_rate));
92		}
93
94		return $form_list;
95	}
96
97	/**
98	* Add Columns and Rows fields to the form of iterator.
99	*
100	* @param CFormList $form_list
101	* @param CWidgetFieldIntegerBox $field_columns
102	* @param CWidgetFieldIntegerBox $field_rows
103	*/
104	public static function addIteratorFields($form_list, $field_columns, $field_rows) {
105		$form_list
106			->addRow(self::getLabel($field_columns), self::getIntegerBox($field_columns))
107			->addRow(self::getLabel($field_rows), self::getIntegerBox($field_rows));
108	}
109
110	/**
111	 * Creates label linked to the field.
112	 *
113	 * @param CWidgetField $field
114	 *
115	 * @return CLabel
116	 */
117	public static function getLabel($field) {
118		if ($field instanceof CWidgetFieldSelect) {
119			return (new CLabel($field->getLabel(), 'label-'.$field->getName()))
120				->setAsteriskMark(self::isAriaRequired($field));
121		}
122
123		return (new CLabel($field->getLabel(), $field->getName()))
124			->setAsteriskMark(self::isAriaRequired($field));
125	}
126
127	/**
128	 * @param CWidgetFieldSelect $field
129	 *
130	 * @return CSelect
131	 */
132	public static function getSelect($field) {
133		return (new CSelect($field->getName()))
134			->setId($field->getName())
135			->setFocusableElementId('label-'.$field->getName())
136			->setValue($field->getValue())
137			->addOptions(CSelect::createOptionsFromArray($field->getValues()))
138			->setDisabled($field->getFlags() & CWidgetField::FLAG_DISABLED)
139			->setAriaRequired(self::isAriaRequired($field));
140	}
141
142	/**
143	 * @param CWidgetFieldTextBox $field
144	 *
145	 * @return CTextBox
146	 */
147	public static function getTextBox($field) {
148		return (new CTextBox($field->getName(), $field->getValue()))
149			->setAriaRequired(self::isAriaRequired($field))
150			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
151			->setAttribute('placeholder', $field->getPlaceholder())
152			->setWidth($field->getWidth());
153	}
154
155	/**
156	 * @param CWidgetFieldUrl $field
157	 *
158	 * @return CTextBox
159	 */
160	public static function getUrlBox($field) {
161		return (new CTextBox($field->getName(), $field->getValue()))
162			->setAriaRequired(self::isAriaRequired($field))
163			->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH);
164	}
165
166	/**
167	 * @param CWidgetFieldRangeControl $field
168	 *
169	 * @return CRangeControl
170	 */
171	public static function getRangeControl($field) {
172		return (new CRangeControl($field->getName(), (int) $field->getValue()))
173			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
174			->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
175			->setStep($field->getStep())
176			->setMin($field->getMin())
177			->setMax($field->getMax());
178	}
179
180	/**
181	 * @param CWidgetFieldHostPatternSelect  $field      Widget field object.
182	 * @param string                         $form_name  HTML form element name.
183	 *
184	 * @return CDiv
185	 */
186	public static function getHostPatternSelect($field, $form_name) {
187		return (new CPatternSelect([
188			'name' => $field->getName().'[]',
189			'object_name' => 'hosts',
190			'data' => $field->getValue(),
191			'placeholder' => $field->getPlaceholder(),
192			'popup' => [
193				'parameters' => [
194					'srctbl' => 'hosts',
195					'srcfld1' => 'hostid',
196					'dstfrm' => $form_name,
197					'dstfld1' => zbx_formatDomId($field->getName().'[]')
198				]
199			],
200			'add_post_js' => false
201		]))
202			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
203			->setAriaRequired(self::isAriaRequired($field))
204			->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH);
205	}
206
207	/**
208	 * @param CWidgetFieldCheckBox $field
209	 *
210	 * @return array
211	 */
212	public static function getCheckBox($field) {
213		return [(new CVar($field->getName(), '0'))->removeId(), (new CCheckBox($field->getName()))
214			->setChecked((bool) $field->getValue())
215			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
216			->setLabel($field->getCaption())
217			->onChange($field->getAction())
218		];
219	}
220
221	/**
222	 * Creates label linked to the multiselect field.
223	 *
224	 * @param CWidgetFieldMs $field
225	 *
226	 * @return CLabel
227	 */
228	public static function getMultiselectLabel($field) {
229		$field_name = $field->getName();
230
231		if ($field instanceof CWidgetFieldMs) {
232			$field_name .= ($field->isMultiple() ? '[]' : '');
233		}
234		else {
235			$field_name .= '[]';
236		}
237
238		return (new CLabel($field->getLabel(), $field_name.'_ms'))
239			->setAsteriskMark(self::isAriaRequired($field));
240	}
241
242	/**
243	 * @param CWidgetFieldMs $field
244	 * @param array $captions
245	 * @param string $form_name
246	 *
247	 * @return CMultiSelect
248	 */
249	private static function getMultiselectField($field, $captions, $form_name, $object_name, $popup_options) {
250		$field_name = $field->getName().($field->isMultiple() ? '[]' : '');
251		$options = [
252			'name' => $field_name,
253			'object_name' => $object_name,
254			'multiple' => $field->isMultiple(),
255			'data' => $captions,
256			'popup' => [
257				'parameters' => [
258					'dstfrm' => $form_name,
259					'dstfld1' => zbx_formatDomId($field_name)
260				] + $popup_options
261			],
262			'add_post_js' => false
263		];
264
265		if ($field instanceof CWidgetFieldMsHost && $field->filter_preselect_host_group_field) {
266			$options['popup']['filter_preselect_fields']['hostgroups'] = $field->filter_preselect_host_group_field;
267		}
268
269		return (new CMultiSelect($options))
270			->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH)
271			->setAriaRequired(self::isAriaRequired($field));
272	}
273
274	/**
275	 * @param CWidgetFieldMsGroup $field
276	 * @param array $captions
277	 * @param string $form_name
278	 *
279	 * @return CMultiSelect
280	 */
281	public static function getGroup($field, $captions, $form_name) {
282		return self::getMultiselectField($field, $captions, $form_name, 'hostGroup', [
283			'srctbl' => 'host_groups',
284			'srcfld1' => 'groupid',
285			'real_hosts' => true,
286			'enrich_parent_groups' => true
287		] + $field->getFilterParameters());
288	}
289
290	/**
291	 * @param CWidgetFieldMsHost $field
292	 * @param array $captions
293	 * @param string $form_name
294	 *
295	 * @return CMultiSelect
296	 */
297	public static function getHost($field, $captions, $form_name) {
298		return self::getMultiselectField($field, $captions, $form_name, 'hosts', [
299			'srctbl' => 'hosts',
300			'srcfld1' => 'hostid'
301		] + $field->getFilterParameters());
302	}
303
304	/**
305	 * @param CWidgetFieldMsItem $field
306	 * @param array $captions
307	 * @param string $form_name
308	 *
309	 * @return CMultiSelect
310	 */
311	public static function getItem($field, $captions, $form_name) {
312		return self::getMultiselectField($field, $captions, $form_name, 'items', [
313			'srctbl' => 'items',
314			'srcfld1' => 'itemid',
315			'webitems' => true
316		] + $field->getFilterParameters());
317	}
318
319	/**
320	 * @param CWidgetFieldMsGraph $field
321	 * @param array $captions
322	 * @param string $form_name
323	 *
324	 * @return CMultiSelect
325	 */
326	public static function getGraph($field, $captions, $form_name) {
327		return self::getMultiselectField($field, $captions, $form_name, 'graphs', [
328			'srctbl' => 'graphs',
329			'srcfld1' => 'graphid',
330			'srcfld2' => 'name',
331			'with_graphs' => true
332		] + $field->getFilterParameters());
333	}
334
335	/**
336	 * @param CWidgetFieldMsItemPrototype $field
337	 * @param array $captions
338	 * @param string $form_name
339	 *
340	 * @return CMultiSelect
341	 */
342	public static function getItemPrototype($field, $captions, $form_name) {
343		return self::getMultiselectField($field, $captions, $form_name, 'item_prototypes', [
344			'srctbl' => 'item_prototypes',
345			'srcfld1' => 'itemid'
346		] + $field->getFilterParameters());
347	}
348
349	/**
350	 * @param CWidgetFieldMsGraphPrototype $field
351	 * @param array $captions
352	 * @param string $form_name
353	 *
354	 * @return CMultiSelect
355	 */
356	public static function getGraphPrototype($field, $captions, $form_name) {
357		return self::getMultiselectField($field, $captions, $form_name, 'graph_prototypes', [
358			'srctbl' => 'graph_prototypes',
359			'srcfld1' => 'graphid',
360			'srcfld2' => 'name',
361			'with_graph_prototypes' => true
362		] + $field->getFilterParameters());
363	}
364
365	public static function getSelectResource($field, $caption, $form_name) {
366		return [
367			(new CTextBox($field->getName().'_caption', $caption, true))
368				->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH)
369				->setAriaRequired(self::isAriaRequired($field)),
370			(new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN),
371			(new CButton('select', _('Select')))
372				->addClass(ZBX_STYLE_BTN_GREY)
373				->onClick('return PopUp("popup.generic",'.
374					json_encode($field->getPopupOptions($form_name)).', null, this);')
375		];
376	}
377
378	/**
379	 * Creates select field without values, to later fill it by JS script.
380	 *
381	 * @param CWidgetFieldWidgetSelect $field
382	 *
383	 * @return CSelect
384	 */
385	public static function getEmptySelect($field) {
386		return (new CSelect($field->getName()))
387			->setFocusableElementId('label-'.$field->getName())
388			->setId($field->getName())
389			->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH)
390			->setAriaRequired(self::isAriaRequired($field));
391	}
392
393	/**
394	 * @param CWidgetFieldIntegerBox $field
395	 *
396	 * @return CNumericBox
397	 */
398	public static function getIntegerBox($field) {
399		return (new CNumericBox($field->getName(), $field->getValue(), $field->getMaxLength()))
400			->setWidth(ZBX_TEXTAREA_NUMERIC_STANDARD_WIDTH)
401			->setAriaRequired(self::isAriaRequired($field));
402	}
403
404	/**
405	 * @param CWidgetFieldNumericBox $field
406	 *
407	 * @return CTextBox
408	 */
409	public static function getNumericBox($field) {
410		return (new CTextBox($field->getName(), $field->getValue()))
411			->setAriaRequired(self::isAriaRequired($field))
412			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
413			->setAttribute('placeholder', $field->getPlaceholder())
414			->setWidth($field->getWidth());
415	}
416
417	/**
418	 * @param CWidgetFieldRadioButtonList $field
419	 *
420	 * @return CRadioButtonList
421	 */
422	public static function getRadioButtonList($field) {
423		$radio_button_list = (new CRadioButtonList($field->getName(), $field->getValue()))
424			->setModern($field->getModern())
425			->setAriaRequired(self::isAriaRequired($field));
426
427		foreach ($field->getValues() as $key => $value) {
428			$radio_button_list
429				->addValue($value, $key, null, $field->getAction())
430				->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED));
431		}
432
433		return $radio_button_list;
434	}
435
436	/**
437	 * @param CWidgetFieldSeverities $field
438	 *
439	 * @return CSeverityCheckBoxList
440	 */
441	public static function getSeverities($field) {
442		return (new CSeverityCheckBoxList($field->getName()))
443			->setChecked($field->getValue())
444			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
445			->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH);
446	}
447
448	/**
449	 * @param CWidgetFieldCheckBoxList $field
450	 * @param array                    $list  Option list array.
451	 *
452	 * @return CList
453	 */
454	public static function getCheckBoxList($field, array $list) {
455		$checkbox_list = (new CList())->addClass(ZBX_STYLE_LIST_CHECK_RADIO);
456
457		foreach ($list as $key => $label) {
458			$checkbox_list->addItem(
459				(new CCheckBox($field->getName().'[]', $key))
460					->setLabel($label)
461					->setId($field->getName().'_'.$key)
462					->setChecked(in_array($key, $field->getValue()))
463					->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
464			);
465		}
466
467		return $checkbox_list;
468	}
469
470	/**
471	 * @param CWidgetFieldTags $field
472	 *
473	 * @return CTable
474	 */
475	public static function getTags($field) {
476		$tags = $field->getValue();
477
478		if (!$tags) {
479			$tags = [['tag' => '', 'operator' => TAG_OPERATOR_LIKE, 'value' => '']];
480		}
481
482		$tags_table = (new CTable())->setId('tags_table_'.$field->getName());
483		$enabled = !($field->getFlags() & CWidgetField::FLAG_DISABLED);
484		$i = 0;
485
486		foreach ($tags as $tag) {
487			$zselect_operator = (new CSelect($field->getName().'['.$i.'][operator]'))
488				->addOptions(CSelect::createOptionsFromArray([
489					TAG_OPERATOR_EXISTS => _('Exists'),
490					TAG_OPERATOR_EQUAL => _('Equals'),
491					TAG_OPERATOR_LIKE => _('Contains'),
492					TAG_OPERATOR_NOT_EXISTS => _('Does not exist'),
493					TAG_OPERATOR_NOT_EQUAL => _('Does not equal'),
494					TAG_OPERATOR_NOT_LIKE => _('Does not contain')
495				]))
496				->setValue($tag['operator'])
497				->setFocusableElementId($field->getName().'-'.$i.'-operator-select')
498				->setId($field->getName().'_'.$i.'_operator');
499
500			if (!$enabled) {
501				$zselect_operator->setDisabled();
502			}
503
504			$tags_table->addRow([
505				(new CTextBox($field->getName().'['.$i.'][tag]', $tag['tag']))
506					->setAttribute('placeholder', _('tag'))
507					->setWidth(ZBX_TEXTAREA_FILTER_SMALL_WIDTH)
508					->setAriaRequired(self::isAriaRequired($field))
509					->setEnabled($enabled),
510				$zselect_operator,
511				(new CTextBox($field->getName().'['.$i.'][value]', $tag['value']))
512					->setAttribute('placeholder', _('value'))
513					->setWidth(ZBX_TEXTAREA_FILTER_SMALL_WIDTH)
514					->setAriaRequired(self::isAriaRequired($field))
515					->setId($field->getName().'_'.$i.'_value')
516					->setEnabled($enabled),
517				(new CCol(
518					(new CButton($field->getName().'['.$i.'][remove]', _('Remove')))
519						->addClass(ZBX_STYLE_BTN_LINK)
520						->addClass('element-table-remove')
521						->setEnabled($enabled)
522				))->addClass(ZBX_STYLE_NOWRAP)
523			], 'form_row');
524
525			$i++;
526		}
527
528		$tags_table->addRow(
529			(new CCol(
530				(new CButton('tags_add', _('Add')))
531					->addClass(ZBX_STYLE_BTN_LINK)
532					->addClass('element-table-add')
533					->setEnabled($enabled)
534			))->setColSpan(3)
535		);
536
537		return $tags_table;
538	}
539
540	/**
541	 * JS Template for one tag line for Tags field
542	 *
543	 * @param CWidgetFieldTags $field
544	 *
545	 * @return string
546	 */
547	public static function getTagsTemplate($field) {
548		return (new CRow([
549			(new CTextBox($field->getName().'[#{rowNum}][tag]'))
550				->setAttribute('placeholder', _('tag'))
551				->setWidth(ZBX_TEXTAREA_FILTER_SMALL_WIDTH)
552				->setAriaRequired(self::isAriaRequired($field)),
553			(new CSelect($field->getName().'[#{rowNum}][operator]'))
554				->addOptions(CSelect::createOptionsFromArray([
555					TAG_OPERATOR_EXISTS => _('Exists'),
556					TAG_OPERATOR_EQUAL => _('Equals'),
557					TAG_OPERATOR_LIKE => _('Contains'),
558					TAG_OPERATOR_NOT_EXISTS => _('Does not exist'),
559					TAG_OPERATOR_NOT_EQUAL => _('Does not equal'),
560					TAG_OPERATOR_NOT_LIKE => _('Does not contain')
561				]))
562				->setValue(TAG_OPERATOR_LIKE)
563				->setFocusableElementId($field->getName().'-#{rowNum}-operator-select')
564				->setId($field->getName().'_#{rowNum}_operator'),
565			(new CTextBox($field->getName().'[#{rowNum}][value]'))
566				->setAttribute('placeholder', _('value'))
567				->setWidth(ZBX_TEXTAREA_FILTER_SMALL_WIDTH)
568				->setAriaRequired(self::isAriaRequired($field))
569				->setId($field->getName().'_#{rowNum}_value'),
570			(new CCol(
571				(new CButton($field->getName().'[#{rowNum}][remove]', _('Remove')))
572					->addClass(ZBX_STYLE_BTN_LINK)
573					->addClass('element-table-remove')
574			))->addClass(ZBX_STYLE_NOWRAP)
575		]))
576			->addClass('form_row')
577			->toString();
578	}
579
580	/**
581	 * @param CWidgetFieldDatePicker $field
582	 *
583	 * @return CDateSelector
584	 */
585	public static function getDatePicker($field) {
586		return (new CDateSelector($field->getName(), $field->getValue()))
587			->setAriaRequired(self::isAriaRequired($field))
588			->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED));
589	}
590
591	/**
592	 * Function returns array containing HTML objects filled with given values. Used to generate HTML in widget
593	 * overrides field.
594	 *
595	 * @param CWidgetFieldGraphOverride  $field
596	 * @param array                      $value      Values to fill in particular data set row. See self::setValue() for
597	 *                                               detailed description.
598	 * @param string                     $form_name  Name of form in which data set fields resides.
599	 * @param int|string                 $row_num    Unique data set numeric identifier. Used to make unique field names.
600	 *
601	 * @return array
602	 */
603	public static function getGraphOverrideLayout($field, array $value, $form_name, $row_num) {
604		$inputs = [];
605
606		// Create override optins list.
607		foreach (CWidgetFieldGraphOverride::getOverrideOptions() as $option) {
608			if (array_key_exists($option, $value)) {
609				$inputs[] = (new CVar($field->getName().'['.$row_num.']['.$option.']', $value[$option]));
610			}
611		}
612
613		return (new CListItem([
614			/**
615			 * First line: host pattern field, item pattern field.
616			 * Contains also drag and drop button and delete button.
617			 */
618			(new CDiv([
619				(new CDiv())
620					->addClass(ZBX_STYLE_DRAG_ICON)
621					->addStyle('position: absolute; margin-left: -25px;'),
622				(new CDiv([
623					(new CDiv(
624						(new CPatternSelect([
625							'name' => $field->getName().'['.$row_num.'][hosts][]',
626							'object_name' => 'hosts',
627							'data' => $value['hosts'],
628							'placeholder' => _('host pattern'),
629							'popup' => [
630								'parameters' => [
631									'srctbl' => 'hosts',
632									'srcfld1' => 'hostid',
633									'dstfrm' => $form_name,
634									'dstfld1' => zbx_formatDomId($field->getName().'['.$row_num.'][hosts][]')
635								]
636							],
637							'add_post_js' => false
638						]))
639							->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
640							->setAriaRequired(self::isAriaRequired($field))
641							->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
642					))->addClass(ZBX_STYLE_COLUMN_50),
643					(new CDiv(
644						(new CPatternSelect([
645							'name' => $field->getName().'['.$row_num.'][items][]',
646							'object_name' => 'items',
647							'data' => $value['items'],
648							'placeholder' => _('item pattern'),
649							'multiple' => true,
650							'popup' => [
651								'parameters' => [
652									'srctbl' => 'items',
653									'srcfld1' => 'itemid',
654									'real_hosts' => 1,
655									'numeric' => 1,
656									'webitems' => 1,
657									'orig_names' => 1,
658									'dstfrm' => $form_name,
659									'dstfld1' => zbx_formatDomId($field->getName().'['.$row_num.'][items][]')
660								]
661							],
662							'add_post_js' => false
663						]))
664							->setEnabled(!($field->getFlags() & CWidgetField::FLAG_DISABLED))
665							->setAriaRequired(self::isAriaRequired($field))
666							->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
667					))->addClass(ZBX_STYLE_COLUMN_50)
668				]))
669					->addClass(ZBX_STYLE_COLUMNS)
670					->addClass(ZBX_STYLE_COLUMNS_NOWRAP)
671					->addClass(ZBX_STYLE_COLUMN_95),
672
673				(new CDiv(
674					(new CButton())
675						->setAttribute('title', _('Delete'))
676						->addClass(ZBX_STYLE_REMOVE_BTN)
677						->removeId()
678				))
679					->addClass(ZBX_STYLE_COLUMN_5)
680			]))
681				->addClass(ZBX_STYLE_COLUMNS),
682
683			// Selected override options.
684			(new CList($inputs))
685				->addClass(ZBX_STYLE_OVERRIDES_OPTIONS_LIST)
686				->addItem((new CButton(null, (new CSpan())
687							->addClass(ZBX_STYLE_PLUS_ICON)
688							->addStyle('margin-right: 0px;')
689					))
690						->setAttribute('data-row', $row_num)
691						->addClass(ZBX_STYLE_BTN_ALT)
692				)
693		]))
694			->addClass(ZBX_STYLE_OVERRIDES_LIST_ITEM);
695	}
696
697	/**
698	 * Return template used by dynamic rows in CWidgetFieldGraphOverride field.
699	 *
700	 * @param CWidgetFieldGraphOverride $field
701	 * @param string                    $form_name  Form name in which override field is located.
702	 *
703	 * @return string
704	 */
705	public static function getGraphOverrideTemplate($field, $form_name) {
706		$value = CWidgetFieldGraphOverride::getDefaults();
707
708		return self::getGraphOverrideLayout($field, $value, $form_name, '#{rowNum}')->toString();
709	}
710
711	/**
712	 * @param CWidgetFieldGraphOverride $field
713	 *
714	 * @return CList
715	 */
716	public static function getGraphOverride($field, $form_name) {
717		$list = (new CList())->addClass(ZBX_STYLE_OVERRIDES_LIST);
718
719		$values = $field->getValue();
720
721		if (!$values) {
722			$values = [];
723		}
724
725		$i = 0;
726
727		foreach ($values as $override) {
728			$list->addItem(self::getGraphOverrideLayout($field, $override, $form_name, $i));
729
730			$i++;
731		}
732
733		// Add 'Add' button under the list.
734		$list->addItem(
735			(new CDiv(
736				(new CButton('override_add', [(new CSpan())->addClass(ZBX_STYLE_PLUS_ICON), _('Add new override')]))
737					->addClass(ZBX_STYLE_BTN_ALT)
738					->setId('override-add')
739			)),
740			'overrides-foot'
741		);
742
743		return $list;
744	}
745
746	/**
747	 * Function returns array containing string values used as titles for override options.
748	 *
749	 * @return array
750	 */
751	private static function getGraphOverrideOptionNames() {
752		return [
753			'width' => _('Width'),
754			'type' => _('Draw'),
755			'type'.SVG_GRAPH_TYPE_LINE => _('Line'),
756			'type'.SVG_GRAPH_TYPE_POINTS => _('Points'),
757			'type'.SVG_GRAPH_TYPE_STAIRCASE => _('Staircase'),
758			'type'.SVG_GRAPH_TYPE_BAR => _('Bar'),
759			'transparency' => _('Transparency'),
760			'fill' => _('Fill'),
761			'pointsize' => _('Point size'),
762			'missingdatafunc' => _('Missing data'),
763			'missingdatafunc'.SVG_GRAPH_MISSING_DATA_NONE => _('None'),
764			'missingdatafunc'.SVG_GRAPH_MISSING_DATA_CONNECTED => _x('Connected', 'missing data function'),
765			'missingdatafunc'.SVG_GRAPH_MISSING_DATA_TREAT_AS_ZERO => _x('Treat as 0', 'missing data function'),
766			'axisy' => _('Y-axis'),
767			'axisy'.GRAPH_YAXIS_SIDE_LEFT => _('Left'),
768			'axisy'.GRAPH_YAXIS_SIDE_RIGHT => _('Right'),
769			'timeshift' => _('Time shift')
770		];
771	}
772
773	/**
774	 * Function returns array used to construct override field menu of available override options.
775	 *
776	 * @return array
777	 */
778	private static function getGraphOverrideMenu() {
779		return [
780			'sections' => [
781				[
782					'name' => _('ADD OVERRIDE'),
783					'options' => [
784						['name' => _('Base colour'), 'callback' => 'addOverride', 'args' => ['color', '']],
785
786						['name' => _('Width').'/0', 'callback' => 'addOverride', 'args' => ['width', 0]],
787						['name' => _('Width').'/1', 'callback' => 'addOverride', 'args' => ['width', 1]],
788						['name' => _('Width').'/2', 'callback' => 'addOverride', 'args' => ['width', 2]],
789						['name' => _('Width').'/3', 'callback' => 'addOverride', 'args' => ['width', 3]],
790						['name' => _('Width').'/4', 'callback' => 'addOverride', 'args' => ['width', 4]],
791						['name' => _('Width').'/5', 'callback' => 'addOverride', 'args' => ['width', 5]],
792						['name' => _('Width').'/6', 'callback' => 'addOverride', 'args' => ['width', 6]],
793						['name' => _('Width').'/7', 'callback' => 'addOverride', 'args' => ['width', 7]],
794						['name' => _('Width').'/8', 'callback' => 'addOverride', 'args' => ['width', 8]],
795						['name' => _('Width').'/9', 'callback' => 'addOverride', 'args' => ['width', 9]],
796						['name' => _('Width').'/10', 'callback' => 'addOverride', 'args' => ['width', 10]],
797
798						['name' => _('Draw').'/'._('Line'), 'callback' => 'addOverride', 'args' => ['type', SVG_GRAPH_TYPE_LINE]],
799						['name' => _('Draw').'/'._('Points'), 'callback' => 'addOverride', 'args' => ['type', SVG_GRAPH_TYPE_POINTS]],
800						['name' => _('Draw').'/'._('Staircase'), 'callback' => 'addOverride', 'args' => ['type', SVG_GRAPH_TYPE_STAIRCASE]],
801						['name' => _('Draw').'/'._('Bar'), 'callback' => 'addOverride', 'args' => ['type', SVG_GRAPH_TYPE_BAR]],
802
803						['name' => _('Transparency').'/0', 'callback' => 'addOverride', 'args' => ['transparency', 0]],
804						['name' => _('Transparency').'/1', 'callback' => 'addOverride', 'args' => ['transparency', 1]],
805						['name' => _('Transparency').'/2', 'callback' => 'addOverride', 'args' => ['transparency', 2]],
806						['name' => _('Transparency').'/3', 'callback' => 'addOverride', 'args' => ['transparency', 3]],
807						['name' => _('Transparency').'/4', 'callback' => 'addOverride', 'args' => ['transparency', 4]],
808						['name' => _('Transparency').'/5', 'callback' => 'addOverride', 'args' => ['transparency', 5]],
809						['name' => _('Transparency').'/6', 'callback' => 'addOverride', 'args' => ['transparency', 6]],
810						['name' => _('Transparency').'/7', 'callback' => 'addOverride', 'args' => ['transparency', 7]],
811						['name' => _('Transparency').'/8', 'callback' => 'addOverride', 'args' => ['transparency', 8]],
812						['name' => _('Transparency').'/9', 'callback' => 'addOverride', 'args' => ['transparency', 9]],
813						['name' => _('Transparency').'/10', 'callback' => 'addOverride', 'args' => ['transparency', 10]],
814
815						['name' => _('Fill').'/0', 'callback' => 'addOverride', 'args' => ['fill', 0]],
816						['name' => _('Fill').'/1', 'callback' => 'addOverride', 'args' => ['fill', 1]],
817						['name' => _('Fill').'/2', 'callback' => 'addOverride', 'args' => ['fill', 2]],
818						['name' => _('Fill').'/3', 'callback' => 'addOverride', 'args' => ['fill', 3]],
819						['name' => _('Fill').'/4', 'callback' => 'addOverride', 'args' => ['fill', 4]],
820						['name' => _('Fill').'/5', 'callback' => 'addOverride', 'args' => ['fill', 5]],
821						['name' => _('Fill').'/6', 'callback' => 'addOverride', 'args' => ['fill', 6]],
822						['name' => _('Fill').'/7', 'callback' => 'addOverride', 'args' => ['fill', 7]],
823						['name' => _('Fill').'/8', 'callback' => 'addOverride', 'args' => ['fill', 8]],
824						['name' => _('Fill').'/9', 'callback' => 'addOverride', 'args' => ['fill', 9]],
825						['name' => _('Fill').'/10', 'callback' => 'addOverride', 'args' => ['fill', 10]],
826
827						['name' => _('Point size').'/1', 'callback' => 'addOverride', 'args' => ['pointsize', 1]],
828						['name' => _('Point size').'/2', 'callback' => 'addOverride', 'args' => ['pointsize', 2]],
829						['name' => _('Point size').'/3', 'callback' => 'addOverride', 'args' => ['pointsize', 3]],
830						['name' => _('Point size').'/4', 'callback' => 'addOverride', 'args' => ['pointsize', 4]],
831						['name' => _('Point size').'/5', 'callback' => 'addOverride', 'args' => ['pointsize', 5]],
832						['name' => _('Point size').'/6', 'callback' => 'addOverride', 'args' => ['pointsize', 6]],
833						['name' => _('Point size').'/7', 'callback' => 'addOverride', 'args' => ['pointsize', 7]],
834						['name' => _('Point size').'/8', 'callback' => 'addOverride', 'args' => ['pointsize', 8]],
835						['name' => _('Point size').'/9', 'callback' => 'addOverride', 'args' => ['pointsize', 9]],
836						['name' => _('Point size').'/10', 'callback' => 'addOverride', 'args' => ['pointsize', 10]],
837
838						['name' => _('Missing data').'/'._('None'), 'callback' => 'addOverride', 'args' => ['missingdatafunc', SVG_GRAPH_MISSING_DATA_NONE]],
839						['name' => _('Missing data').'/'._x('Connected', 'missing data function'), 'callback' => 'addOverride', 'args' => ['missingdatafunc', SVG_GRAPH_MISSING_DATA_CONNECTED]],
840						['name' => _('Missing data').'/'._x('Treat as 0', 'missing data function'), 'callback' => 'addOverride', 'args' => ['missingdatafunc', SVG_GRAPH_MISSING_DATA_TREAT_AS_ZERO]],
841
842						['name' => _('Y-axis').'/'._('Left'), 'callback' => 'addOverride', 'args' => ['axisy', GRAPH_YAXIS_SIDE_LEFT]],
843						['name' => _('Y-axis').'/'._('Right'), 'callback' => 'addOverride', 'args' => ['axisy', GRAPH_YAXIS_SIDE_RIGHT]],
844
845						['name' => _('Time shift'), 'callback' => 'addOverride', 'args' => ['timeshift']]
846					]
847				]
848			]
849		];
850	}
851
852	/**
853	 * Return javascript necessary to initialize CWidgetFieldGraphOverride field.
854	 *
855	 * @param CWidgetFieldGraphOverride $field
856	 *
857	 * @return string
858	 */
859	public static function getGraphOverrideJavascript($field) {
860		$scripts = [
861			// Define it as function to avoid redundancy.
862			'function initializeOverrides() {'.
863				'jQuery("#overrides .'.ZBX_STYLE_OVERRIDES_OPTIONS_LIST.'").overrides({'.
864					'add: ".'.ZBX_STYLE_BTN_ALT.'",'.
865					'options: "input[type=hidden]",'.
866					'captions: '.json_encode(self::getGraphOverrideOptionNames()).','.
867					'makeName: function(option, row_id) {'.
868						'return "'.$field->getName().'[" + row_id + "][" + option + "]";'.
869					'},'.
870					'makeOption: function(name) {'.
871						'return name.match('.
872							'/.*\[('.implode('|', CWidgetFieldGraphOverride::getOverrideOptions()).')\]/'.
873						')[1];'.
874					'},'.
875					'override: ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'",'.
876					'overridesList: ".'.ZBX_STYLE_OVERRIDES_LIST.'",'.
877					'onUpdate: onGraphConfigChange,'.
878					'menu: '.json_encode(self::getGraphOverrideMenu()).
879				'});'.
880			'}',
881
882			// Initialize dynamicRows.
883			'jQuery("#overrides")'.
884				'.dynamicRows({'.
885					'template: "#overrides-row",'.
886					'beforeRow: ".overrides-foot",'.
887					'remove: ".'.ZBX_STYLE_REMOVE_BTN.'",'.
888					'add: "#override-add",'.
889					'row: ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'"'.
890				'})'.
891				'.bind("afteradd.dynamicRows", function(event, options) {'.
892					'var container = jQuery(".overlay-dialogue-body");'.
893					'container.scrollTop(Math.max(container.scrollTop(),
894						jQuery("#widget-dialogue-form")[0].scrollHeight - container.height()
895					));'.
896
897					'jQuery(".multiselect", jQuery("#overrides")).each(function() {'.
898						'jQuery(this).multiSelect(jQuery(this).data("params"));'.
899					'});'.
900					'updateVariableOrder(jQuery("#overrides"), ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'", "or");'.
901					'onGraphConfigChange();'.
902				'})'.
903				'.bind("afterremove.dynamicRows", function(event, options) {'.
904					'updateVariableOrder(jQuery("#overrides"), ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'", "or");'.
905					'onGraphConfigChange();'.
906				'})'.
907				'.bind("tableupdate.dynamicRows", function(event, options) {'.
908					'updateVariableOrder(jQuery("#overrides"), ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'", "or");'.
909					'initializeOverrides();'.
910					'if (jQuery("#overrides .'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'").length > 1) {'.
911						'jQuery("#overrides .drag-icon").removeClass("disabled");'.
912						'jQuery("#overrides").sortable("enable");'.
913					'}'.
914					'else {'.
915						'jQuery("#overrides .drag-icon").addClass("disabled");'.
916						'jQuery("#overrides").sortable("disable");'.
917					'}'.
918				'});',
919
920			// Initialize overrides UI control.
921			'initializeOverrides();',
922
923			// Initialize override pattern-selectors.
924			'jQuery(".multiselect", jQuery("#overrides")).each(function() {'.
925				'jQuery(this).multiSelect(jQuery(this).data("params"));'.
926			'});',
927
928			// Make overrides sortable.
929			'if (jQuery("#overrides .'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'").length < 2) {'.
930				'jQuery("#overrides .drag-icon").addClass("disabled");'.
931			'}'.
932			'jQuery("#overrides").sortable({'.
933				'items: ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'",'.
934				'containment: "parent",'.
935				'handle: ".drag-icon",'.
936				'tolerance: "pointer",'.
937				'scroll: false,'.
938				'cursor: "grabbing",'.
939				'opacity: 0.6,'.
940				'axis: "y",'.
941				'disabled: function() {'.
942					'return jQuery("#overrides .'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'").length < 2;'.
943				'}(),'.
944				'start: function() {'. // Workaround to fix wrong scrolling at initial sort.
945					'jQuery(this).sortable("refreshPositions");'.
946				'},'.
947				'stop: onGraphConfigChange,'.
948				'update: function() {'.
949					'updateVariableOrder(jQuery("#overrides"), ".'.ZBX_STYLE_OVERRIDES_LIST_ITEM.'", "or");'.
950				'}'.
951			'});'
952		];
953
954		return implode ('', $scripts);
955	}
956
957	/**
958	 * Function returns array containing HTML objects filled with given values. Used to generate HTML row in widget
959	 * data set field.
960	 *
961	 * @param string     $field_name
962	 * @param array      $value      Values to fill in particular data set row. See self::setValue() for detailed
963	 *                               description.
964	 * @param string     $form_name  Name of form in which data set fields resides.
965	 * @param int|string $row_num    Unique data set numeric identifier. Used to make unique field names.
966	 * @param bool       $is_opened  Either accordion row is made opened or closed.
967	 *
968	 * @return CListItem
969	 */
970	private static function getGraphDataSetLayout($field_name, array $value, $form_name, $row_num, $is_opened) {
971		return (new CListItem([
972			// Accordion head - data set selection fields and tools.
973			(new CDiv([
974				(new CDiv())
975					->addClass(ZBX_STYLE_DRAG_ICON)
976					->addStyle('position: absolute; margin-left: -25px;'),
977				(new CDiv([
978					(new CDiv([
979						(new CButton())
980							->addClass(ZBX_STYLE_COLOR_PREVIEW_BOX)
981							->addStyle('background-color: #'.$value['color'].';')
982							->setAttribute('title', $is_opened ? _('Collapse') : _('Expand'))
983							->removeId(),
984						(new CPatternSelect([
985							'name' => $field_name.'['.$row_num.'][hosts][]',
986							'object_name' => 'hosts',
987							'data' => $value['hosts'],
988							'placeholder' => _('host pattern'),
989							'popup' => [
990								'parameters' => [
991									'srctbl' => 'hosts',
992									'srcfld1' => 'host',
993									'dstfrm' => $form_name,
994									'dstfld1' => zbx_formatDomId($field_name.'['.$row_num.'][hosts][]')
995								]
996							],
997							'add_post_js' => false
998						]))->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
999					]))->addClass(ZBX_STYLE_COLUMN_50),
1000					(new CDiv(
1001						(new CPatternSelect([
1002							'name' => $field_name.'['.$row_num.'][items][]',
1003							'object_name' => 'items',
1004							'data' => $value['items'],
1005							'placeholder' => _('item pattern'),
1006							'popup' => [
1007								'parameters' => [
1008									'srctbl' => 'items',
1009									'srcfld1' => 'name',
1010									'real_hosts' => 1,
1011									'numeric' => 1,
1012									'webitems' => 1,
1013									'orig_names' => 1,
1014									'dstfrm' => $form_name,
1015									'dstfld1' => zbx_formatDomId($field_name.'['.$row_num.'][items][]')
1016								]
1017							],
1018							'add_post_js' => false
1019						]))->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
1020					))->addClass(ZBX_STYLE_COLUMN_50)
1021				]))
1022					->addClass(ZBX_STYLE_COLUMNS)
1023					->addClass(ZBX_STYLE_COLUMNS_NOWRAP)
1024					->addClass(ZBX_STYLE_COLUMN_95),
1025				(new CDiv([
1026					(new CButton())
1027						->setAttribute('title', _('Delete'))
1028						->addClass(ZBX_STYLE_REMOVE_BTN)
1029						->removeId()
1030				]))->addClass(ZBX_STYLE_COLUMN_5)
1031			]))
1032				->addClass(ZBX_STYLE_LIST_ACCORDION_ITEM_HEAD)
1033				->addClass(ZBX_STYLE_COLUMNS),
1034
1035			// Accordion body - data set configuration options.
1036			(new CDiv(
1037				(new CDiv([
1038					// Left column fields.
1039					(new CDiv(
1040						(new CFormList())
1041							->addRow(_('Base colour'),
1042								(new CColor($field_name.'['.$row_num.'][color]', $value['color']))
1043									->appendColorPickerJs(false)
1044							)
1045							->addRow(_('Draw'),
1046								(new CRadioButtonList($field_name.'['.$row_num.'][type]', (int) $value['type']))
1047									->addValue(_('Line'), SVG_GRAPH_TYPE_LINE)
1048									->addValue(_('Points'), SVG_GRAPH_TYPE_POINTS)
1049									->addValue(_('Staircase'), SVG_GRAPH_TYPE_STAIRCASE)
1050									->addValue(_('Bar'), SVG_GRAPH_TYPE_BAR)
1051									->onChange('changeDataSetDrawType(this)')
1052									->setModern(true)
1053							)
1054							->addRow(_('Width'),
1055								(new CRangeControl($field_name.'['.$row_num.'][width]', (int) $value['width']))
1056									->setEnabled(!in_array($value['type'], [SVG_GRAPH_TYPE_POINTS, SVG_GRAPH_TYPE_BAR]))
1057									->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
1058									->setStep(1)
1059									->setMin(0)
1060									->setMax(10)
1061							)
1062							->addRow(_('Point size'),
1063								(new CRangeControl($field_name.'['.$row_num.'][pointsize]', (int) $value['pointsize']))
1064									->setEnabled($value['type'] == SVG_GRAPH_TYPE_POINTS)
1065									->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
1066									->setStep(1)
1067									->setMin(1)
1068									->setMax(10)
1069							)
1070							->addRow(_('Transparency'),
1071								(new CRangeControl($field_name.'['.$row_num.'][transparency]',
1072										(int) $value['transparency'])
1073									)
1074									->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
1075									->setStep(1)
1076									->setMin(0)
1077									->setMax(10)
1078							)
1079							->addRow(_('Fill'),
1080								(new CRangeControl($field_name.'['.$row_num.'][fill]', (int) $value['fill']))
1081									->setEnabled(!in_array($value['type'], [SVG_GRAPH_TYPE_POINTS, SVG_GRAPH_TYPE_BAR]))
1082									->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH)
1083									->setStep(1)
1084									->setMin(0)
1085									->setMax(10)
1086							)
1087						)
1088					)
1089						->addClass(ZBX_STYLE_COLUMN_50),
1090
1091					// Right column fields.
1092					(new CDiv(
1093						(new CFormList())
1094							->addRow(_('Missing data'),
1095								(new CRadioButtonList($field_name.'['.$row_num.'][missingdatafunc]',
1096										(int) $value['missingdatafunc'])
1097									)
1098									->addValue(_('None'), SVG_GRAPH_MISSING_DATA_NONE)
1099									->addValue(_x('Connected', 'missing data function'),
1100										SVG_GRAPH_MISSING_DATA_CONNECTED
1101									)
1102									->addValue(_x('Treat as 0', 'missing data function'),
1103										SVG_GRAPH_MISSING_DATA_TREAT_AS_ZERO
1104									)
1105									->setEnabled(!in_array($value['type'], [SVG_GRAPH_TYPE_POINTS, SVG_GRAPH_TYPE_BAR]))
1106									->setModern(true)
1107							)
1108							->addRow(_('Y-axis'),
1109								(new CRadioButtonList($field_name.'['.$row_num.'][axisy]', (int) $value['axisy']))
1110									->addValue(_('Left'), GRAPH_YAXIS_SIDE_LEFT)
1111									->addValue(_('Right'), GRAPH_YAXIS_SIDE_RIGHT)
1112									->setModern(true)
1113							)
1114							->addRow(_('Time shift'),
1115								(new CTextBox($field_name.'['.$row_num.'][timeshift]', $value['timeshift']))
1116									->setAttribute('placeholder', _('none'))
1117									->setWidth(ZBX_TEXTAREA_TINY_WIDTH)
1118							)
1119							->addRow(
1120								new CLabel(_('Aggregation function'),
1121									'label-'.$field_name.'_'.$row_num.'_aggregate_function'
1122								),
1123								(new CSelect($field_name.'['.$row_num.'][aggregate_function]'))
1124									->setId($field_name.'_'.$row_num.'_aggregate_function')
1125									->setFocusableElementId('label-'.$field_name.'_'.$row_num.'_aggregate_function')
1126									->setValue((int) $value['aggregate_function'])
1127									->addOptions(CSelect::createOptionsFromArray([
1128										GRAPH_AGGREGATE_NONE => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_NONE),
1129										GRAPH_AGGREGATE_MIN => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_MIN),
1130										GRAPH_AGGREGATE_MAX => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_MAX),
1131										GRAPH_AGGREGATE_AVG => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_AVG),
1132										GRAPH_AGGREGATE_COUNT => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_COUNT),
1133										GRAPH_AGGREGATE_SUM => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_SUM),
1134										GRAPH_AGGREGATE_FIRST => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_FIRST),
1135										GRAPH_AGGREGATE_LAST => graph_item_aggr_fnc2str(GRAPH_AGGREGATE_LAST)
1136									]))
1137									->setWidth(ZBX_TEXTAREA_TINY_WIDTH)
1138							)
1139							->addRow(_('Aggregation interval'),
1140								(new CTextBox(
1141									$field_name.'['.$row_num.'][aggregate_interval]',
1142									$value['aggregate_interval']
1143								))
1144									->setEnabled($value['aggregate_function'] != GRAPH_AGGREGATE_NONE)
1145									->setAttribute('placeholder', GRAPH_AGGREGATE_DEFAULT_INTERVAL)
1146									->setWidth(ZBX_TEXTAREA_TINY_WIDTH)
1147							)
1148							->addRow(_('Aggregate'),
1149								(new CRadioButtonList(
1150									$field_name.'['.$row_num.'][aggregate_grouping]',
1151									(int) $value['aggregate_grouping'])
1152								)
1153									->addValue(_('Each item'), GRAPH_AGGREGATE_BY_ITEM)
1154									->addValue(_('Data set'), GRAPH_AGGREGATE_BY_DATASET)
1155									->setEnabled($value['aggregate_function'] != GRAPH_AGGREGATE_NONE)
1156									->setModern(true)
1157							)
1158					))
1159						->addClass(ZBX_STYLE_COLUMN_50)
1160				]))
1161					->addClass(ZBX_STYLE_COLUMNS)
1162					->addClass(ZBX_STYLE_COLUMNS_NOWRAP)
1163					->addClass(ZBX_STYLE_COLUMN_95)
1164			))
1165				->addClass(ZBX_STYLE_LIST_ACCORDION_ITEM_BODY)
1166				->addClass(ZBX_STYLE_COLUMNS)
1167		]))
1168			->addClass(ZBX_STYLE_LIST_ACCORDION_ITEM)
1169			->addClass($is_opened ? ZBX_STYLE_LIST_ACCORDION_ITEM_OPENED : ZBX_STYLE_LIST_ACCORDION_ITEM_CLOSED);
1170	}
1171
1172	/**
1173	 * Return template used by dynamic rows in CWidgetFieldGraphDataSet field.
1174	 *
1175	 * @param CWidgetFieldGraphDataSet $field
1176	 * @param string                   $form_name   Form name in which data set field resides.
1177	 *
1178	 * @return string
1179	 */
1180	public static function getGraphDataSetTemplate($field, $form_name) {
1181		$value = ['color' => '#{color}'] + CWidgetFieldGraphDataSet::getDefaults();
1182
1183		return self::getGraphDataSetLayout($field->getName(), $value, $form_name, '#{rowNum}', true)->toString();
1184	}
1185
1186	/**
1187	 * @param CWidgetFieldGraphDataSet $field
1188	 *
1189	 * @return CList
1190	 */
1191	public static function getGraphDataSet($field, $form_name) {
1192		$list = (new CList())
1193			->addClass(ZBX_STYLE_LIST_VERTICAL_ACCORDION)
1194			->setId('data_sets');
1195
1196		$values = $field->getValue();
1197
1198		if (!$values) {
1199			$values[] = CWidgetFieldGraphDataSet::getDefaults();
1200		}
1201
1202		foreach ($values as $i => $value) {
1203			$list->addItem(self::getGraphDataSetLayout($field->getName(), $value, $form_name, $i, $i == 0));
1204		}
1205
1206		// Add 'Add' button under accordion.
1207		$list->addItem(
1208			(new CDiv(
1209				(new CButton('data_sets_add', [(new CSpan())->addClass(ZBX_STYLE_PLUS_ICON), _('Add new data set')]))
1210					->addClass(ZBX_STYLE_BTN_ALT)
1211					->setId('dataset-add')
1212			)),
1213			ZBX_STYLE_LIST_ACCORDION_FOOT
1214		);
1215
1216		return $list;
1217	}
1218
1219	/**
1220	 * Return javascript necessary to initialize CWidgetFieldGraphDataSet field.
1221	 *
1222	 * @return string
1223	 */
1224	public static function getGraphDataSetJavascript() {
1225		$scripts = [
1226			'function changeDataSetDrawType(obj) {'.
1227				'var row_num = obj.id.replace("ds_", "").replace("_type", "");'.
1228				'switch (jQuery(":checked", jQuery(obj)).val()) {'.
1229					'case "'.SVG_GRAPH_TYPE_POINTS.'":'.
1230						'jQuery("#ds_" + row_num + "_width").rangeControl("disable");'.
1231						'jQuery("#ds_" + row_num + "_pointsize").rangeControl("enable");'.
1232						'jQuery("#ds_" + row_num + "_transparency").rangeControl("enable");'.
1233						'jQuery("#ds_" + row_num + "_fill").rangeControl("disable");'.
1234						'jQuery("#ds_" + row_num + "_missingdatafunc_0").prop("disabled", true);'.
1235						'jQuery("#ds_" + row_num + "_missingdatafunc_1").prop("disabled", true);'.
1236						'jQuery("#ds_" + row_num + "_missingdatafunc_2").prop("disabled", true);'.
1237						'break;'.
1238					'case "'.SVG_GRAPH_TYPE_BAR.'":'.
1239						'jQuery("#ds_" + row_num + "_width").rangeControl("disable");'.
1240						'jQuery("#ds_" + row_num + "_pointsize").rangeControl("disable");'.
1241						'jQuery("#ds_" + row_num + "_transparency").rangeControl("enable");'.
1242						'jQuery("#ds_" + row_num + "_fill").rangeControl("disable");'.
1243						'jQuery("#ds_" + row_num + "_missingdatafunc_0").prop("disabled", true);'.
1244						'jQuery("#ds_" + row_num + "_missingdatafunc_1").prop("disabled", true);'.
1245						'jQuery("#ds_" + row_num + "_missingdatafunc_2").prop("disabled", true);'.
1246						'break;'.
1247					'default:'.
1248						'jQuery("#ds_" + row_num + "_width").rangeControl("enable");'.
1249						'jQuery("#ds_" + row_num + "_pointsize").rangeControl("disable");'.
1250						'jQuery("#ds_" + row_num + "_transparency").rangeControl("enable");'.
1251						'jQuery("#ds_" + row_num + "_fill").rangeControl("enable");'.
1252						'jQuery("#ds_" + row_num + "_missingdatafunc_0").prop("disabled", false);'.
1253						'jQuery("#ds_" + row_num + "_missingdatafunc_1").prop("disabled", false);'.
1254						'jQuery("#ds_" + row_num + "_missingdatafunc_2").prop("disabled", false);'.
1255						'break;'.
1256				'}'.
1257			'};',
1258
1259			'function changeDataSetAggregateFunction(obj) {'.
1260				'var row_num = obj.id.replace("ds_", "").replace("_aggregate_function", "");'.
1261				'var no_aggregation = (jQuery(obj).val() == '.GRAPH_AGGREGATE_NONE.');'.
1262				'jQuery("#ds_" + row_num + "_aggregate_interval").prop("disabled", no_aggregation);'.
1263				'jQuery("#ds_" + row_num + "_aggregate_grouping_0").prop("disabled", no_aggregation);'.
1264				'jQuery("#ds_" + row_num + "_aggregate_grouping_1").prop("disabled", no_aggregation);'.
1265			'};',
1266
1267			// Initialize dynamic rows.
1268			'jQuery("#data_sets")'.
1269				'.dynamicRows({'.
1270					'template: "#dataset-row",'.
1271					'beforeRow: ".'.ZBX_STYLE_LIST_ACCORDION_FOOT.'",'.
1272					'remove: ".'.ZBX_STYLE_REMOVE_BTN.'",'.
1273					'add: "#dataset-add",'.
1274					'row: ".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'",'.
1275					'dataCallback: function(data) {'.
1276						'data.color = function(num) {'.
1277							'var palette = '.CWidgetFieldGraphDataSet::DEFAULT_COLOR_PALETTE.';'.
1278							'return palette[num % palette.length];'.
1279						'} (data.rowNum);'.
1280						'return data;'.
1281					'}'.
1282				'})'.
1283				'.bind("beforeadd.dynamicRows", function(event, options) {'.
1284					'jQuery("#data_sets").zbx_vertical_accordion("collapseAll");'.
1285				'})'.
1286				'.bind("afteradd.dynamicRows", function(event, options) {'.
1287					'var container = jQuery(".overlay-dialogue-body");'.
1288					'container.scrollTop(Math.max(container.scrollTop(),
1289						jQuery("#widget-dialogue-form")[0].scrollHeight - container.height()
1290					));'.
1291
1292					'jQuery(".input-color-picker input").colorpicker({onUpdate: function(color) {'.
1293						'var ds = jQuery(this).closest(".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'");'.
1294						'jQuery(".'.ZBX_STYLE_COLOR_PREVIEW_BOX.'", ds).css("background-color", "#"+color);'.
1295					'}, appendTo: ".overlay-dialogue-body"});'.
1296
1297					'jQuery(".multiselect", jQuery("#data_sets")).each(function() {'.
1298						'jQuery(this).multiSelect(jQuery(this).data("params"));'.
1299					'});'.
1300					'updateVariableOrder(jQuery("#data_sets"), ".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'", "ds");'.
1301					'onGraphConfigChange();'.
1302				'})'.
1303				'.bind("afterremove.dynamicRows", function(event, options) {'.
1304					'updateVariableOrder(jQuery("#data_sets"), ".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'", "ds");'.
1305					'onGraphConfigChange();'.
1306				'})'.
1307				'.bind("tableupdate.dynamicRows", function(event, options) {'.
1308					'updateVariableOrder(jQuery("#data_sets"), ".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'", "ds");'.
1309					'jQuery(".'.CRangeControl::ZBX_STYLE_CLASS.'[data-options]").rangeControl();'.
1310					'if (jQuery("#data_sets .'.ZBX_STYLE_LIST_ACCORDION_ITEM.'").length > 1) {'.
1311						'jQuery("#data_sets .drag-icon").removeClass("disabled");'.
1312						'jQuery("#data_sets").sortable("enable");'.
1313					'}'.
1314					'else {'.
1315						'jQuery("#data_sets .drag-icon").addClass("disabled");'.
1316						'jQuery("#data_sets").sortable("disable");'.
1317					'}'.
1318				'});',
1319
1320			// Initialize vertical accordion.
1321			'jQuery("#data_sets")'.
1322				'.on("focus", ".'.CMultiSelect::ZBX_STYLE_CLASS.' input.input", function() {'.
1323					'jQuery("#data_sets").zbx_vertical_accordion("expandNth",'.
1324						'jQuery(this).closest(".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'").index());'.
1325					'})'.
1326				'.on("collapse", function(event, data) {'.
1327					'jQuery("textarea, .multiselect", data.section).scrollTop(0);'.
1328					'jQuery(window).trigger("resize");'.
1329				'})'.
1330				'.on("expand", function() {'.
1331					'jQuery(window).trigger("resize");'.
1332				'})'.
1333				'.zbx_vertical_accordion({handler: ".'.ZBX_STYLE_COLOR_PREVIEW_BOX.'"});',
1334
1335			// Initialize rangeControl UI elements.
1336			'jQuery(".'.CRangeControl::ZBX_STYLE_CLASS.'", jQuery("#data_sets")).rangeControl();',
1337
1338			// Expand dataset when click in pattern fields.
1339			'jQuery("#data_sets").on("click", "'.implode(', ', [
1340				'.'.ZBX_STYLE_LIST_ACCORDION_ITEM_CLOSED.' .'.CPatternSelect::ZBX_STYLE_CLASS,
1341				'.'.ZBX_STYLE_LIST_ACCORDION_ITEM_CLOSED.' .'.ZBX_STYLE_BTN_GREY
1342			]).'", function(event) {'.
1343				'var index = jQuery(this).closest(".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'").index();'.
1344				'jQuery("#data_sets").zbx_vertical_accordion("expandNth", index);'.
1345				'jQuery(event.currentTarget).find("input.input").focus();'.
1346			'});',
1347
1348			// Initialize pattern fields.
1349			'jQuery(".multiselect", jQuery("#data_sets")).each(function() {'.
1350				'jQuery(this).multiSelect(jQuery(this).data("params"));'.
1351			'});',
1352
1353			// Initialize color-picker UI elements.
1354			'jQuery(".input-color-picker input").colorpicker({onUpdate: function(color){'.
1355				'var ds = jQuery(this).closest(".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'");'.
1356				'jQuery(".'.ZBX_STYLE_COLOR_PREVIEW_BOX.'", ds).css("background-color", "#"+color);'.
1357			'}, appendTo: ".overlay-dialogue-body"});',
1358
1359			// Initialize sortability.
1360			'if (jQuery("#data_sets .'.ZBX_STYLE_LIST_ACCORDION_ITEM.'").length < 2) {'.
1361				'jQuery("#data_sets .drag-icon").addClass("disabled");'.
1362			'}'.
1363			'jQuery("#data_sets").sortable({'.
1364				'items: ".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'",'.
1365				'containment: "parent",'.
1366				'handle: ".drag-icon",'.
1367				'tolerance: "pointer",'.
1368				'scroll: false,'.
1369				'cursor: "grabbing",'.
1370				'opacity: 0.6,'.
1371				'axis: "y",'.
1372				'disabled: function() {'.
1373					'return jQuery("#data_sets .'.ZBX_STYLE_LIST_ACCORDION_ITEM.'").length < 2;'.
1374				'}(),'.
1375				'start: function() {'. // Workaround to fix wrong scrolling at initial sort.
1376					'jQuery(this).sortable("refreshPositions");'.
1377				'},'.
1378				'stop: onGraphConfigChange,'.
1379				'update: function() {'.
1380					'updateVariableOrder(jQuery("#data_sets"), ".'.ZBX_STYLE_LIST_ACCORDION_ITEM.'", "ds");'.
1381				'}'.
1382			'});'.
1383			'$(".overlay-dialogue-body").on("change", "z-select[id$=\"aggregate_function\"]", (e) => {'.
1384				'changeDataSetAggregateFunction(e.target);'.
1385			'});'
1386		];
1387
1388		return implode ('', $scripts);
1389	}
1390
1391	/**
1392	 * @param CWidgetField $field
1393	 *
1394	 * @return int
1395	 */
1396	public static function isAriaRequired($field) {
1397		return ($field->getFlags() & CWidgetField::FLAG_LABEL_ASTERISK);
1398	}
1399}
1400