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
22require_once dirname(__FILE__).'/include/config.inc.php';
23require_once dirname(__FILE__).'/include/hosts.inc.php';
24require_once dirname(__FILE__).'/include/screens.inc.php';
25require_once dirname(__FILE__).'/include/forms.inc.php';
26require_once dirname(__FILE__).'/include/ident.inc.php';
27
28$page['type'] = detect_page_type(PAGE_TYPE_HTML);
29$page['title'] = _('Configuration of templates');
30$page['file'] = 'templates.php';
31$page['scripts'] = ['multiselect.js'];
32
33require_once dirname(__FILE__).'/include/page_header.php';
34
35//		VAR						TYPE		OPTIONAL FLAGS			VALIDATION	EXCEPTION
36$fields = [
37	'groups'			=> [T_ZBX_STR, O_OPT, null,			NOT_EMPTY,	'isset({add}) || isset({update})'],
38	'clear_templates'	=> [T_ZBX_INT, O_OPT, P_SYS,		DB_ID,	null],
39	'templates'			=> [T_ZBX_INT, O_OPT, null,		DB_ID,	null],
40	'add_templates'		=> [T_ZBX_INT, O_OPT, null,		DB_ID,	null],
41	'add_template' 		=> [T_ZBX_STR, O_OPT, null,		null,	null],
42	'templateid'		=> [T_ZBX_INT, O_OPT, P_SYS,		DB_ID,	'isset({form}) && {form} == "update"'],
43	'template_name'		=> [T_ZBX_STR, O_OPT, null,		NOT_EMPTY, 'isset({add}) || isset({update})', _('Template name')],
44	'visiblename'		=> [T_ZBX_STR, O_OPT, null,		null,	'isset({add}) || isset({update})'],
45	'groupid'			=> [T_ZBX_INT, O_OPT, P_SYS,		DB_ID,	null],
46	'description'		=> [T_ZBX_STR, O_OPT, null,		null,	null],
47	'macros'			=> [T_ZBX_STR, O_OPT, P_SYS,		null,	null],
48	'show_inherited_macros' => [T_ZBX_INT, O_OPT, null,	IN([0,1]), null],
49	// actions
50	'action'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,
51								IN('"template.export","template.massdelete","template.massdeleteclear"'),
52								null
53							],
54	'unlink'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
55	'unlink_and_clear'	=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
56	'add'				=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
57	'update'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
58	'clone'				=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
59	'full_clone'		=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
60	'delete'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
61	'delete_and_clear'	=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
62	'cancel'			=> [T_ZBX_STR, O_OPT, P_SYS,		null,	null],
63	'form'				=> [T_ZBX_STR, O_OPT, P_SYS,		null,	null],
64	'form_refresh'		=> [T_ZBX_INT, O_OPT, null,		null,	null],
65	// filter
66	'filter_set'		=> [T_ZBX_STR, O_OPT, P_SYS,	null,		null],
67	'filter_rst'		=> [T_ZBX_STR, O_OPT, P_SYS,	null,		null],
68	'filter_name'		=> [T_ZBX_STR, O_OPT, null,		null,		null],
69	'filter_templates' =>  [T_ZBX_INT, O_OPT, null,		DB_ID,		null],
70	// sort and sortorder
71	'sort'				=> [T_ZBX_STR, O_OPT, P_SYS, IN('"name"'),									null],
72	'sortorder'			=> [T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'),	null]
73];
74check_fields($fields);
75
76/*
77 * Permissions
78 */
79if (getRequest('groupid') && !isWritableHostGroups([getRequest('groupid')])) {
80	access_deny();
81}
82if (getRequest('templateid')) {
83	$templates = API::Template()->get([
84		'output' => [],
85		'templateids' => getRequest('templateid'),
86		'editable' => true
87	]);
88
89	if (!$templates) {
90		access_deny();
91	}
92}
93
94$templateIds = getRequest('templates', []);
95
96// remove inherited macros data (actions: 'add', 'update' and 'form')
97if (hasRequest('macros')) {
98	$_REQUEST['macros'] = cleanInheritedMacros($_REQUEST['macros']);
99
100	// remove empty new macro lines
101	foreach ($_REQUEST['macros'] as $idx => $macro) {
102		if (!array_key_exists('hostmacroid', $macro) && $macro['macro'] === '' && $macro['value'] === '') {
103			unset($_REQUEST['macros'][$idx]);
104		}
105	}
106}
107
108/*
109 * Actions
110 */
111if (isset($_REQUEST['add_template']) && isset($_REQUEST['add_templates'])) {
112	$_REQUEST['templates'] = array_merge($templateIds, $_REQUEST['add_templates']);
113}
114if (hasRequest('unlink') || hasRequest('unlink_and_clear')) {
115	$unlinkTemplates = [];
116
117	if (hasRequest('unlink') && is_array(getRequest('unlink'))) {
118		$unlinkTemplates = array_keys(getRequest('unlink'));
119	}
120	elseif (hasRequest('unlink_and_clear') && is_array(getRequest('unlink_and_clear'))) {
121		$unlinkTemplates = array_keys(getRequest('unlink_and_clear'));
122		$_REQUEST['clear_templates'] = array_merge($unlinkTemplates, getRequest('clear_templates', []));
123	}
124
125	foreach ($unlinkTemplates as $id) {
126		unset($_REQUEST['templates'][array_search($id, $_REQUEST['templates'])]);
127	}
128}
129elseif ((hasRequest('clone') || hasRequest('full_clone')) && hasRequest('templateid')) {
130	$_REQUEST['form'] = hasRequest('clone') ? 'clone' : 'full_clone';
131
132	$groups = getRequest('groups', []);
133	$groupids = [];
134
135	// Remove inaccessible groups from request, but leave "new".
136	foreach ($groups as $group) {
137		if (!is_array($group)) {
138			$groupids[] = $group;
139		}
140	}
141
142	if ($groupids) {
143		$groups_allowed = API::HostGroup()->get([
144			'output' => [],
145			'groupids' => $groupids,
146			'editable' => true,
147			'preservekeys' => true
148		]);
149
150		foreach ($groups as $idx => $group) {
151			if (!is_array($group) && !array_key_exists($group, $groups_allowed)) {
152				unset($groups[$idx]);
153			}
154		}
155
156		$_REQUEST['groups'] = $groups;
157	}
158
159	if (hasRequest('clone')) {
160		unset($_REQUEST['templateid']);
161	}
162}
163elseif (hasRequest('add') || hasRequest('update')) {
164	try {
165		DBstart();
166
167		$templateId = getRequest('templateid', 0);
168		$cloneTemplateId = 0;
169
170		if (getRequest('form') === 'full_clone') {
171			$cloneTemplateId = $templateId;
172			$templateId = 0;
173		}
174
175		if ($templateId == 0) {
176			$messageSuccess = _('Template added');
177			$messageFailed = _('Cannot add template');
178		}
179		else {
180			$messageSuccess = _('Template updated');
181			$messageFailed = _('Cannot update template');
182		}
183
184		// Add new group.
185		$groups = getRequest('groups', []);
186		$new_groups = [];
187
188		foreach ($groups as $idx => $group) {
189			if (is_array($group) && array_key_exists('new', $group)) {
190				$new_groups[] = ['name' => $group['new']];
191				unset($groups[$idx]);
192			}
193		}
194
195		if ($new_groups) {
196			$new_groupid = API::HostGroup()->create($new_groups);
197
198			if (!$new_groupid) {
199				throw new Exception();
200			}
201
202			$groups = array_merge($groups, $new_groupid['groupids']);
203		}
204
205		// linked templates
206		$linkedTemplates = getRequest('templates', []);
207		$templates = [];
208		foreach ($linkedTemplates as $linkedTemplateId) {
209			$templates[] = ['templateid' => $linkedTemplateId];
210		}
211
212		$templatesClear = getRequest('clear_templates', []);
213		$templatesClear = zbx_toObject($templatesClear, 'templateid');
214		$templateName = getRequest('template_name', '');
215
216		// create / update template
217		$template = [
218			'host' => $templateName,
219			'name' => getRequest('visiblename', ''),
220			'groups' => zbx_toObject($groups, 'groupid'),
221			'templates' => $templates,
222			'macros' => getRequest('macros', []),
223			'description' => getRequest('description', '')
224		];
225
226		if ($templateId == 0) {
227			$result = API::Template()->create($template);
228
229			if ($result) {
230				$templateId = reset($result['templateids']);
231			}
232			else {
233				throw new Exception();
234			}
235		}
236		else {
237			$template['templateid'] = $templateId;
238			$template['templates_clear'] = $templatesClear;
239
240			$result = API::Template()->update($template);
241
242			if ($result) {
243				add_audit_ext(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_TEMPLATE, $templateId, $templateName, 'hosts', null, null);
244			}
245			else {
246				throw new Exception();
247			}
248		}
249
250		// full clone
251		if ($cloneTemplateId != 0 && getRequest('form') === 'full_clone') {
252			if (!copyApplications($cloneTemplateId, $templateId)) {
253				throw new Exception();
254			}
255
256			/*
257			 * First copy web scenarios with web items, so that later regular items can use web item as their master
258			 * item.
259			 */
260			if (!copyHttpTests($cloneTemplateId, $templateId)) {
261				throw new Exception();
262			}
263
264			if (!copyItems($cloneTemplateId, $templateId)) {
265				throw new Exception();
266			}
267
268			// copy triggers
269			$dbTriggers = API::Trigger()->get([
270				'output' => ['triggerid'],
271				'hostids' => $cloneTemplateId,
272				'inherited' => false
273			]);
274
275			if ($dbTriggers) {
276				$result &= copyTriggersToHosts(zbx_objectValues($dbTriggers, 'triggerid'),
277						$templateId, $cloneTemplateId);
278
279				if (!$result) {
280					throw new Exception();
281				}
282			}
283
284			// copy graphs
285			$dbGraphs = API::Graph()->get([
286				'output' => ['graphid'],
287				'hostids' => $cloneTemplateId,
288				'inherited' => false
289			]);
290
291			foreach ($dbGraphs as $dbGraph) {
292				copyGraphToHost($dbGraph['graphid'], $templateId);
293			}
294
295			// copy discovery rules
296			$dbDiscoveryRules = API::DiscoveryRule()->get([
297				'output' => ['itemid'],
298				'hostids' => $cloneTemplateId,
299				'inherited' => false
300			]);
301
302			if ($dbDiscoveryRules) {
303				$result &= API::DiscoveryRule()->copy([
304					'discoveryids' => zbx_objectValues($dbDiscoveryRules, 'itemid'),
305					'hostids' => [$templateId]
306				]);
307
308				if (!$result) {
309					throw new Exception();
310				}
311			}
312
313			// copy template screens
314			$dbTemplateScreens = API::TemplateScreen()->get([
315				'noInheritance' => true,
316				'output' => ['screenid'],
317				'templateids' => $cloneTemplateId,
318				'preservekeys' => true
319			]);
320
321			if ($dbTemplateScreens) {
322				$result &= API::TemplateScreen()->copy([
323					'screenIds' => zbx_objectValues($dbTemplateScreens, 'screenid'),
324					'templateIds' => $templateId
325				]);
326
327				if (!$result) {
328					throw new Exception();
329				}
330			}
331		}
332
333		unset($_REQUEST['form'], $_REQUEST['templateid']);
334		$result = DBend($result);
335
336		if ($result) {
337			uncheckTableRows();
338		}
339		show_messages($result, $messageSuccess, $messageFailed);
340	}
341	catch (Exception $e) {
342		DBend(false);
343		show_error_message($messageFailed);
344	}
345}
346elseif (isset($_REQUEST['delete']) && isset($_REQUEST['templateid'])) {
347	DBstart();
348
349	$result = API::Template()->massUpdate([
350		'templates' => zbx_toObject($_REQUEST['templateid'], 'templateid'),
351		'hosts' => []
352	]);
353	if ($result) {
354		$result = API::Template()->delete([getRequest('templateid')]);
355	}
356
357	$result = DBend($result);
358
359	if ($result) {
360		unset($_REQUEST['form'], $_REQUEST['templateid']);
361		uncheckTableRows();
362	}
363	unset($_REQUEST['delete']);
364	show_messages($result, _('Template deleted'), _('Cannot delete template'));
365}
366elseif (isset($_REQUEST['delete_and_clear']) && isset($_REQUEST['templateid'])) {
367	DBstart();
368
369	$result = API::Template()->delete([getRequest('templateid')]);
370
371	$result = DBend($result);
372
373	if ($result) {
374		unset($_REQUEST['form'], $_REQUEST['templateid']);
375		uncheckTableRows();
376	}
377	unset($_REQUEST['delete']);
378	show_messages($result, _('Template deleted'), _('Cannot delete template'));
379}
380elseif (hasRequest('action') && str_in_array(getRequest('action'), ['template.massdelete', 'template.massdeleteclear']) && hasRequest('templates')) {
381	$templates = getRequest('templates');
382
383	DBstart();
384
385	$result = true;
386
387	if (getRequest('action') === 'template.massdelete') {
388		$result = API::Template()->massUpdate([
389			'templates' => zbx_toObject($templates, 'templateid'),
390			'hosts' => []
391		]);
392	}
393
394	if ($result) {
395		$result = API::Template()->delete($templates);
396	}
397
398	$result = DBend($result);
399
400	if ($result) {
401		uncheckTableRows();
402	}
403	else {
404		$templateids = API::Template()->get([
405			'output' => [],
406			'templateids' => $templates,
407			'editable' => true
408		]);
409		uncheckTableRows(null, zbx_objectValues($templateids, 'templateid'));
410	}
411	show_messages($result, _('Template deleted'), _('Cannot delete template'));
412}
413
414/*
415 * Display
416 */
417$pageFilter = new CPageFilter([
418	'config' => [
419		'individual' => 1
420	],
421	'groups' => [
422		'templated_hosts' => true,
423		'editable' => true
424	],
425	'groupid' => getRequest('groupid')
426]);
427$_REQUEST['groupid'] = $pageFilter->groupid;
428
429if (hasRequest('form')) {
430	$data = [
431		'form' => getRequest('form'),
432		'templateid' => getRequest('templateid', 0),
433		'show_inherited_macros' => getRequest('show_inherited_macros', 0)
434	];
435
436	if ($data['templateid'] != 0) {
437		$dbTemplates = API::Template()->get([
438			'templateids' => $data['templateid'],
439			'selectGroups' => API_OUTPUT_EXTEND,
440			'selectParentTemplates' => ['templateid', 'name'],
441			'selectMacros' => API_OUTPUT_EXTEND,
442			'output' => API_OUTPUT_EXTEND
443		]);
444		$data['dbTemplate'] = reset($dbTemplates);
445
446		$data['original_templates'] = [];
447		foreach ($data['dbTemplate']['parentTemplates'] as $parentTemplate) {
448			$data['original_templates'][$parentTemplate['templateid']] = $parentTemplate['templateid'];
449		}
450	}
451	else {
452		$data['original_templates'] = [];
453	}
454
455	// description
456	$data['description'] = ($data['templateid'] != 0 && !hasRequest('form_refresh'))
457		? $data['dbTemplate']['description']
458		: getRequest('description');
459
460	$templateIds = getRequest('templates', hasRequest('form_refresh') ? [] : $data['original_templates']);
461
462	// Get linked templates.
463	$data['linkedTemplates'] = API::Template()->get([
464		'output' => ['templateid', 'name'],
465		'templateids' => $templateIds,
466		'preservekeys' => true
467	]);
468
469	$data['writable_templates'] = API::Template()->get([
470		'output' => ['templateid'],
471		'templateids' => $templateIds,
472		'editable' => true,
473		'preservekeys' => true
474	]);
475
476	CArrayHelper::sort($data['linkedTemplates'], ['name']);
477
478	$groups = [];
479
480	if (!hasRequest('form_refresh')) {
481		if ($data['templateid'] != 0) {
482			$groups = zbx_objectValues($data['dbTemplate']['groups'], 'groupid');
483		}
484		elseif (getRequest('groupid', 0) != 0) {
485			$groups[] = getRequest('groupid');
486		}
487	}
488	else {
489		$groups = getRequest('groups', []);
490	}
491
492	$groupids = [];
493
494	foreach ($groups as $group) {
495		if (is_array($group) && array_key_exists('new', $group)) {
496			continue;
497		}
498
499		$groupids[] = $group;
500	}
501
502	// Groups with R and RW permissions.
503	$groups_all = $groupids
504		? API::HostGroup()->get([
505			'output' => ['name'],
506			'groupids' => $groupids,
507			'preservekeys' => true
508		])
509		: [];
510
511	// Groups with RW permissions.
512	$groups_rw = $groupids && (CWebUser::getType() != USER_TYPE_SUPER_ADMIN)
513		? API::HostGroup()->get([
514			'output' => [],
515			'groupids' => $groupids,
516			'editable' => true,
517			'preservekeys' => true
518		])
519		: [];
520
521	$data['groups_ms'] = [];
522
523	// Prepare data for multiselect.
524	foreach ($groups as $group) {
525		if (is_array($group) && array_key_exists('new', $group)) {
526			$data['groups_ms'][] = [
527				'id' => $group['new'],
528				'name' => $group['new'].' ('._x('new', 'new element in multiselect').')',
529				'isNew' => true
530			];
531		}
532		elseif (array_key_exists($group, $groups_all)) {
533			$data['groups_ms'][] = [
534				'id' => $group,
535				'name' => $groups_all[$group]['name'],
536				'disabled' => (CWebUser::getType() != USER_TYPE_SUPER_ADMIN) && !array_key_exists($group, $groups_rw)
537			];
538		}
539	}
540	CArrayHelper::sort($data['groups_ms'], ['name']);
541
542	$view = new CView('configuration.template.edit', $data);
543}
544else {
545	$sortField = getRequest('sort', CProfile::get('web.'.$page['file'].'.sort', 'name'));
546	$sortOrder = getRequest('sortorder', CProfile::get('web.'.$page['file'].'.sortorder', ZBX_SORT_UP));
547
548	CProfile::update('web.'.$page['file'].'.sort', $sortField, PROFILE_TYPE_STR);
549	CProfile::update('web.'.$page['file'].'.sortorder', $sortOrder, PROFILE_TYPE_STR);
550
551	// filter
552	if (hasRequest('filter_set')) {
553		CProfile::update('web.templates.filter_name', getRequest('filter_name', ''), PROFILE_TYPE_STR);
554		CProfile::updateArray('web.templates.filter_templates', getRequest('filter_templates', []), PROFILE_TYPE_ID);
555	}
556	elseif (hasRequest('filter_rst')) {
557		CProfile::delete('web.templates.filter_name');
558		CProfile::deleteIdx('web.templates.filter_templates');
559	}
560
561	$filter = [
562		'name' => CProfile::get('web.templates.filter_name', ''),
563		'templates' => CProfile::getArray('web.templates.filter_templates', null)
564	];
565
566	$config = select_config();
567
568	// get templates
569	$templates = [];
570
571	$filter['templates'] = $filter['templates']
572		? CArrayHelper::renameObjectsKeys(API::Template()->get([
573			'output' => ['templateid', 'name'],
574			'templateids' => $filter['templates'],
575			'preservekeys' => true
576		]), ['templateid' => 'id'])
577		: [];
578
579	if ($pageFilter->groupsSelected) {
580		$templates = API::Template()->get([
581			'output' => ['templateid', $sortField],
582			'search' => [
583				'name' => ($filter['name'] === '') ? null : $filter['name']
584			],
585			'parentTemplateids' => $filter['templates'] ? array_keys($filter['templates']) : null,
586			'groupids' => $pageFilter->groupids,
587			'editable' => true,
588			'sortfield' => $sortField,
589			'limit' => $config['search_limit'] + 1
590		]);
591	}
592	order_result($templates, $sortField, $sortOrder);
593
594	$url = (new CUrl('templates.php'))
595		->setArgument('groupid', getRequest('groupid', 0));
596
597	$paging = getPagingLine($templates, $sortOrder, $url);
598
599	$templates = API::Template()->get([
600		'output' => ['templateid', 'name'],
601		'selectHosts' => ['hostid', 'name', 'status'],
602		'selectTemplates' => ['templateid', 'name', 'status'],
603		'selectParentTemplates' => ['templateid', 'name', 'status'],
604		'selectItems' => API_OUTPUT_COUNT,
605		'selectTriggers' => API_OUTPUT_COUNT,
606		'selectGraphs' => API_OUTPUT_COUNT,
607		'selectApplications' => API_OUTPUT_COUNT,
608		'selectDiscoveries' => API_OUTPUT_COUNT,
609		'selectScreens' => API_OUTPUT_COUNT,
610		'selectHttpTests' => API_OUTPUT_COUNT,
611		'templateids' => zbx_objectValues($templates, 'templateid'),
612		'editable' => true
613	]);
614
615	order_result($templates, $sortField, $sortOrder);
616
617	// Select writable templates:
618	$linked_template_ids = [];
619	$writable_templates = [];
620	$linked_hostids = [];
621	$writable_hosts = [];
622	foreach ($templates as $template) {
623		$linked_template_ids = array_merge(
624			$linked_template_ids,
625			zbx_objectValues($template['parentTemplates'], 'templateid'),
626			zbx_objectValues($template['templates'], 'templateid'),
627			zbx_objectValues($template['hosts'], 'hostid')
628		);
629
630		$linked_hostids = array_merge(
631			$linked_hostids,
632			zbx_objectValues($template['hosts'], 'hostid')
633		);
634	}
635	if ($linked_template_ids) {
636		$linked_template_ids = array_unique($linked_template_ids);
637		$writable_templates = API::Template()->get([
638			'output' => ['templateid'],
639			'templateids' => $linked_template_ids,
640			'editable' => true,
641			'preservekeys' => true
642		]);
643	}
644	if ($linked_hostids) {
645		$linked_hostids = array_unique($linked_hostids);
646		$writable_hosts = API::Host()->get([
647			'output' => ['hostid'],
648			'hostids' => $linked_hostids,
649			'editable' => true,
650			'preservekeys' => true
651		]);
652	}
653
654	$data = [
655		'pageFilter' => $pageFilter,
656		'templates' => $templates,
657		'paging' => $paging,
658		'filter' => $filter,
659		'sortField' => $sortField,
660		'sortOrder' => $sortOrder,
661		'config' => [
662			'max_in_table' => $config['max_in_table']
663		],
664		'writable_templates' => $writable_templates,
665		'writable_hosts' => $writable_hosts,
666		'profileIdx' => 'web.templates.filter',
667		'active_tab' => CProfile::get('web.templates.filter.active', 1)
668	];
669
670	$view = new CView('configuration.template.list', $data);
671}
672
673$view->render();
674$view->show();
675
676require_once dirname(__FILE__).'/include/page_footer.php';
677