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 CWidgetFormSvgGraph extends CWidgetForm {
23
24	public function __construct($data, $templateid) {
25		parent::__construct($data, $templateid, WIDGET_SVG_GRAPH);
26
27		$this->data = self::convertDottedKeys($this->data);
28
29		/**
30		 * Data set tab.
31		 *
32		 * Contains single CWidgetFieldGraphDataSet field for data sets definition and configuration.
33		 */
34		$field_ds = (new CWidgetFieldGraphDataSet('ds', _('Data set')))->setFlags(CWidgetField::FLAG_NOT_EMPTY);
35
36		if (array_key_exists('ds', $this->data)) {
37			$field_ds->setValue($this->data['ds']);
38		}
39
40		$this->fields[$field_ds->getName()] = $field_ds;
41
42		/**
43		 * Display options tab.
44		 *
45		 * Used to select either data are loaded from History or Trends or turning automatic mode on.
46		 */
47		$field_data_source = (new CWidgetFieldRadioButtonList('source', _('History data selection'), [
48			SVG_GRAPH_DATA_SOURCE_AUTO => _x('Auto', 'history source selection method'),
49			SVG_GRAPH_DATA_SOURCE_HISTORY => _('History'),
50			SVG_GRAPH_DATA_SOURCE_TRENDS => _('Trends')
51		]))
52			->setDefault(SVG_GRAPH_DATA_SOURCE_AUTO)
53			->setModern(true);
54
55		if (array_key_exists('source', $this->data)) {
56			$field_data_source->setValue($this->data['source']);
57		}
58
59		$this->fields[$field_data_source->getName()] = $field_data_source;
60
61		/**
62		 * Time period tab.
63		 *
64		 * Contains fields for specifying widget time options.
65		 */
66		// Checkbox to specify either relative dashboard time or widget's own time.
67		$field_graph_time = (new CWidgetFieldCheckBox('graph_time', _('Set custom time period')))
68			->setAction('jQuery("#time_from, #time_to, #time_from_calendar, #time_to_calendar")'.
69				'.prop("disabled", !jQuery(this).is(":checked"));'
70			);
71
72		if (array_key_exists('graph_time', $this->data)) {
73			$field_graph_time->setValue($this->data['graph_time']);
74		}
75
76		$this->fields[$field_graph_time->getName()] = $field_graph_time;
77
78		// Date from.
79		$field_time_from = (new CWidgetFieldDatePicker('time_from', _('From')))->setDefault('now-1h');
80
81		if ($field_graph_time->getValue() != SVG_GRAPH_CUSTOM_TIME) {
82			$field_time_from->setFlags(CWidgetField::FLAG_DISABLED);
83		}
84		elseif (array_key_exists('time_from', $this->data)) {
85			$field_time_from->setValue($this->data['time_from']);
86		}
87
88		$this->fields[$field_time_from->getName()] = $field_time_from;
89
90		// Time to.
91		$field_time_to = (new CWidgetFieldDatePicker('time_to', _('To')))->setDefault('now');
92
93		if ($field_graph_time->getValue() != SVG_GRAPH_CUSTOM_TIME) {
94			$field_time_to->setFlags(CWidgetField::FLAG_DISABLED);
95		}
96		elseif (array_key_exists('time_to', $this->data)) {
97			$field_time_to->setValue($this->data['time_to']);
98		}
99
100		$this->fields[$field_time_to->getName()] = $field_time_to;
101
102		/**
103		 * Axes tab.
104		 *
105		 * Contains fields to specify options for graph axes.
106		 */
107		// Show left Y axis.
108		$field_lefty = (new CWidgetFieldCheckBox('lefty', _('Left Y'), _('Show')))
109			->setDefault(SVG_GRAPH_AXIS_SHOW)
110			->setAction('onLeftYChange()');
111
112		if (array_key_exists('lefty', $this->data)) {
113			$field_lefty->setValue($this->data['lefty']);
114		}
115
116		$this->fields[$field_lefty->getName()] = $field_lefty;
117
118		// Min value on left Y axis.
119		$field_lefty_min = (new CWidgetFieldNumericBox('lefty_min', _('Min')))
120			->setPlaceholder(_('calculated'))
121			->setFullName(_('Left Y').'/'._('Min'))
122			->setWidth(ZBX_TEXTAREA_SMALL_WIDTH);
123
124		if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW) {
125			$field_lefty_min->setFlags(CWidgetField::FLAG_DISABLED);
126		}
127		elseif (array_key_exists('lefty_min', $this->data)) {
128			$field_lefty_min->setValue($this->data['lefty_min']);
129		}
130
131		$this->fields[$field_lefty_min->getName()] = $field_lefty_min;
132
133		// Max value on left Y axis.
134		$field_lefty_max = (new CWidgetFieldNumericBox('lefty_max', _('Max')))
135			->setPlaceholder(_('calculated'))
136			->setFullName(_('Left Y').'/'._('Max'))
137			->setWidth(ZBX_TEXTAREA_SMALL_WIDTH);
138
139		if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW) {
140			$field_lefty_max->setFlags(CWidgetField::FLAG_DISABLED);
141		}
142		elseif (array_key_exists('lefty_max', $this->data)) {
143			$field_lefty_max->setValue($this->data['lefty_max']);
144		}
145
146		$this->fields[$field_lefty_max->getName()] = $field_lefty_max;
147
148		// Specify the type of units on left Y axis.
149		$field_lefty_units = (new CWidgetFieldSelect('lefty_units', _('Units'), [
150			SVG_GRAPH_AXIS_UNITS_AUTO => _x('Auto', 'history source selection method'),
151			SVG_GRAPH_AXIS_UNITS_STATIC => _x('Static', 'history source selection method')
152		]))
153			->setDefault(SVG_GRAPH_AXIS_UNITS_AUTO);
154
155		if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW) {
156			$field_lefty_units->setFlags(CWidgetField::FLAG_DISABLED);
157		}
158		elseif (array_key_exists('lefty_units', $this->data)) {
159			$field_lefty_units->setValue($this->data['lefty_units']);
160		}
161
162		$this->fields[$field_lefty_units->getName()] = $field_lefty_units;
163
164		// Static units on left Y axis.
165		$field_lefty_static_units = (new CWidgetFieldTextBox('lefty_static_units', null))
166			->setPlaceholder(_('value'))
167			->setWidth(ZBX_TEXTAREA_TINY_WIDTH);
168
169		if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW
170				|| $field_lefty_units->getValue() != SVG_GRAPH_AXIS_UNITS_STATIC) {
171			$field_lefty_static_units->setFlags(CWidgetField::FLAG_DISABLED);
172		}
173		elseif (array_key_exists('lefty_static_units', $this->data)) {
174			$field_lefty_static_units->setValue($this->data['lefty_static_units']);
175		}
176
177		$this->fields[$field_lefty_static_units->getName()] = $field_lefty_static_units;
178
179		// Show right Y axis.
180		$field_righty = (new CWidgetFieldCheckBox('righty', _('Right Y'), _('Show')))
181			->setDefault(SVG_GRAPH_AXIS_SHOW)
182			->setAction('onRightYChange()');
183
184		if (array_key_exists('righty', $this->data)) {
185			$field_righty->setValue($this->data['righty']);
186		}
187
188		$this->fields[$field_righty->getName()] = $field_righty;
189
190		// Min value on right Y axis.
191		$field_righty_min = (new CWidgetFieldNumericBox('righty_min', _('Min')))
192			->setPlaceholder(_('calculated'))
193			->setFullName(_('Right Y').'/'._('Min'))
194			->setWidth(ZBX_TEXTAREA_SMALL_WIDTH);
195
196		if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW) {
197			$field_righty_min->setFlags(CWidgetField::FLAG_DISABLED);
198		}
199		elseif (array_key_exists('righty_min', $this->data)) {
200			$field_righty_min->setValue($this->data['righty_min']);
201		}
202
203		$this->fields[$field_righty_min->getName()] = $field_righty_min;
204
205		// Max value on right Y axis.
206		$field_righty_max = (new CWidgetFieldNumericBox('righty_max', _('Max')))
207			->setPlaceholder(_('calculated'))
208			->setFullName(_('Right Y').'/'._('Max'))
209			->setWidth(ZBX_TEXTAREA_SMALL_WIDTH);
210
211		if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW) {
212			$field_righty_max->setFlags(CWidgetField::FLAG_DISABLED);
213		}
214		elseif (array_key_exists('righty_max', $this->data)) {
215			$field_righty_max->setValue($this->data['righty_max']);
216		}
217
218		$this->fields[$field_righty_max->getName()] = $field_righty_max;
219
220		// Specify the type of units on right Y axis.
221		$field_righty_units = (new CWidgetFieldSelect('righty_units', _('Units'), [
222			SVG_GRAPH_AXIS_UNITS_AUTO => _x('Auto', 'history source selection method'),
223			SVG_GRAPH_AXIS_UNITS_STATIC => _x('Static', 'history source selection method')
224		]))
225			->setDefault(SVG_GRAPH_AXIS_UNITS_AUTO);
226
227		if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW) {
228			$field_righty_units->setFlags(CWidgetField::FLAG_DISABLED);
229		}
230		elseif (array_key_exists('righty_units', $this->data)) {
231			$field_righty_units->setValue($this->data['righty_units']);
232		}
233
234		$this->fields[$field_righty_units->getName()] = $field_righty_units;
235
236		// Static units on right Y axis.
237		$field_righty_static_units = (new CWidgetFieldTextBox('righty_static_units', null))
238			->setPlaceholder(_('value'))
239			->setWidth(ZBX_TEXTAREA_TINY_WIDTH);
240
241		if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW
242				|| $field_righty_units->getValue() != SVG_GRAPH_AXIS_UNITS_STATIC) {
243			$field_righty_static_units->setFlags(CWidgetField::FLAG_DISABLED);
244		}
245		elseif (array_key_exists('righty_static_units', $this->data)) {
246			$field_righty_static_units->setValue($this->data['righty_static_units']);
247		}
248
249		$this->fields[$field_righty_static_units->getName()] = $field_righty_static_units;
250
251		// Show X axis.
252		$field_axisx = (new CWidgetFieldCheckBox('axisx', _('X-Axis'), _('Show')))->setDefault(SVG_GRAPH_AXIS_SHOW);
253
254		if (array_key_exists('axisx', $this->data)) {
255			$field_axisx->setValue($this->data['axisx']);
256		}
257
258		$this->fields[$field_axisx->getName()] = $field_axisx;
259
260		/**
261		 * Legend tab.
262		 *
263		 * Contains check-box field to show/hide legend and field to specify number of lines in which legend is shown.
264		 */
265		// Show legend.
266		$field_legend = (new CWidgetFieldCheckBox('legend', _('Show legend')))
267			->setAction('jQuery("[name=legend_lines]").rangeControl('.
268				'jQuery(this).is(":checked") ? "enable" : "disable"'.
269			');')
270			->setDefault(SVG_GRAPH_LEGEND_TYPE_SHORT);
271
272		if (array_key_exists('legend', $this->data)) {
273			$field_legend->setValue($this->data['legend']);
274		}
275
276		$this->fields[$field_legend->getName()] = $field_legend;
277
278		// Number of lines.
279		$field_legend_lines = (new CWidgetFieldRangeControl('legend_lines', _('Number of rows'),
280			SVG_GRAPH_LEGEND_LINES_MIN, SVG_GRAPH_LEGEND_LINES_MAX
281		))
282			->setDefault(SVG_GRAPH_LEGEND_LINES_MIN);
283
284		if ($field_legend->getValue() == SVG_GRAPH_LEGEND_TYPE_NONE) {
285			$field_legend_lines->setFlags(CWidgetField::FLAG_DISABLED);
286		}
287		if (array_key_exists('legend_lines', $this->data)) {
288			$field_legend_lines->setValue($this->data['legend_lines']);
289		}
290
291		$this->fields[$field_legend_lines->getName()] = $field_legend_lines;
292
293		/**
294		 * Problems tab.
295		 *
296		 * Contains fields to configure highlighted problem areas in graph.
297		 */
298		// Checkbox: Selected items only.
299		$field_show_problems = (new CWidgetFieldCheckBox('show_problems', _('Show problems')))
300			->setAction(
301				'var on = jQuery(this).is(":checked"),'.
302					'widget = jQuery(this).closest(".ui-widget");'.
303				'jQuery("#graph_item_problems, #problem_name, #problemhosts_select")'.
304					'.prop("disabled", !on);'.
305				'jQuery("#problemhosts_").multiSelect(on ? "enable" : "disable");'.
306				'jQuery("[name^=\"severities[\"]", widget).prop("disabled", !on);'.
307				'jQuery("[name=\"evaltype\"]", widget).prop("disabled", !on);'.
308				'jQuery("input, button, z-select", jQuery("#tags_table_tags", widget)).prop("disabled", !on);'
309			);
310
311		if (array_key_exists('show_problems', $this->data)) {
312			$field_show_problems->setValue($this->data['show_problems']);
313		}
314
315		$this->fields[$field_show_problems->getName()] = $field_show_problems;
316
317		// Checkbox: Selected items only.
318		$field_problems = (new CWidgetFieldCheckBox('graph_item_problems', _('Selected items only')))
319			->setDefault(SVG_GRAPH_SELECTED_ITEM_PROBLEMS);
320
321		if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) {
322			$field_problems->setFlags(CWidgetField::FLAG_DISABLED);
323		}
324		elseif (array_key_exists('graph_item_problems', $this->data)) {
325			$field_problems->setValue($this->data['graph_item_problems']);
326		}
327
328		$this->fields[$field_problems->getName()] = $field_problems;
329
330		// Problem hosts.
331		$field_problemhosts = (new CWidgetFieldHostPatternSelect('problemhosts', _('Problem hosts')))
332			->setPlaceholder(_('host pattern'));
333
334		if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) {
335			$field_problemhosts->setFlags(CWidgetField::FLAG_DISABLED);
336		}
337		elseif (array_key_exists('problemhosts', $this->data)) {
338			$field_problemhosts->setValue($this->data['problemhosts']);
339		}
340
341		$this->fields[$field_problemhosts->getName()] = $field_problemhosts;
342
343		// Severity checkboxes list.
344		$field_severities = new CWidgetFieldSeverities('severities', _('Severity'));
345
346		if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) {
347			$field_severities->setFlags(CWidgetField::FLAG_DISABLED);
348		}
349		elseif (array_key_exists('severities', $this->data)) {
350			$field_severities->setValue($this->data['severities']);
351		}
352
353		$this->fields[$field_severities->getName()] = $field_severities;
354
355		// Problem name input-text field.
356		$field_problem_name = (new CWidgetFieldTextBox('problem_name', _('Problem')))
357			->setPlaceholder(_('problem pattern'));
358
359		if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) {
360			$field_problem_name->setFlags(CWidgetField::FLAG_DISABLED);
361		}
362		elseif (array_key_exists('problem_name', $this->data)) {
363			$field_problem_name->setValue($this->data['problem_name']);
364		}
365
366		$this->fields[$field_problem_name->getName()] = $field_problem_name;
367
368		// Problem tag evaltype (And/Or).
369		$field_evaltype = (new CWidgetFieldRadioButtonList('evaltype', _('Tags'), [
370			TAG_EVAL_TYPE_AND_OR => _('And/Or'),
371			TAG_EVAL_TYPE_OR => _('Or')
372		]))
373			->setDefault(TAG_EVAL_TYPE_AND_OR)
374			->setModern(true);
375
376		if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) {
377			$field_evaltype->setFlags(CWidgetField::FLAG_DISABLED);
378		}
379		elseif (array_key_exists('evaltype', $this->data)) {
380			$field_evaltype->setValue($this->data['evaltype']);
381		}
382
383		$this->fields[$field_evaltype->getName()] = $field_evaltype;
384
385		// Problem tags field.
386		$field_tags = new CWidgetFieldTags('tags', '');
387
388		if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) {
389			$field_tags->setFlags(CWidgetField::FLAG_DISABLED);
390		}
391		elseif (array_key_exists('tags', $this->data)) {
392			$field_tags->setValue($this->data['tags']);
393		}
394
395		$this->fields[$field_tags->getName()] = $field_tags;
396
397		/**
398		 * Overrides tab.
399		 *
400		 * Contains single field for override configuration.
401		 */
402		$field_or = (new CWidgetFieldGraphOverride('or', _('Overrides')))->setFlags(CWidgetField::FLAG_NOT_EMPTY);
403
404		if (array_key_exists('or', $this->data)) {
405			$field_or->setValue($this->data['or']);
406		}
407
408		$this->fields[$field_or->getName()] = $field_or;
409	}
410
411	/**
412	 * Validate "from" and "to" parameters for allowed period.
413	 *
414	 * @param string $from
415	 * @param string $to
416	 *
417	 * @return array
418	 */
419	private static function validateTimeSelectorPeriod($from, $to) {
420		$errors = [];
421		$ts = [];
422		$ts['now'] = time();
423		$range_time_parser = new CRangeTimeParser();
424
425		foreach (['from' => $from, 'to' => $to] as $field => $value) {
426			$range_time_parser->parse($value);
427			$ts[$field] = $range_time_parser
428				->getDateTime($field === 'from')
429				->getTimestamp();
430		}
431
432		$period = $ts['to'] - $ts['from'] + 1;
433		$range_time_parser->parse('now-'.CSettingsHelper::get(CSettingsHelper::MAX_PERIOD));
434		$max_period = 1 + $ts['now'] - $range_time_parser
435			->getDateTime(true)
436			->getTimestamp();
437
438		if ($period < ZBX_MIN_PERIOD) {
439			$errors[] = _n('Minimum time period to display is %1$s minute.',
440				'Minimum time period to display is %1$s minutes.', (int) (ZBX_MIN_PERIOD / SEC_PER_MIN)
441			);
442		}
443		elseif ($period > $max_period) {
444			$errors[] = _n('Maximum time period to display is %1$s day.',
445				'Maximum time period to display is %1$s days.', (int) round($max_period / SEC_PER_DAY)
446			);
447		}
448
449		return $errors;
450	}
451
452	/**
453	 * Validate form fields.
454	 *
455	 * @param bool $strict  Enables more strict validation of the form fields.
456	 *                      Must be enabled for validation of input parameters in the widget configuration form.
457	 *
458	 * @return bool
459	 */
460	public function validate($strict = false) {
461		$errors = parent::validate($strict);
462
463		// Test graph custom time period.
464		if ($this->fields['graph_time']->getValue() == SVG_GRAPH_CUSTOM_TIME) {
465			$errors = array_merge($errors, self::validateTimeSelectorPeriod($this->fields['time_from']->getValue(),
466				$this->fields['time_to']->getValue()
467			));
468		}
469
470		$number_parser = new CNumberParser(['with_suffix' => true]);
471
472		// Validate Min/Max values in Axes tab.
473		if ($this->fields['lefty']->getValue() == SVG_GRAPH_AXIS_SHOW) {
474			$lefty_min = $number_parser->parse($this->fields['lefty_min']->getValue()) == CParser::PARSE_SUCCESS
475				? $number_parser->calcValue()
476				: '';
477
478			$lefty_max = $number_parser->parse($this->fields['lefty_max']->getValue()) == CParser::PARSE_SUCCESS
479				? $number_parser->calcValue()
480				: '';
481
482			if ($lefty_min !== '' && $lefty_max !== '' && $lefty_min >= $lefty_max) {
483				$errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Left Y').'/'._('Max'),
484					_('Y axis MAX value must be greater than Y axis MIN value')
485				);
486			}
487		}
488
489		if ($this->fields['righty']->getValue() == SVG_GRAPH_AXIS_SHOW) {
490			$righty_min = $number_parser->parse($this->fields['righty_min']->getValue()) == CParser::PARSE_SUCCESS
491				? $number_parser->calcValue()
492				: '';
493
494			$righty_max = $number_parser->parse($this->fields['righty_max']->getValue()) == CParser::PARSE_SUCCESS
495				? $number_parser->calcValue()
496				: '';
497
498			if ($righty_min !== '' && $righty_max !== '' && $righty_min >= $righty_max) {
499				$errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Right Y').'/'._('Max'),
500					_('Y axis MAX value must be greater than Y axis MIN value')
501				);
502			}
503		}
504
505		return $errors;
506	}
507
508	/**
509	 * Check if widget configuration is set to use overridden time.
510	 *
511	 * @param array $fields                Widget configuration fields.
512	 * @param int   $fields['graph_time']  (optional)
513	 *
514	 * @return bool
515	 */
516	public static function hasOverrideTime($fields) {
517		return (array_key_exists('graph_time', $fields) && $fields['graph_time'] == SVG_GRAPH_CUSTOM_TIME);
518	}
519}
520