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', 'textareaflexible.js', 'inputsecret.js', 'macrovalue.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	'mass_update_groups' => [T_ZBX_INT, O_OPT, null,	IN([ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]),
39								null
40							],
41	'clear_templates'	=> [T_ZBX_INT, O_OPT, P_SYS,		DB_ID,	null],
42	'templates'			=> [T_ZBX_INT, O_OPT, null,		DB_ID,	null],
43	'linked_templates'	=> [T_ZBX_INT, O_OPT, null,		DB_ID,	null],
44	'add_templates'		=> [T_ZBX_INT, O_OPT, null,		DB_ID,	null],
45	'templateid'		=> [T_ZBX_INT, O_OPT, P_SYS,		DB_ID,	'isset({form}) && {form} == "update"'],
46	'template_name'		=> [T_ZBX_STR, O_OPT, null,		NOT_EMPTY, 'isset({add}) || isset({update})', _('Template name')],
47	'visiblename'		=> [T_ZBX_STR, O_OPT, null,		null,	'isset({add}) || isset({update})'],
48	'groupids'			=> [T_ZBX_INT, O_OPT, null,		DB_ID,	null],
49	'tags'				=> [T_ZBX_STR, O_OPT, null,		null,	null],
50	'mass_update_tags'	=> [T_ZBX_INT, O_OPT, null,		IN([ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]),
51								null
52							],
53	'description'		=> [T_ZBX_STR, O_OPT, null,		null,	null],
54	'macros'			=> [T_ZBX_STR, O_OPT, P_SYS,		null,	null],
55	'mass_update_macros' => [T_ZBX_INT, O_OPT, null,
56								IN([ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE, ZBX_ACTION_REMOVE_ALL]),
57								null
58							],
59	'macros_add'		=> [T_ZBX_INT, O_OPT, null, IN([0,1]), null],
60	'macros_update'		=> [T_ZBX_INT, O_OPT, null, IN([0,1]), null],
61	'macros_remove'		=> [T_ZBX_INT, O_OPT, null, IN([0,1]), null],
62	'macros_remove_all' => [T_ZBX_INT, O_OPT, null, IN([0,1]), null],
63	'visible'			=> [T_ZBX_STR, O_OPT, null,			null,	null],
64	'mass_action_tpls'	=> [T_ZBX_INT, O_OPT, null, IN([ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]), null ],
65	'mass_clear_tpls'	=> [T_ZBX_STR, O_OPT, null,			null,	null],
66	'show_inherited_macros' => [T_ZBX_INT, O_OPT, null,	IN([0,1]), null],
67	// actions
68	'action'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,
69								IN('"template.export","template.massupdate","template.massupdateform",'.
70									'"template.massdelete","template.massdeleteclear"'
71								),
72								null
73							],
74	'unlink'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
75	'unlink_and_clear'	=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
76	'add'				=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
77	'update'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
78	'masssave'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
79	'clone'				=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
80	'full_clone'		=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
81	'delete'			=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
82	'delete_and_clear'	=> [T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,	null],
83	'cancel'			=> [T_ZBX_STR, O_OPT, P_SYS,		null,	null],
84	'form'				=> [T_ZBX_STR, O_OPT, P_SYS,		null,	null],
85	'form_refresh'		=> [T_ZBX_INT, O_OPT, null,		null,	null],
86	// filter
87	'filter_set'		=> [T_ZBX_STR, O_OPT, P_SYS,	null,		null],
88	'filter_rst'		=> [T_ZBX_STR, O_OPT, P_SYS,	null,		null],
89	'filter_name'		=> [T_ZBX_STR, O_OPT, null,		null,		null],
90	'filter_templates' =>  [T_ZBX_INT, O_OPT, null,		DB_ID,		null],
91	'filter_groups'		=> [T_ZBX_INT, O_OPT, null,		DB_ID,		null],
92	'filter_evaltype'	=> [T_ZBX_INT, O_OPT, null,
93								IN([TAG_EVAL_TYPE_AND_OR, TAG_EVAL_TYPE_OR]),
94								null
95							],
96	'filter_tags'		=> [T_ZBX_STR, O_OPT, null,		null,		null],
97	// sort and sortorder
98	'sort'				=> [T_ZBX_STR, O_OPT, P_SYS, IN('"name"'),									null],
99	'sortorder'			=> [T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'),	null]
100];
101check_fields($fields);
102
103/*
104 * Permissions
105 */
106if (getRequest('templateid')) {
107	$templates = API::Template()->get([
108		'output' => [],
109		'templateids' => getRequest('templateid'),
110		'editable' => true
111	]);
112
113	if (!$templates) {
114		access_deny();
115	}
116}
117
118$tags = getRequest('tags', []);
119foreach ($tags as $key => $tag) {
120	// remove empty new tag lines
121	if ($tag['tag'] === '' && $tag['value'] === '') {
122		unset($tags[$key]);
123		continue;
124	}
125
126	// remove inherited tags
127	if (array_key_exists('type', $tag) && !($tag['type'] & ZBX_PROPERTY_OWN)) {
128		unset($tags[$key]);
129	}
130	else {
131		unset($tags[$key]['type']);
132	}
133}
134
135// Remove inherited macros data (actions: 'add', 'update' and 'form').
136$macros = cleanInheritedMacros(getRequest('macros', []));
137
138// Remove empty new macro lines.
139$macros = array_filter($macros, function($macro) {
140	$keys = array_flip(['hostmacroid', 'macro', 'value', 'description']);
141
142	return (bool) array_filter(array_intersect_key($macro, $keys));
143});
144
145/*
146 * Actions
147 */
148if (hasRequest('unlink') || hasRequest('unlink_and_clear')) {
149	$unlinkTemplates = [];
150
151	if (hasRequest('unlink') && is_array(getRequest('unlink'))) {
152		$unlinkTemplates = array_keys(getRequest('unlink'));
153	}
154	elseif (hasRequest('unlink_and_clear') && is_array(getRequest('unlink_and_clear'))) {
155		$unlinkTemplates = array_keys(getRequest('unlink_and_clear'));
156		$_REQUEST['clear_templates'] = array_merge($unlinkTemplates, getRequest('clear_templates', []));
157	}
158
159	foreach ($unlinkTemplates as $id) {
160		unset($_REQUEST['templates'][array_search($id, $_REQUEST['templates'])]);
161	}
162}
163elseif (hasRequest('templateid') && (hasRequest('clone') || hasRequest('full_clone'))) {
164	$_REQUEST['form'] = hasRequest('clone') ? 'clone' : 'full_clone';
165
166	$groups = getRequest('groups', []);
167	$groupids = [];
168
169	// Remove inaccessible groups from request, but leave "new".
170	foreach ($groups as $group) {
171		if (!is_array($group)) {
172			$groupids[] = $group;
173		}
174	}
175
176	if ($groupids) {
177		$groups_allowed = API::HostGroup()->get([
178			'output' => [],
179			'groupids' => $groupids,
180			'editable' => true,
181			'preservekeys' => true
182		]);
183
184		foreach ($groups as $idx => $group) {
185			if (!is_array($group) && !array_key_exists($group, $groups_allowed)) {
186				unset($groups[$idx]);
187			}
188		}
189
190		$_REQUEST['groups'] = $groups;
191	}
192
193	if ($macros && in_array(ZBX_MACRO_TYPE_SECRET, array_column($macros, 'type'))) {
194		// Reset macro type and value.
195		$macros = array_map(function($value) {
196			return ($value['type'] == ZBX_MACRO_TYPE_SECRET)
197				? ['value' => '', 'type' => ZBX_MACRO_TYPE_TEXT] + $value
198				: $value;
199		}, $macros);
200
201		warning(_('The cloned template contains user defined macros with type "Secret text". The value and type of these macros were reset.'));
202	}
203
204	if (hasRequest('clone')) {
205		unset($_REQUEST['templateid']);
206	}
207}
208elseif (hasRequest('action') && getRequest('action') === 'template.massupdate' && hasRequest('masssave')) {
209	$templateids = getRequest('templates', []);
210	$visible = getRequest('visible', []);
211
212	try {
213		DBstart();
214
215		$options = [
216			'output' => ['templateid'],
217			'templateids' => $templateids
218		];
219
220		if (array_key_exists('groups', $visible)) {
221			$options['selectGroups'] = ['groupid'];
222		}
223
224		if (array_key_exists('linked_templates', $visible)
225				&& !(getRequest('mass_action_tpls') == ZBX_ACTION_REPLACE && !hasRequest('mass_clear_tpls'))) {
226			$options['selectParentTemplates'] = ['templateid'];
227		}
228
229		if (array_key_exists('tags', $visible)) {
230			$mass_update_tags = getRequest('mass_update_tags', ZBX_ACTION_ADD);
231
232			if ($mass_update_tags == ZBX_ACTION_ADD || $mass_update_tags == ZBX_ACTION_REMOVE) {
233				$options['selectTags'] = ['tag', 'value'];
234			}
235
236			$unique_tags = [];
237
238			foreach ($tags as $tag) {
239				$unique_tags[$tag['tag'].':'.$tag['value']] = $tag;
240			}
241
242			$tags = array_values($unique_tags);
243		}
244
245		if (array_key_exists('macros', $visible)) {
246			$mass_update_macros = getRequest('mass_update_macros', ZBX_ACTION_ADD);
247
248			if ($mass_update_macros == ZBX_ACTION_ADD || $mass_update_macros == ZBX_ACTION_REPLACE
249					|| $mass_update_macros == ZBX_ACTION_REMOVE) {
250				$options['selectMacros'] = ['hostmacroid', 'macro'];
251			}
252		}
253
254		$templates = API::Template()->get($options);
255
256		if (array_key_exists('groups', $visible)) {
257			$new_groupids = [];
258			$remove_groupids = [];
259			$mass_update_groups = getRequest('mass_update_groups', ZBX_ACTION_ADD);
260
261			if ($mass_update_groups == ZBX_ACTION_ADD || $mass_update_groups == ZBX_ACTION_REPLACE) {
262				if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) {
263					$ins_groups = [];
264
265					foreach (getRequest('groups', []) as $new_group) {
266						if (is_array($new_group) && array_key_exists('new', $new_group)) {
267							$ins_groups[] = ['name' => $new_group['new']];
268						}
269						else {
270							$new_groupids[] = $new_group;
271						}
272					}
273
274					if ($ins_groups) {
275						if (!$result = API::HostGroup()->create($ins_groups)) {
276							throw new Exception();
277						}
278
279						$new_groupids = array_merge($new_groupids, $result['groupids']);
280					}
281				}
282				else {
283					$new_groupids = getRequest('groups', []);
284				}
285			}
286			elseif ($mass_update_groups == ZBX_ACTION_REMOVE) {
287				$remove_groupids = getRequest('groups', []);
288			}
289		}
290
291		$new_values = [];
292
293		if (array_key_exists('description', $visible)) {
294			$new_values['description'] = getRequest('description');
295		}
296
297		$template_macros_add = [];
298		$template_macros_update = [];
299		$template_macros_remove = [];
300		foreach ($templates as &$template) {
301			if (array_key_exists('groups', $visible)) {
302				if ($new_groupids && $mass_update_groups == ZBX_ACTION_ADD) {
303					$current_groupids = zbx_objectValues($template['groups'], 'groupid');
304					$template['groups'] = zbx_toObject(array_unique(array_merge($current_groupids, $new_groupids)),
305						'groupid'
306					);
307				}
308				elseif ($new_groupids && $mass_update_groups == ZBX_ACTION_REPLACE) {
309					$template['groups'] = zbx_toObject($new_groupids, 'groupid');
310				}
311				elseif ($remove_groupids) {
312					$current_groupids = zbx_objectValues($template['groups'], 'groupid');
313					$template['groups'] = zbx_toObject(array_diff($current_groupids, $remove_groupids), 'groupid');
314				}
315			}
316
317			if (array_key_exists('linked_templates', $visible)) {
318				$parent_templateids = array_key_exists('parentTemplates', $template)
319					? zbx_objectValues($template['parentTemplates'], 'templateid')
320					: [];
321
322				switch (getRequest('mass_action_tpls')) {
323					case ZBX_ACTION_ADD:
324						$template['templates'] = array_unique(
325							array_merge($parent_templateids, getRequest('linked_templates', []))
326						);
327						break;
328
329					case ZBX_ACTION_REPLACE:
330						$template['templates'] = getRequest('linked_templates', []);
331						if (getRequest('mass_clear_tpls')) {
332							$template['templates_clear'] = array_unique(
333								array_diff($parent_templateids, getRequest('linked_templates', []))
334							);
335						}
336						break;
337
338					case ZBX_ACTION_REMOVE:
339						$template['templates'] = array_unique(
340							array_diff($parent_templateids, getRequest('linked_templates', []))
341						);
342						if (getRequest('mass_clear_tpls')) {
343							$template['templates_clear'] = array_unique(getRequest('linked_templates', []));
344						}
345						break;
346				}
347			}
348
349			if (array_key_exists('tags', $visible)) {
350				if ($tags && $mass_update_tags == ZBX_ACTION_ADD) {
351					$unique_tags = [];
352
353					foreach (array_merge($template['tags'], $tags) as $tag) {
354						$unique_tags[$tag['tag'].':'.$tag['value']] = $tag;
355					}
356
357					$template['tags'] = array_values($unique_tags);
358				}
359				elseif ($mass_update_tags == ZBX_ACTION_REPLACE) {
360					$template['tags'] = $tags;
361				}
362				elseif ($tags && $mass_update_tags == ZBX_ACTION_REMOVE) {
363					$diff_tags = [];
364
365					foreach ($template['tags'] as $a) {
366						foreach ($tags as $b) {
367							if ($a['tag'] === $b['tag'] && $a['value'] === $b['value']) {
368								continue 2;
369							}
370						}
371
372						$diff_tags[] = $a;
373					}
374
375					$template['tags'] = $diff_tags;
376				}
377			}
378
379			if (array_key_exists('macros', $visible)) {
380				switch ($mass_update_macros) {
381					case ZBX_ACTION_ADD:
382						if ($macros) {
383							$update_existing = getRequest('macros_add', 0);
384
385							foreach ($macros as $macro) {
386								foreach ($template['macros'] as $template_macro) {
387									if ($macro['macro'] === $template_macro['macro']) {
388										if ($update_existing) {
389											$macro['hostmacroid'] = $template_macro['hostmacroid'];
390											$template_macros_update[] = $macro;
391										}
392
393										continue 2;
394									}
395								}
396
397								$macro['hostid'] = $template['templateid'];
398								$template_macros_add[] = $macro;
399							}
400						}
401						break;
402
403					case ZBX_ACTION_REPLACE: // In Macros its update.
404						if ($macros) {
405							$add_missing = getRequest('macros_update', 0);
406
407							foreach ($macros as $macro) {
408								foreach ($template['macros'] as $template_macro) {
409									if ($macro['macro'] === $template_macro['macro']) {
410										$macro['hostmacroid'] = $template_macro['hostmacroid'];
411										$template_macros_update[] = $macro;
412
413										continue 2;
414									}
415								}
416
417								if ($add_missing) {
418									$macro['hostid'] = $template['templateid'];
419									$template_macros_add[] = $macro;
420								}
421							}
422						}
423						break;
424
425					case ZBX_ACTION_REMOVE:
426						if ($macros) {
427							$except_selected = getRequest('macros_remove', 0);
428
429							$macro_names = array_column($macros, 'macro');
430
431							foreach ($template['macros'] as $template_macro) {
432								if ((!$except_selected && in_array($template_macro['macro'], $macro_names))
433										|| ($except_selected && !in_array($template_macro['macro'], $macro_names))) {
434									$template_macros_remove[] = $template_macro['hostmacroid'];
435								}
436							}
437						}
438						break;
439
440					case ZBX_ACTION_REMOVE_ALL:
441						if (!getRequest('macros_remove_all', 0)) {
442							throw new Exception();
443						}
444
445						$template['macros'] = [];
446						break;
447				}
448
449				if ($mass_update_macros != ZBX_ACTION_REMOVE_ALL) {
450					unset($template['macros']);
451				}
452			}
453
454			unset($template['parentTemplates']);
455
456			$template = $new_values + $template;
457		}
458		unset($template);
459
460		if (!API::Template()->update($templates)) {
461			throw new Exception();
462		}
463
464		/**
465		 * Macros must be updated separately, since calling API::UserMacro->replaceMacros() inside
466		 * API::Template->update() results in loss of secret macro values.
467		 */
468		if ($template_macros_remove) {
469			if (!API::UserMacro()->delete($template_macros_remove)) {
470				throw new Exception();
471			}
472		}
473
474		if ($template_macros_add) {
475			if (!API::UserMacro()->create($template_macros_add)) {
476				throw new Exception();
477			}
478		}
479
480		if ($template_macros_update) {
481			if (!API::UserMacro()->update($template_macros_update)) {
482				throw new Exception();
483			}
484		}
485
486		DBend(true);
487
488		uncheckTableRows();
489		show_message(_('Templates updated'));
490
491		unset($_REQUEST['masssave'], $_REQUEST['form'], $_REQUEST['templates']);
492	}
493	catch (Exception $e) {
494		DBend(false);
495		show_error_message(_('Cannot update templates'));
496	}
497}
498elseif (hasRequest('add') || hasRequest('update')) {
499	try {
500		DBstart();
501
502		$templateId = getRequest('templateid', 0);
503		$cloneTemplateId = 0;
504
505		if (getRequest('form') === 'full_clone') {
506			$cloneTemplateId = $templateId;
507			$templateId = 0;
508		}
509
510		if ($templateId == 0) {
511			$messageSuccess = _('Template added');
512			$messageFailed = _('Cannot add template');
513		}
514		else {
515			$messageSuccess = _('Template updated');
516			$messageFailed = _('Cannot update template');
517		}
518
519		// Add new group.
520		$groups = getRequest('groups', []);
521		$new_groups = [];
522
523		foreach ($groups as $idx => $group) {
524			if (is_array($group) && array_key_exists('new', $group)) {
525				$new_groups[] = ['name' => $group['new']];
526				unset($groups[$idx]);
527			}
528		}
529
530		if ($new_groups) {
531			$new_groupid = API::HostGroup()->create($new_groups);
532
533			if (!$new_groupid) {
534				throw new Exception();
535			}
536
537			$groups = array_merge($groups, $new_groupid['groupids']);
538		}
539
540		// Linked templates.
541		$templates = [];
542
543		foreach (array_merge(getRequest('templates', []), getRequest('add_templates', [])) as $templateid) {
544			$templates[] = ['templateid' => $templateid];
545		}
546
547		$templatesClear = getRequest('clear_templates', []);
548		$templatesClear = zbx_toObject($templatesClear, 'templateid');
549		$templateName = getRequest('template_name', '');
550
551		// create / update template
552		$template = [
553			'host' => $templateName,
554			'name' => getRequest('visiblename', ''),
555			'groups' => zbx_toObject($groups, 'groupid'),
556			'templates' => $templates,
557			'macros' => $macros,
558			'tags' => $tags,
559			'description' => getRequest('description', '')
560		];
561
562		if ($templateId == 0) {
563			$result = API::Template()->create($template);
564
565			if ($result) {
566				$templateId = reset($result['templateids']);
567			}
568			else {
569				throw new Exception();
570			}
571		}
572		else {
573			$template['templateid'] = $templateId;
574			$template['templates_clear'] = $templatesClear;
575
576			$result = API::Template()->update($template);
577
578			if ($result) {
579				add_audit_ext(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_TEMPLATE, $templateId, $templateName, 'hosts', null, null);
580			}
581			else {
582				throw new Exception();
583			}
584		}
585
586		// full clone
587		if ($cloneTemplateId != 0 && getRequest('form') === 'full_clone') {
588			if (!copyApplications($cloneTemplateId, $templateId)) {
589				throw new Exception();
590			}
591
592			/*
593			 * First copy web scenarios with web items, so that later regular items can use web item as their master
594			 * item.
595			 */
596			if (!copyHttpTests($cloneTemplateId, $templateId)) {
597				throw new Exception();
598			}
599
600			if (!copyItems($cloneTemplateId, $templateId)) {
601				throw new Exception();
602			}
603
604			// copy triggers
605			$dbTriggers = API::Trigger()->get([
606				'output' => ['triggerid'],
607				'hostids' => $cloneTemplateId,
608				'inherited' => false
609			]);
610
611			if ($dbTriggers) {
612				$result &= copyTriggersToHosts(zbx_objectValues($dbTriggers, 'triggerid'),
613						$templateId, $cloneTemplateId);
614
615				if (!$result) {
616					throw new Exception();
617				}
618			}
619
620			// copy graphs
621			$dbGraphs = API::Graph()->get([
622				'output' => ['graphid'],
623				'hostids' => $cloneTemplateId,
624				'inherited' => false
625			]);
626
627			foreach ($dbGraphs as $dbGraph) {
628				copyGraphToHost($dbGraph['graphid'], $templateId);
629			}
630
631			// copy discovery rules
632			$dbDiscoveryRules = API::DiscoveryRule()->get([
633				'output' => ['itemid'],
634				'hostids' => $cloneTemplateId,
635				'inherited' => false
636			]);
637
638			if ($dbDiscoveryRules) {
639				$result &= API::DiscoveryRule()->copy([
640					'discoveryids' => zbx_objectValues($dbDiscoveryRules, 'itemid'),
641					'hostids' => [$templateId]
642				]);
643
644				if (!$result) {
645					throw new Exception();
646				}
647			}
648
649			// copy template screens
650			$dbTemplateScreens = API::TemplateScreen()->get([
651				'noInheritance' => true,
652				'output' => ['screenid'],
653				'templateids' => $cloneTemplateId,
654				'preservekeys' => true
655			]);
656
657			if ($dbTemplateScreens) {
658				$result &= API::TemplateScreen()->copy([
659					'screenIds' => zbx_objectValues($dbTemplateScreens, 'screenid'),
660					'templateIds' => $templateId
661				]);
662
663				if (!$result) {
664					throw new Exception();
665				}
666			}
667		}
668
669		unset($_REQUEST['form'], $_REQUEST['templateid']);
670		$result = DBend($result);
671
672		if ($result) {
673			uncheckTableRows();
674		}
675		show_messages($result, $messageSuccess, $messageFailed);
676	}
677	catch (Exception $e) {
678		DBend(false);
679		show_error_message($messageFailed);
680	}
681}
682elseif (hasRequest('templateid') && hasRequest('delete')) {
683	DBstart();
684
685	$result = API::Template()->massUpdate([
686		'templates' => zbx_toObject($_REQUEST['templateid'], 'templateid'),
687		'hosts' => []
688	]);
689	if ($result) {
690		$result = API::Template()->delete([getRequest('templateid')]);
691	}
692
693	$result = DBend($result);
694
695	if ($result) {
696		unset($_REQUEST['form'], $_REQUEST['templateid']);
697		uncheckTableRows();
698	}
699	unset($_REQUEST['delete']);
700	show_messages($result, _('Template deleted'), _('Cannot delete template'));
701}
702elseif (hasRequest('templateid') && hasRequest('delete_and_clear')) {
703	DBstart();
704
705	$result = API::Template()->delete([getRequest('templateid')]);
706
707	$result = DBend($result);
708
709	if ($result) {
710		unset($_REQUEST['form'], $_REQUEST['templateid']);
711		uncheckTableRows();
712	}
713	unset($_REQUEST['delete']);
714	show_messages($result, _('Template deleted'), _('Cannot delete template'));
715}
716elseif (hasRequest('templates') && hasRequest('action') && str_in_array(getRequest('action'), ['template.massdelete', 'template.massdeleteclear'])) {
717	$templates = getRequest('templates');
718
719	DBstart();
720
721	$result = true;
722
723	if (getRequest('action') === 'template.massdelete') {
724		$result = API::Template()->massUpdate([
725			'templates' => zbx_toObject($templates, 'templateid'),
726			'hosts' => []
727		]);
728	}
729
730	if ($result) {
731		$result = API::Template()->delete($templates);
732	}
733
734	$result = DBend($result);
735
736	if ($result) {
737		uncheckTableRows();
738	}
739	else {
740		$templateids = API::Template()->get([
741			'output' => [],
742			'templateids' => $templates,
743			'editable' => true
744		]);
745		uncheckTableRows(null, zbx_objectValues($templateids, 'templateid'));
746	}
747	show_messages($result, _('Template deleted'), _('Cannot delete template'));
748}
749
750/*
751 * Display
752 */
753if (hasRequest('templates') && (getRequest('action') === 'template.massupdateform' || hasRequest('masssave'))) {
754	$data = [
755		'templates' => getRequest('templates', []),
756		'visible' => getRequest('visible', []),
757		'mass_action_tpls' => getRequest('mass_action_tpls'),
758		'mass_clear_tpls' => getRequest('mass_clear_tpls'),
759		'groups' => getRequest('groups', []),
760		'mass_update_groups' => getRequest('mass_update_groups', ZBX_ACTION_ADD),
761		'tags' => $tags,
762		'mass_update_tags' => getRequest('mass_update_tags', ZBX_ACTION_ADD),
763		'macros' => $macros,
764		'macros_checkbox' => [
765			ZBX_ACTION_ADD => getRequest('macros_add', '0'),
766			ZBX_ACTION_REPLACE => getRequest('macros_update', '0'),
767			ZBX_ACTION_REMOVE => getRequest('macros_remove', '0'),
768			ZBX_ACTION_REMOVE_ALL => getRequest('macros_remove_all', '0')
769		],
770		'macros_visible' => getRequest('mass_update_macros', ZBX_ACTION_ADD),
771		'description' => getRequest('description'),
772		'linked_templates' => getRequest('linked_templates', [])
773	];
774
775	// sort templates
776	natsort($data['linked_templates']);
777
778	if (!$data['tags']) {
779		$data['tags'][] = ['tag' => '', 'value' => ''];
780	}
781
782	// get templates data
783	$data['linked_templates'] = $data['linked_templates']
784		? CArrayHelper::renameObjectsKeys(API::Template()->get([
785			'output' => ['templateid', 'name'],
786			'templateids' => $data['linked_templates']
787		]), ['templateid' => 'id'])
788		: [];
789
790	$data['groups'] = $data['groups']
791		? CArrayHelper::renameObjectsKeys(API::HostGroup()->get([
792			'output' => ['groupid', 'name'],
793			'groupids' => $data['groups'],
794			'editable' => true
795		]), ['groupid' => 'id'])
796		: [];
797
798	if (!$data['macros']) {
799		$data['macros'] = [['macro' => '', 'type' => ZBX_MACRO_TYPE_TEXT, 'value' => '', 'description' => '']];
800	}
801
802	$view = new CView('configuration.template.massupdate', $data);
803}
804elseif (hasRequest('form')) {
805	$data = [
806		'form' => getRequest('form'),
807		'templateid' => getRequest('templateid', 0),
808		'linked_templates' => [],
809		'add_templates' => [],
810		'original_templates' => [],
811		'tags' => $tags,
812		'show_inherited_macros' => getRequest('show_inherited_macros', 0),
813		'readonly' => false,
814		'macros' => $macros
815	];
816
817	if ($data['templateid'] != 0) {
818		$dbTemplates = API::Template()->get([
819			'output' => API_OUTPUT_EXTEND,
820			'selectGroups' => API_OUTPUT_EXTEND,
821			'selectParentTemplates' => ['templateid', 'name'],
822			'selectMacros' => API_OUTPUT_EXTEND,
823			'selectTags' => ['tag', 'value'],
824			'templateids' => $data['templateid']
825		]);
826		$data['dbTemplate'] = reset($dbTemplates);
827
828		foreach ($data['dbTemplate']['parentTemplates'] as $parentTemplate) {
829			$data['original_templates'][$parentTemplate['templateid']] = $parentTemplate['templateid'];
830		}
831
832		if (!hasRequest('form_refresh')) {
833			$data['tags'] = $data['dbTemplate']['tags'];
834			$data['macros'] = $data['dbTemplate']['macros'];
835		}
836	}
837
838	// description
839	$data['description'] = ($data['templateid'] != 0 && !hasRequest('form_refresh'))
840		? $data['dbTemplate']['description']
841		: getRequest('description', '');
842
843	// tags
844	if (!$data['tags']) {
845		$data['tags'][] = ['tag' => '', 'value' => ''];
846	}
847	else {
848		CArrayHelper::sort($data['tags'], ['tag', 'value']);
849	}
850
851	// Add already linked and new templates.
852	$templates = [];
853	$request_linked_templates = getRequest('templates', hasRequest('form_refresh') ? [] : $data['original_templates']);
854	$request_add_templates = getRequest('add_templates', []);
855
856	if ($request_linked_templates || $request_add_templates) {
857		$templates = API::Template()->get([
858			'output' => ['templateid', 'name'],
859			'templateids' => array_merge($request_linked_templates, $request_add_templates),
860			'preservekeys' => true
861		]);
862
863		$data['linked_templates'] = array_intersect_key($templates, array_flip($request_linked_templates));
864		CArrayHelper::sort($data['linked_templates'], ['name']);
865
866		$data['add_templates'] = array_intersect_key($templates, array_flip($request_add_templates));
867
868		foreach ($data['add_templates'] as &$template) {
869			$template = CArrayHelper::renameKeys($template, ['templateid' => 'id']);
870		}
871		unset($template);
872	}
873
874	$data['writable_templates'] = API::Template()->get([
875		'output' => ['templateid'],
876		'templateids' => array_keys($data['linked_templates']),
877		'editable' => true,
878		'preservekeys' => true
879	]);
880
881	// Add inherited macros to template macros.
882	if ($data['show_inherited_macros']) {
883		$data['macros'] = mergeInheritedMacros($data['macros'], getInheritedMacros(array_keys($templates)));
884	}
885
886	// Sort only after inherited macros are added. Otherwise the list will look chaotic.
887	$data['macros'] = array_values(order_macros($data['macros'], 'macro'));
888
889	// The empty inputs will not be shown if there are inherited macros, for example.
890	if (!$data['macros']) {
891		$macro = ['macro' => '', 'value' => '', 'description' => '', 'type' => ZBX_MACRO_TYPE_TEXT];
892
893		if ($data['show_inherited_macros']) {
894			$macro['inherited_type'] = ZBX_PROPERTY_OWN;
895		}
896
897		$data['macros'][] = $macro;
898	}
899
900	if (!hasRequest('form_refresh')) {
901		if ($data['templateid'] != 0) {
902			$groups = zbx_objectValues($data['dbTemplate']['groups'], 'groupid');
903		}
904		else {
905			$groups = getRequest('groupids', []);
906		}
907	}
908	else {
909		$groups = getRequest('groups', []);
910	}
911
912	$groupids = [];
913
914	foreach ($groups as $group) {
915		if (is_array($group) && array_key_exists('new', $group)) {
916			continue;
917		}
918
919		$groupids[] = $group;
920	}
921
922	// Groups with R and RW permissions.
923	$groups_all = $groupids
924		? API::HostGroup()->get([
925			'output' => ['name'],
926			'groupids' => $groupids,
927			'preservekeys' => true
928		])
929		: [];
930
931	// Groups with RW permissions.
932	$groups_rw = $groupids && (CWebUser::getType() != USER_TYPE_SUPER_ADMIN)
933		? API::HostGroup()->get([
934			'output' => [],
935			'groupids' => $groupids,
936			'editable' => true,
937			'preservekeys' => true
938		])
939		: [];
940
941	$data['groups_ms'] = [];
942
943	// Prepare data for multiselect.
944	foreach ($groups as $group) {
945		if (is_array($group) && array_key_exists('new', $group)) {
946			$data['groups_ms'][] = [
947				'id' => $group['new'],
948				'name' => $group['new'].' ('._x('new', 'new element in multiselect').')',
949				'isNew' => true
950			];
951		}
952		elseif (array_key_exists($group, $groups_all)) {
953			$data['groups_ms'][] = [
954				'id' => $group,
955				'name' => $groups_all[$group]['name'],
956				'disabled' => (CWebUser::getType() != USER_TYPE_SUPER_ADMIN) && !array_key_exists($group, $groups_rw)
957			];
958		}
959	}
960	CArrayHelper::sort($data['groups_ms'], ['name']);
961
962	// This data is used in common.template.edit.js.php.
963	$data['macros_tab'] = [
964		'linked_templates' => array_map('strval', array_keys($data['linked_templates'])),
965		'add_templates' => array_map('strval', array_keys($data['add_templates']))
966	];
967
968	$view = new CView('configuration.template.edit', $data);
969}
970else {
971	$sortField = getRequest('sort', CProfile::get('web.'.$page['file'].'.sort', 'name'));
972	$sortOrder = getRequest('sortorder', CProfile::get('web.'.$page['file'].'.sortorder', ZBX_SORT_UP));
973
974	CProfile::update('web.'.$page['file'].'.sort', $sortField, PROFILE_TYPE_STR);
975	CProfile::update('web.'.$page['file'].'.sortorder', $sortOrder, PROFILE_TYPE_STR);
976
977	// filter
978	if (hasRequest('filter_set')) {
979		CProfile::update('web.templates.filter_name', getRequest('filter_name', ''), PROFILE_TYPE_STR);
980		CProfile::updateArray('web.templates.filter_templates', getRequest('filter_templates', []), PROFILE_TYPE_ID);
981		CProfile::updateArray('web.templates.filter_groups', getRequest('filter_groups', []), PROFILE_TYPE_ID);
982		CProfile::update('web.templates.filter.evaltype', getRequest('filter_evaltype', TAG_EVAL_TYPE_AND_OR),
983			PROFILE_TYPE_INT
984		);
985
986		$filter_tags = ['tags' => [], 'values' => [], 'operators' => []];
987		foreach (getRequest('filter_tags', []) as $filter_tag) {
988			if ($filter_tag['tag'] === '' && $filter_tag['value'] === '') {
989				continue;
990			}
991
992			$filter_tags['tags'][] = $filter_tag['tag'];
993			$filter_tags['values'][] = $filter_tag['value'];
994			$filter_tags['operators'][] = $filter_tag['operator'];
995		}
996		CProfile::updateArray('web.templates.filter.tags.tag', $filter_tags['tags'], PROFILE_TYPE_STR);
997		CProfile::updateArray('web.templates.filter.tags.value', $filter_tags['values'], PROFILE_TYPE_STR);
998		CProfile::updateArray('web.templates.filter.tags.operator', $filter_tags['operators'], PROFILE_TYPE_INT);
999	}
1000	elseif (hasRequest('filter_rst')) {
1001		CProfile::delete('web.templates.filter_name');
1002		CProfile::deleteIdx('web.templates.filter_templates');
1003		CProfile::deleteIdx('web.templates.filter_groups');
1004		CProfile::delete('web.templates.filter.evaltype');
1005		CProfile::deleteIdx('web.templates.filter.tags.tag');
1006		CProfile::deleteIdx('web.templates.filter.tags.value');
1007		CProfile::deleteIdx('web.templates.filter.tags.operator');
1008	}
1009
1010	$filter = [
1011		'name' => CProfile::get('web.templates.filter_name', ''),
1012		'templates' => CProfile::getArray('web.templates.filter_templates', null),
1013		'groups' => CProfile::getArray('web.templates.filter_groups', null),
1014		'evaltype' => CProfile::get('web.templates.filter.evaltype', TAG_EVAL_TYPE_AND_OR),
1015		'tags' => []
1016	];
1017
1018	foreach (CProfile::getArray('web.templates.filter.tags.tag', []) as $i => $tag) {
1019		$filter['tags'][] = [
1020			'tag' => $tag,
1021			'value' => CProfile::get('web.templates.filter.tags.value', null, $i),
1022			'operator' => CProfile::get('web.templates.filter.tags.operator', null, $i)
1023		];
1024	}
1025
1026	$config = select_config();
1027
1028	$filter['templates'] = $filter['templates']
1029		? CArrayHelper::renameObjectsKeys(API::Template()->get([
1030			'output' => ['templateid', 'name'],
1031			'templateids' => $filter['templates'],
1032			'preservekeys' => true
1033		]), ['templateid' => 'id'])
1034		: [];
1035
1036	// Get host groups.
1037	$filter['groups'] = $filter['groups']
1038		? CArrayHelper::renameObjectsKeys(API::HostGroup()->get([
1039			'output' => ['groupid', 'name'],
1040			'groupids' => $filter['groups'],
1041			'editable' => true,
1042			'preservekeys' => true
1043		]), ['groupid' => 'id'])
1044		: [];
1045
1046	$filter_groupids = $filter['groups'] ? array_keys($filter['groups']) : null;
1047	if ($filter_groupids) {
1048		$filter_groupids = getSubGroups($filter_groupids);
1049	}
1050
1051	// Select templates.
1052	$templates = API::Template()->get([
1053		'output' => ['templateid', $sortField],
1054		'evaltype' => $filter['evaltype'],
1055		'tags' => $filter['tags'],
1056		'search' => [
1057			'name' => ($filter['name'] === '') ? null : $filter['name']
1058		],
1059		'parentTemplateids' => $filter['templates'] ? array_keys($filter['templates']) : null,
1060		'groupids' => $filter_groupids,
1061		'editable' => true,
1062		'sortfield' => $sortField,
1063		'limit' => $config['search_limit'] + 1
1064	]);
1065
1066	order_result($templates, $sortField, $sortOrder);
1067
1068	// pager
1069	if (hasRequest('page')) {
1070		$page_num = getRequest('page');
1071	}
1072	elseif (isRequestMethod('get') && !hasRequest('cancel')) {
1073		$page_num = 1;
1074	}
1075	else {
1076		$page_num = CPagerHelper::loadPage($page['file']);
1077	}
1078
1079	CPagerHelper::savePage($page['file'], $page_num);
1080
1081	$paging = CPagerHelper::paginate($page_num, $templates, $sortOrder, new CUrl('templates.php'));
1082
1083	$templates = API::Template()->get([
1084		'output' => ['templateid', 'name'],
1085		'selectHosts' => ['hostid'],
1086		'selectTemplates' => ['templateid', 'name'],
1087		'selectParentTemplates' => ['templateid', 'name'],
1088		'selectItems' => API_OUTPUT_COUNT,
1089		'selectTriggers' => API_OUTPUT_COUNT,
1090		'selectGraphs' => API_OUTPUT_COUNT,
1091		'selectApplications' => API_OUTPUT_COUNT,
1092		'selectDiscoveries' => API_OUTPUT_COUNT,
1093		'selectScreens' => API_OUTPUT_COUNT,
1094		'selectHttpTests' => API_OUTPUT_COUNT,
1095		'selectTags' => ['tag', 'value'],
1096		'templateids' => zbx_objectValues($templates, 'templateid'),
1097		'editable' => true,
1098		'preservekeys' => true
1099	]);
1100
1101	order_result($templates, $sortField, $sortOrder);
1102
1103	// Select editable templates:
1104	$linked_templateids = [];
1105	$editable_templates = [];
1106	$linked_hostids = [];
1107	$editable_hosts = [];
1108	foreach ($templates as &$template) {
1109		order_result($template['templates'], 'name');
1110		order_result($template['parentTemplates'], 'name');
1111
1112		$linked_templateids += array_flip(array_column($template['parentTemplates'], 'templateid'));
1113		$linked_templateids += array_flip(array_column($template['templates'], 'templateid'));
1114
1115		$template['hosts'] = array_flip(array_column($template['hosts'], 'hostid'));
1116		$linked_hostids += $template['hosts'];
1117	}
1118	unset($template);
1119
1120	if ($linked_templateids) {
1121		$editable_templates = API::Template()->get([
1122			'output' => ['templateid'],
1123			'templateids' => array_keys($linked_templateids),
1124			'editable' => true,
1125			'preservekeys' => true
1126		]);
1127	}
1128	if ($linked_hostids) {
1129		$editable_hosts = API::Host()->get([
1130			'output' => ['hostid'],
1131			'hostids' => array_keys($linked_hostids),
1132			'editable' => true,
1133			'preservekeys' => true
1134		]);
1135	}
1136
1137	$data = [
1138		'templates' => $templates,
1139		'paging' => $paging,
1140		'page' => $page_num,
1141		'filter' => $filter,
1142		'sortField' => $sortField,
1143		'sortOrder' => $sortOrder,
1144		'config' => [
1145			'max_in_table' => $config['max_in_table']
1146		],
1147		'editable_templates' => $editable_templates,
1148		'editable_hosts' => $editable_hosts,
1149		'profileIdx' => 'web.templates.filter',
1150		'active_tab' => CProfile::get('web.templates.filter.active', 1),
1151		'tags' => makeTags($templates, true, 'templateid', ZBX_TAG_COUNT_DEFAULT, $filter['tags'])
1152	];
1153
1154	$view = new CView('configuration.template.list', $data);
1155}
1156
1157echo $view->getOutput();
1158
1159require_once dirname(__FILE__).'/include/page_footer.php';
1160