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 * This controller is used by gtlc.js to update time selector date and time interval in user's profile.
24 */
25class CControllerTimeSelectorUpdate extends CController {
26
27	/**
28	 * @var CRangeTimeParser
29	 */
30	private $range_time_parser;
31
32	private $data = [];
33
34	public function init() {
35		$this->disableSIDvalidation();
36
37		$this->range_time_parser = new CRangeTimeParser();
38	}
39
40	protected function checkInput() {
41		$profiles = ['web.dashbrd.filter', 'web.screens.filter', 'web.graphs.filter', 'web.httpdetails.filter',
42			'web.problem.filter', 'web.auditlogs.filter', 'web.slides.filter', 'web.auditacts.filter',
43			'web.item.graph.filter', 'web.toptriggers.filter', 'web.avail_report.filter'
44		];
45
46		$fields = [
47			'method' => 'required|in increment,zoomout,decrement,rangechange,rangeoffset',
48			'idx' => 'required|in '.implode(',', $profiles),
49			'idx2' => 'required|id',
50			'from' => 'required|string',
51			'to' => 'required|string',
52			'from_offset' => 'int32|ge 0',
53			'to_offset' => 'int32|ge 0'
54		];
55
56		$ret = $this->validateInput($fields);
57
58		if (!$ret) {
59			/*
60			 * This block executes if, for example, a missing profile is given. Since this is an AJAX request, it should
61			 * throw a JS alert() with current message in timeSelectorEventHandler() in gtlc.js.
62			 */
63
64			global $ZBX_MESSAGES;
65
66			$this->setResponse(new CControllerResponseData([
67				'main_block' => CJs::encodeJson(['error' => $ZBX_MESSAGES[0]['message']])
68			]));
69
70			return $ret;
71		}
72
73		$ret = $this->validateInputDateRange();
74
75		if ($this->getInput('method') === 'rangeoffset' && (!$this->hasInput('from_offset')
76				|| !$this->hasInput('to_offset'))) {
77			$ret = false;
78		}
79
80		if (!$ret) {
81			$this->setResponse(new CControllerResponseData(['main_block' => CJs::encodeJson($this->data)]));
82		}
83
84		return $ret;
85	}
86
87	protected function checkPermissions() {
88		return ($this->getUserType() >= USER_TYPE_ZABBIX_USER);
89	}
90
91	protected function doAction() {
92		$method = $this->getInput('method');
93		$date = new DateTime();
94		$value = [];
95		$date_type = [];
96		$ts = [];
97		$ts['now'] = time();
98
99		foreach (['from', 'to'] as $field) {
100			$value[$field] = $this->getInput($field);
101			$this->range_time_parser->parse($value[$field]);
102			$date_type[$field] = $this->range_time_parser->getTimeType();
103			$ts[$field] = $this->range_time_parser->getDateTime($field === 'from')->getTimestamp();
104		}
105
106		$period = $ts['to'] - $ts['from'] + 1;
107
108		switch ($method) {
109			case 'decrement':
110				$offset = $period;
111
112				if ($ts['from'] - $offset < 0) {
113					$offset = $ts['from'];
114				}
115
116				$ts['from'] -= $offset;
117				$ts['to'] -= $offset;
118
119				$value['from'] = $date->setTimestamp($ts['from'])->format(ZBX_FULL_DATE_TIME);
120				$value['to'] = $date->setTimestamp($ts['to'])->format(ZBX_FULL_DATE_TIME);
121				break;
122
123			case 'increment':
124				$offset = $period;
125
126				if ($ts['to'] + $offset > $ts['now']) {
127					$offset = $ts['now'] - $ts['to'];
128				}
129
130				$ts['from'] += $offset;
131				$ts['to'] += $offset;
132
133				$value['from'] = $date->setTimestamp($ts['from'])->format(ZBX_FULL_DATE_TIME);
134				$value['to'] = $date->setTimestamp($ts['to'])->format(ZBX_FULL_DATE_TIME);
135				break;
136
137			case 'zoomout':
138				$right_offset = (int) ($period / 2);
139				if ($ts['to'] + $right_offset > $ts['now']) {
140					$right_offset = $ts['now'] - $ts['to'];
141				}
142				$left_offset = $period - $right_offset;
143				if ($ts['from'] - $left_offset < 0) {
144					$left_offset = $ts['from'];
145				}
146
147				$ts['from'] -= $left_offset;
148				$ts['to'] += $right_offset;
149
150				if ($ts['to'] - $ts['from'] + 1 > ZBX_MAX_PERIOD) {
151					$ts['from'] = $ts['to'] - ZBX_MAX_PERIOD + 1;
152				}
153
154				$value['from'] = $date->setTimestamp($ts['from'])->format(ZBX_FULL_DATE_TIME);
155				$value['to'] = $date->setTimestamp($ts['to'])->format(ZBX_FULL_DATE_TIME);
156				break;
157
158			case 'rangeoffset':
159				$from_offset = $this->getInput('from_offset');
160				$to_offset = $this->getInput('to_offset');
161
162				if ($from_offset > 0) {
163					$ts['from'] += $from_offset;
164					$value['from'] = $date->setTimestamp($ts['from'])->format(ZBX_FULL_DATE_TIME);
165				}
166
167				if ($to_offset > 0) {
168					$ts['to'] -= $to_offset;
169					$value['to'] = $date->setTimestamp($ts['to'])->format(ZBX_FULL_DATE_TIME);
170				}
171				break;
172
173			case 'rangechange':
174				// Format only absolute date according ZBX_FULL_DATE_TIME string.
175				foreach (['from', 'to'] as $field) {
176					if ($date_type[$field] === CRangeTimeParser::ZBX_TIME_ABSOLUTE) {
177						$value[$field] = $date->setTimestamp($ts[$field])->format(ZBX_FULL_DATE_TIME);
178					}
179				}
180				break;
181		}
182
183		updateTimeSelectorPeriod([
184			'profileIdx' => $this->getInput('idx'),
185			'profileIdx2' => $this->getInput('idx2'),
186			'from' => $value['from'],
187			'to' => $value['to']
188		]);
189
190		$this->setResponse(new CControllerResponseData(['main_block' => CJs::encodeJson([
191			'label' => relativeDateToText($value['from'], $value['to']),
192			'from' => $value['from'],
193			'from_ts' => $ts['from'],
194			'from_date' => $date->setTimestamp($ts['from'])->format(ZBX_FULL_DATE_TIME),
195			'to' => $value['to'],
196			'to_ts' => $ts['to'],
197			'to_date' => $date->setTimestamp($ts['to'])->format(ZBX_FULL_DATE_TIME),
198			'can_zoomout' => ($ts['to'] - $ts['from'] + 1 < ZBX_MAX_PERIOD),
199			'can_decrement' => ($ts['from'] > 0),
200			'can_increment' => ($ts['to'] < $ts['now'] - ZBX_MIN_PERIOD)
201		])]));
202	}
203
204	/**
205	 * Validate input 'from' and 'to' arguments. Returns true on success.
206	 *
207	 * @return bool
208	 */
209	protected function validateInputDateRange() {
210		$this->data['error'] = [];
211		$ts = [];
212
213		foreach (['from', 'to'] as $field) {
214			$value = $this->getInput($field);
215
216			if ($this->range_time_parser->parse($value) !== CParser::PARSE_SUCCESS) {
217				$this->data['error'][$field] = _('Invalid date.');
218			}
219			else {
220				$ts[$field] = $this->range_time_parser->getDateTime($field === 'from')->getTimestamp();
221			}
222		}
223
224		if ($this->data['error']) {
225			return false;
226		}
227
228		if ($this->getInput('method') === 'rangeoffset') {
229			$ts['from'] += $this->getInput('from_offset');
230			$ts['to'] -= $this->getInput('to_offset');
231		}
232
233		$period = $ts['to'] - $ts['from'] + 1;
234
235		if ($period < ZBX_MIN_PERIOD) {
236			$this->data['error']['from'] = _n('Minimum time period to display is %1$s minute.',
237				'Minimum time period to display is %1$s minutes.', (int) (ZBX_MIN_PERIOD / SEC_PER_MIN)
238			);
239		}
240		elseif ($period > ZBX_MAX_PERIOD) {
241			$this->data['error']['from'] = _n('Maximum time period to display is %1$s day.',
242				'Maximum time period to display is %1$s days.', (int) (ZBX_MAX_PERIOD / SEC_PER_DAY)
243			);
244		}
245
246		return !$this->data['error'];
247	}
248}
249