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 * Class containing methods for operations with maintenances.
24 *
25 * * @package API
26 */
27class CMaintenance extends CApiService {
28
29	protected $tableName = 'maintenances';
30	protected $tableAlias = 'm';
31	protected $sortColumns = ['maintenanceid', 'name', 'maintenance_type', 'active_till', 'active_since'];
32
33	/**
34	 * Get maintenances data.
35	 *
36	 * @param array  $options
37	 * @param array  $options['itemids']
38	 * @param array  $options['hostids']
39	 * @param array  $options['groupids']
40	 * @param array  $options['triggerids']
41	 * @param array  $options['maintenanceids']
42	 * @param bool   $options['status']
43	 * @param bool   $options['editable']
44	 * @param bool   $options['count']
45	 * @param string $options['pattern']
46	 * @param int    $options['limit']
47	 * @param string $options['order']
48	 *
49	 * @return array
50	 */
51	public function get(array $options = []) {
52		$result = [];
53
54		$sqlParts = [
55			'select'	=> ['maintenance' => 'm.maintenanceid'],
56			'from'		=> ['maintenances' => 'maintenances m'],
57			'where'		=> [],
58			'group'		=> [],
59			'order'		=> [],
60			'limit'		=> null
61		];
62
63		$defOptions = [
64			'groupids'					=> null,
65			'hostids'					=> null,
66			'maintenanceids'			=> null,
67			'editable'					=> false,
68			'nopermissions'				=> null,
69			// filter
70			'filter'					=> null,
71			'search'					=> null,
72			'searchByAny'				=> null,
73			'startSearch'				=> null,
74			'excludeSearch'				=> null,
75			'filter'					=> null,
76			'searchWildcardsEnabled'	=> null,
77			// output
78			'output'					=> API_OUTPUT_EXTEND,
79			'selectGroups'				=> null,
80			'selectHosts'				=> null,
81			'selectTimeperiods'			=> null,
82			'countOutput'				=> null,
83			'groupCount'				=> null,
84			'preservekeys'				=> null,
85			'sortfield'					=> '',
86			'sortorder'					=> '',
87			'limit'						=> null
88		];
89		$options = zbx_array_merge($defOptions, $options);
90
91		// editable + PERMISSION CHECK
92		$maintenanceids = [];
93		if (self::$userData['type'] == USER_TYPE_SUPER_ADMIN || $options['nopermissions']) {
94			if (!is_null($options['groupids']) || !is_null($options['hostids'])) {
95				if (!is_null($options['groupids'])) {
96					zbx_value2array($options['groupids']);
97					$res = DBselect(
98						'SELECT mmg.maintenanceid'.
99						' FROM maintenances_groups mmg'.
100						' WHERE '.dbConditionInt('mmg.groupid', $options['groupids'])
101					);
102					while ($maintenance = DBfetch($res)) {
103						$maintenanceids[] = $maintenance['maintenanceid'];
104					}
105				}
106
107				$sql = 'SELECT mmh.maintenanceid'.
108						' FROM maintenances_hosts mmh,hosts_groups hg'.
109						' WHERE hg.hostid=mmh.hostid';
110
111				if (!is_null($options['groupids'])) {
112					zbx_value2array($options['groupids']);
113					$sql .= ' AND '.dbConditionInt('hg.groupid', $options['groupids']);
114				}
115
116				if (!is_null($options['hostids'])) {
117					zbx_value2array($options['hostids']);
118					$sql .= ' AND '.dbConditionInt('hg.hostid', $options['hostids']);
119				}
120				$res = DBselect($sql);
121				while ($maintenance = DBfetch($res)) {
122					$maintenanceids[] = $maintenance['maintenanceid'];
123				}
124				$sqlParts['where'][] = dbConditionInt('m.maintenanceid', $maintenanceids);
125			}
126		}
127		else {
128			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
129			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
130
131			$sql = 'SELECT m.maintenanceid'.
132					' FROM maintenances m'.
133					' WHERE NOT EXISTS ('.
134						'SELECT NULL'.
135						' FROM maintenances_hosts mh,hosts_groups hg'.
136							' LEFT JOIN rights r'.
137								' ON r.id=hg.groupid'.
138									' AND '.dbConditionInt('r.groupid', $userGroups).
139						' WHERE m.maintenanceid=mh.maintenanceid'.
140							' AND mh.hostid=hg.hostid'.
141						' GROUP by mh.hostid'.
142						' HAVING MIN(r.permission) IS NULL'.
143							' OR MIN(r.permission)='.PERM_DENY.
144							' OR MAX(r.permission)<'.zbx_dbstr($permission).
145						')'.
146					' AND NOT EXISTS ('.
147						'SELECT NULL'.
148						' FROM maintenances_groups mg'.
149							' LEFT JOIN rights r'.
150								' ON r.id=mg.groupid'.
151									' AND '.dbConditionInt('r.groupid', $userGroups).
152						' WHERE m.maintenanceid=mg.maintenanceid'.
153						' GROUP by mg.groupid'.
154						' HAVING MIN(r.permission) IS NULL'.
155							' OR MIN(r.permission)='.PERM_DENY.
156							' OR MAX(r.permission)<'.zbx_dbstr($permission).
157						')';
158
159			if (!is_null($options['groupids'])) {
160				zbx_value2array($options['groupids']);
161				$sql .= ' AND ('.
162						'EXISTS ('.
163							'SELECT NULL'.
164								' FROM maintenances_groups mg'.
165								' WHERE m.maintenanceid=mg.maintenanceid'.
166								' AND '.dbConditionInt('mg.groupid', $options['groupids']).
167							')'.
168						' OR EXISTS ('.
169							'SELECT NULL'.
170								' FROM maintenances_hosts mh,hosts_groups hg'.
171								' WHERE m.maintenanceid=mh.maintenanceid'.
172									' AND mh.hostid=hg.hostid'.
173									' AND '.dbConditionInt('hg.groupid', $options['groupids']).
174							')'.
175						')';
176			}
177
178			if (!is_null($options['hostids'])) {
179				zbx_value2array($options['hostids']);
180				$sql .= ' AND EXISTS ('.
181						'SELECT NULL'.
182							' FROM maintenances_hosts mh'.
183							' WHERE m.maintenanceid=mh.maintenanceid'.
184								' AND '.dbConditionInt('mh.hostid', $options['hostids']).
185						')';
186			}
187
188			if (!is_null($options['maintenanceids'])) {
189				zbx_value2array($options['maintenanceids']);
190				$sql .= ' AND '.dbConditionInt('m.maintenanceid', $options['maintenanceids']);
191			}
192
193			$res = DBselect($sql);
194			while ($maintenance = DBfetch($res)) {
195				$maintenanceids[] = $maintenance['maintenanceid'];
196			}
197			$sqlParts['where'][] = dbConditionInt('m.maintenanceid', $maintenanceids);
198		}
199
200		// maintenanceids
201		if (!is_null($options['maintenanceids'])) {
202			zbx_value2array($options['maintenanceids']);
203
204			$sqlParts['where'][] = dbConditionInt('m.maintenanceid', $options['maintenanceids']);
205		}
206
207		// filter
208		if (is_array($options['filter'])) {
209			$this->dbFilter('maintenances m', $options, $sqlParts);
210		}
211
212		// search
213		if (is_array($options['search'])) {
214			zbx_db_search('maintenances m', $options, $sqlParts);
215		}
216
217		// limit
218		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
219			$sqlParts['limit'] = $options['limit'];
220		}
221
222		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
223		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
224		$res = DBselect($this->createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
225		while ($maintenance = DBfetch($res)) {
226			if (!is_null($options['countOutput'])) {
227				if (!is_null($options['groupCount'])) {
228					$result[] = $maintenance;
229				}
230				else {
231					$result = $maintenance['rowscount'];
232				}
233			}
234			else {
235				$result[$maintenance['maintenanceid']] = $maintenance;
236			}
237		}
238
239		if (!is_null($options['countOutput'])) {
240			return $result;
241		}
242
243		if ($result) {
244			$result = $this->addRelatedObjects($options, $result);
245		}
246
247		if (is_null($options['preservekeys'])) {
248			$result = zbx_cleanHashes($result);
249		}
250		return $result;
251	}
252
253	/**
254	 * Add maintenances.
255	 *
256	 * @param array $maintenances
257	 *
258	 * @return boolean
259	 */
260	public function create(array $maintenances) {
261		$maintenances = zbx_toArray($maintenances);
262		if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) {
263			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
264		}
265
266		$hostids = [];
267		$groupids = [];
268		foreach ($maintenances as $maintenance) {
269			$hostids = array_merge($hostids, $maintenance['hostids']);
270			$groupids = array_merge($groupids, $maintenance['groupids']);
271		}
272
273		// validate hosts & groups
274		if (empty($hostids) && empty($groupids)) {
275			self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one host or group should be selected.'));
276		}
277
278		// hosts permissions
279		$options = [
280			'hostids' => $hostids,
281			'editable' => true,
282			'output' => ['hostid'],
283			'preservekeys' => true
284		];
285		$updHosts = API::Host()->get($options);
286		foreach ($hostids as $hostid) {
287			if (!isset($updHosts[$hostid])) {
288				self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
289			}
290		}
291		// groups permissions
292		$options = [
293			'groupids' => $groupids,
294			'editable' => true,
295			'output' => ['groupid'],
296			'preservekeys' => true
297		];
298		$updGroups = API::HostGroup()->get($options);
299		foreach ($groupids as $groupid) {
300			if (!isset($updGroups[$groupid])) {
301				self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
302			}
303		}
304
305		$this->removeSecondsFromTimes($maintenances);
306
307		$tid = 0;
308		$insert = [];
309		$timeperiods = [];
310		$insertTimeperiods = [];
311		$now = time();
312		$now -= $now % SEC_PER_MIN;
313
314		// check fields
315		foreach ($maintenances as $maintenance) {
316			$dbFields = [
317				'name' => null,
318				'active_since' => null,
319				'active_till' => null
320			];
321
322			if (!check_db_fields($dbFields, $maintenance)) {
323				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for maintenance.'));
324			}
325		}
326
327		$collectionValidator = new CCollectionValidator([
328			'uniqueField' => 'name',
329			'messageDuplicate' => _('Maintenance "%1$s" already exists.')
330		]);
331		$this->checkValidator($maintenances, $collectionValidator);
332
333		// validate if maintenance name already exists
334		$dbMaintenances = $this->get([
335			'output' => ['name'],
336			'filter' => ['name' => zbx_objectValues($maintenances, 'name')],
337			'nopermissions' => true,
338			'limit' => 1
339		]);
340
341		if ($dbMaintenances) {
342			$dbMaintenance = reset($dbMaintenances);
343			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Maintenance "%1$s" already exists.', $dbMaintenance['name']));
344		}
345
346		foreach ($maintenances as $mnum => $maintenance) {
347			// validate maintenance active since
348			if (!validateUnixTime($maintenance['active_since'])) {
349				self::exception(ZBX_API_ERROR_PARAMETERS, _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active since')));
350			}
351
352			// validate maintenance active till
353			if (!validateUnixTime($maintenance['active_till'])) {
354				self::exception(ZBX_API_ERROR_PARAMETERS, _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active till')));
355			}
356
357			// validate maintenance active interval
358			if ($maintenance['active_since'] > $maintenance['active_till']) {
359				self::exception(ZBX_API_ERROR_PARAMETERS, _('Maintenance "Active since" value cannot be bigger than "Active till".'));
360			}
361
362			// validate timeperiods
363			if (!array_key_exists('timeperiods', $maintenance) || !is_array($maintenance['timeperiods'])
364					|| !$maintenance['timeperiods']) {
365				self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.'));
366			}
367
368			foreach ($maintenance['timeperiods'] as $timeperiod) {
369				if (!is_array($timeperiod)) {
370					self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.'));
371				}
372
373				$dbFields = [
374					'timeperiod_type' => TIMEPERIOD_TYPE_ONETIME,
375					'period' => SEC_PER_HOUR,
376					'start_date' =>	$now
377				];
378				check_db_fields($dbFields, $timeperiod);
379
380				$tid++;
381				$insertTimeperiods[$tid] = $timeperiod;
382				$timeperiods[$tid] = $mnum;
383			}
384
385			$insert[$mnum] = $maintenance;
386		}
387		$maintenanceids = DB::insert('maintenances', $insert);
388		$timeperiodids = DB::insert('timeperiods', $insertTimeperiods);
389
390		$insertWindows = [];
391		foreach ($timeperiods as $tid => $mnum) {
392			$insertWindows[] = [
393				'timeperiodid' => $timeperiodids[$tid],
394				'maintenanceid' => $maintenanceids[$mnum]
395			];
396		}
397		DB::insert('maintenances_windows', $insertWindows);
398
399		$insertHosts = [];
400		$insertGroups = [];
401		foreach ($maintenances as $mnum => $maintenance) {
402			foreach ($maintenance['hostids'] as $hostid) {
403				$insertHosts[] = [
404					'hostid' => $hostid,
405					'maintenanceid' => $maintenanceids[$mnum]
406				];
407			}
408			foreach ($maintenance['groupids'] as $groupid) {
409				$insertGroups[] = [
410					'groupid' => $groupid,
411					'maintenanceid' => $maintenanceids[$mnum]
412				];
413			}
414
415			add_audit_details(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MAINTENANCE, $maintenanceids[$mnum],
416				$maintenance['name'], null
417			);
418		}
419		DB::insert('maintenances_hosts', $insertHosts);
420		DB::insert('maintenances_groups', $insertGroups);
421
422		return ['maintenanceids' => $maintenanceids];
423	}
424
425	/**
426	 * Update maintenances.
427	 *
428	 * @param array $maintenances
429	 *
430	 * @throws APIException if no permissions to object, it does no exists or validation errors
431	 *
432	 * @return array
433	 */
434	public function update(array $maintenances) {
435		if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) {
436			self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
437		}
438
439		$maintenances = zbx_toArray($maintenances);
440		$maintenanceids = zbx_objectValues($maintenances, 'maintenanceid');
441
442		if (!$maintenances) {
443			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
444		}
445
446		$db_fields = [
447			'maintenanceid' => null
448		];
449
450		foreach ($maintenances as $maintenance) {
451			// Validate fields.
452			if (!check_db_fields($db_fields, $maintenance)) {
453				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for maintenance.'));
454			}
455		}
456
457		$db_maintenances = $this->get([
458			'output' => API_OUTPUT_EXTEND,
459			'maintenanceids' => $maintenanceids,
460			'selectGroups' => ['groupid'],
461			'selectHosts' => ['hostid'],
462			'selectTimeperiods' => API_OUTPUT_EXTEND,
463			'editable' => true,
464			'preservekeys' => true
465		]);
466
467		$changed_names = [];
468		$hostids = [];
469		$groupids = [];
470
471		foreach ($maintenances as $maintenance) {
472			if (!array_key_exists($maintenance['maintenanceid'], $db_maintenances)) {
473				self::exception(ZBX_API_ERROR_PERMISSIONS,
474					_('No permissions to referred object or it does not exist!')
475				);
476			}
477
478			// Check maintenances names and collect for unique checking.
479			if (array_key_exists('name', $maintenance) && $maintenance['name'] !== ''
480					&& $db_maintenances[$maintenance['maintenanceid']]['name'] !== $maintenance['name']) {
481				if (array_key_exists($maintenance['name'], $changed_names)) {
482					self::exception(ZBX_API_ERROR_PARAMETERS,
483						_s('Maintenance "%1$s" already exists.', $maintenance['name'])
484					);
485				}
486
487				$changed_names[$maintenance['name']] = $maintenance['name'];
488			}
489
490			// Validate maintenance active since.
491			if (array_key_exists('active_since', $maintenance)) {
492				$active_since = $maintenance['active_since'];
493
494				if (!validateUnixTime($active_since)) {
495					self::exception(ZBX_API_ERROR_PARAMETERS,
496						_s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active since'))
497					);
498				}
499			}
500			else {
501				$active_since = $db_maintenances[$maintenance['maintenanceid']]['active_since'];
502			}
503
504			// Validate maintenance active till.
505			if (array_key_exists('active_till', $maintenance)) {
506				$active_till = $maintenance['active_till'];
507
508				if (!validateUnixTime($active_till)) {
509					self::exception(ZBX_API_ERROR_PARAMETERS,
510						_s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active till'))
511					);
512				}
513			}
514			else {
515				$active_till = $db_maintenances[$maintenance['maintenanceid']]['active_till'];
516			}
517
518			// Validate maintenance active interval.
519			if ($active_since > $active_till) {
520				self::exception(ZBX_API_ERROR_PARAMETERS,
521					_('Maintenance "Active since" value cannot be bigger than "Active till".')
522				);
523			}
524
525			// Validate timeperiods.
526			if (array_key_exists('timeperiods', $maintenance)) {
527				if (!is_array($maintenance['timeperiods']) || !$maintenance['timeperiods']) {
528					self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.'));
529				}
530
531				foreach ($maintenance['timeperiods'] as $timeperiod) {
532					if (!is_array($timeperiod)) {
533						self::exception(ZBX_API_ERROR_PARAMETERS,
534							_('At least one maintenance period must be created.')
535						);
536					}
537				}
538			}
539
540			// Collect hostids for permission checking.
541			if (array_key_exists('hostids', $maintenance) && is_array($maintenance['hostids'])) {
542				$hostids = array_merge($hostids, $maintenance['hostids']);
543				$has_hosts = (bool) $maintenance['hostids'];
544			}
545			else {
546				$has_hosts = (bool) $db_maintenances[$maintenance['maintenanceid']]['hosts'];
547			}
548
549			// Collect groupids for permission checking.
550			if (array_key_exists('groupids', $maintenance) && is_array($maintenance['groupids'])) {
551				$groupids = array_merge($groupids, $maintenance['groupids']);
552				$has_groups = (bool) $maintenance['groupids'];
553			}
554			else {
555				$has_groups = (bool) $db_maintenances[$maintenance['maintenanceid']]['groups'];
556			}
557
558			if (!$has_hosts && !$has_groups) {
559				self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one host or group should be selected.'));
560			}
561		}
562
563		// Check if maintenance already exists.
564		if ($changed_names) {
565			$db_maintenances_names = $this->get([
566				'output' => ['name'],
567				'filter' => ['name' => $changed_names],
568				'nopermissions' => true,
569				'limit' => 1
570			]);
571
572			if ($db_maintenances_names) {
573				$maintenance = reset($db_maintenances_names);
574				self::exception(ZBX_API_ERROR_PARAMETERS,
575					_s('Maintenance "%1$s" already exists.', $maintenance['name'])
576				);
577			}
578		}
579
580		// Check hosts permission and availability.
581		if ($hostids) {
582			$db_hosts = API::Host()->get([
583				'output' => [],
584				'hostids' => $hostids,
585				'editable' => true,
586				'preservekeys' => true
587			]);
588
589			foreach ($hostids as $hostid) {
590				if (!array_key_exists($hostid, $db_hosts)) {
591					self::exception(ZBX_API_ERROR_PERMISSIONS,
592						_('No permissions to referred object or it does not exist!')
593					);
594				}
595			}
596		}
597
598		// Check host groups permission and availability.
599		if ($groupids) {
600			$db_groups = API::HostGroup()->get([
601				'output' => [],
602				'groupids' => $groupids,
603				'editable' => true,
604				'preservekeys' => true
605			]);
606
607			foreach ($groupids as $groupid) {
608				if (!array_key_exists($groupid, $db_groups)) {
609					self::exception(ZBX_API_ERROR_PERMISSIONS,
610						_('No permissions to referred object or it does not exist!')
611					);
612				}
613			}
614		}
615
616		$this->removeSecondsFromTimes($maintenances);
617
618		$update_maintenances = [];
619		foreach ($maintenances as $mnum => $maintenance) {
620			$update_maintenances[$mnum] = [
621				'values' => $maintenance,
622				'where' => ['maintenanceid' => $maintenance['maintenanceid']]
623			];
624
625			// Update time periods.
626			if (array_key_exists('timeperiods', $maintenance)) {
627				$this->replaceTimePeriods($db_maintenances[$maintenance['maintenanceid']], $maintenance);
628			}
629
630			add_audit_ext(
631				AUDIT_ACTION_UPDATE,
632				AUDIT_RESOURCE_MAINTENANCE,
633				$maintenance['maintenanceid'],
634				array_key_exists('name', $maintenance)
635					? $maintenance['name']
636					: $db_maintenances[$maintenance['maintenanceid']]['name'],
637				'maintenances',
638				$db_maintenances[$maintenance['maintenanceid']],
639				$maintenance
640			);
641		}
642		DB::update('maintenances', $update_maintenances);
643
644		// Some of the hosts and groups bound to maintenance must be deleted, other inserted and others left alone.
645		$insert_hosts = [];
646		$insert_groups = [];
647
648		foreach ($maintenances as $maintenance) {
649			if (array_key_exists('hostids', $maintenance)) {
650				// Putting apart those host<->maintenance connections that should be inserted, deleted and not changed:
651				// $hosts_diff['first'] - new hosts, that should be inserted;
652				// $hosts_diff['second'] - hosts, that should be deleted;
653				// $hosts_diff['both'] - hosts, that should not be touched;
654				$hosts_diff = zbx_array_diff(
655					zbx_toObject($maintenance['hostids'], 'hostid'),
656					$db_maintenances[$maintenance['maintenanceid']]['hosts'],
657					'hostid'
658				);
659
660				foreach ($hosts_diff['first'] as $host) {
661					$insert_hosts[] = [
662						'hostid' => $host['hostid'],
663						'maintenanceid' => $maintenance['maintenanceid']
664					];
665				}
666				foreach ($hosts_diff['second'] as $host) {
667					DB::delete('maintenances_hosts', [
668						'hostid' => $host['hostid'],
669						'maintenanceid' => $maintenance['maintenanceid']
670					]);
671				}
672			}
673
674			if (array_key_exists('groupids', $maintenance)) {
675				// Now the same with the groups.
676				$groups_diff = zbx_array_diff(
677					zbx_toObject($maintenance['groupids'], 'groupid'),
678					$db_maintenances[$maintenance['maintenanceid']]['groups'],
679					'groupid'
680				);
681
682				foreach ($groups_diff['first'] as $group) {
683					$insert_groups[] = [
684						'groupid' => $group['groupid'],
685						'maintenanceid' => $maintenance['maintenanceid']
686					];
687				}
688				foreach ($groups_diff['second'] as $group) {
689					DB::delete('maintenances_groups', [
690						'groupid' => $group['groupid'],
691						'maintenanceid' => $maintenance['maintenanceid']
692					]);
693				}
694			}
695		}
696
697		if ($insert_hosts) {
698			DB::insert('maintenances_hosts', $insert_hosts);
699		}
700
701		if ($insert_groups) {
702			DB::insert('maintenances_groups', $insert_groups);
703		}
704
705		return ['maintenanceids' => $maintenanceids];
706	}
707
708	/**
709	 * Delete Maintenances.
710	 *
711	 * @param array $maintenanceids
712	 *
713	 * @return array
714	 */
715	public function delete(array $maintenanceids) {
716		if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) {
717			self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
718		}
719
720		$options = [
721			'output' => ['maintenanceid', 'name'],
722			'maintenanceids' => $maintenanceids,
723			'editable' => true,
724			'preservekeys' => true
725		];
726		$maintenances = $this->get($options);
727		foreach ($maintenanceids as $maintenanceid) {
728			if (!isset($maintenances[$maintenanceid])) {
729				self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.'));
730			}
731		}
732
733		$timeperiodids = [];
734		$dbTimeperiods = DBselect(
735			'SELECT DISTINCT tp.timeperiodid'.
736			' FROM timeperiods tp,maintenances_windows mw'.
737			' WHERE '.dbConditionInt('mw.maintenanceid', $maintenanceids).
738				' AND tp.timeperiodid=mw.timeperiodid'
739		);
740		while ($timeperiod = DBfetch($dbTimeperiods)) {
741			$timeperiodids[] = $timeperiod['timeperiodid'];
742		}
743
744		$midCond = ['maintenanceid' => $maintenanceids];
745
746		// remove maintenanceid from hosts table
747		$options = [
748			'real_hosts' => true,
749			'output' => ['hostid'],
750			'filter' => ['maintenanceid' => $maintenanceids]
751		];
752		$hosts = API::Host()->get($options);
753		if (!empty($hosts)) {
754			DB::update('hosts', [
755				'values' => ['maintenanceid' => 0],
756				'where' => ['hostid' => zbx_objectValues($hosts, 'hostid')]
757			]);
758		}
759
760		DB::delete('timeperiods', ['timeperiodid' => $timeperiodids]);
761		DB::delete('maintenances_windows', $midCond);
762		DB::delete('maintenances_hosts', $midCond);
763		DB::delete('maintenances_groups', $midCond);
764		DB::delete('maintenances', $midCond);
765
766		foreach ($maintenances as $maintenanceid => $maintenance) {
767			add_audit_details(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MAINTENANCE, $maintenanceid, $maintenance['name'],
768				null
769			);
770		}
771
772		return ['maintenanceids' => $maintenanceids];
773	}
774
775	/**
776	 * Reset seconds to zero in maintenace time values.
777	 *
778	 * @param array $maintenances passed by reference
779	 */
780	protected function removeSecondsFromTimes(array &$maintenances) {
781		foreach ($maintenances as &$maintenance) {
782			if (isset($maintenance['active_since'])) {
783				$maintenance['active_since'] -= $maintenance['active_since'] % SEC_PER_MIN;
784			}
785
786			if (isset($maintenance['active_till'])) {
787				$maintenance['active_till'] -= $maintenance['active_till'] % SEC_PER_MIN;
788			}
789
790
791			if (isset($maintenance['timeperiods'])) {
792				foreach ($maintenance['timeperiods'] as &$timeperiod) {
793					if (isset($timeperiod['start_date'])) {
794						$timeperiod['start_date'] -= $timeperiod['start_date'] % SEC_PER_MIN;
795					}
796				}
797				unset($timeperiod);
798			}
799		}
800		unset($maintenance);
801	}
802
803	/**
804	 * Updates maintenance time periods.
805	 *
806	 * @param array $maintenance
807	 * @param array $oldMaintenance
808	 */
809	protected function replaceTimePeriods(array $oldMaintenance, array $maintenance) {
810		// replace time periods
811		$timePeriods = DB::replace('timeperiods', $oldMaintenance['timeperiods'], $maintenance['timeperiods']);
812
813		// link new time periods to maintenance
814		$oldTimePeriods = zbx_toHash($oldMaintenance['timeperiods'], 'timeperiodid');
815		$newMaintenanceWindows = [];
816		foreach ($timePeriods as $tp) {
817			if (!isset($oldTimePeriods[$tp['timeperiodid']])) {
818				$newMaintenanceWindows[] = [
819					'maintenanceid' => $maintenance['maintenanceid'],
820					'timeperiodid' => $tp['timeperiodid']
821				];
822			}
823		}
824		DB::insert('maintenances_windows', $newMaintenanceWindows);
825	}
826
827	protected function addRelatedObjects(array $options, array $result) {
828		$result = parent::addRelatedObjects($options, $result);
829
830		// selectGroups
831		if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) {
832			$relationMap = $this->createRelationMap($result, 'maintenanceid', 'groupid', 'maintenances_groups');
833			$groups = API::HostGroup()->get([
834				'output' => $options['selectGroups'],
835				'hostgroupids' => $relationMap->getRelatedIds(),
836				'preservekeys' => true
837			]);
838			$result = $relationMap->mapMany($result, $groups, 'groups');
839		}
840
841		// selectHosts
842		if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) {
843			$relationMap = $this->createRelationMap($result, 'maintenanceid', 'hostid', 'maintenances_hosts');
844			$groups = API::Host()->get([
845				'output' => $options['selectHosts'],
846				'hostids' => $relationMap->getRelatedIds(),
847				'preservekeys' => true
848			]);
849			$result = $relationMap->mapMany($result, $groups, 'hosts');
850		}
851
852		// selectTimeperiods
853		if ($options['selectTimeperiods'] !== null && $options['selectTimeperiods'] != API_OUTPUT_COUNT) {
854			$relationMap = $this->createRelationMap($result, 'maintenanceid', 'timeperiodid', 'maintenances_windows');
855			$timeperiods = API::getApiService()->select('timeperiods', [
856				'output' => $options['selectTimeperiods'],
857				'filter' => ['timeperiodid' => $relationMap->getRelatedIds()],
858				'preservekeys' => true
859			]);
860			$result = $relationMap->mapMany($result, $timeperiods, 'timeperiods');
861		}
862
863		return $result;
864	}
865}
866