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/items.inc.php';
25require_once dirname(__FILE__).'/include/forms.inc.php';
26
27$page['title'] = _('Configuration of host prototypes');
28$page['file'] = 'host_prototypes.php';
29$page['scripts'] = ['effects.js', 'class.cviewswitcher.js', 'multiselect.js'];
30
31require_once dirname(__FILE__).'/include/page_header.php';
32
33// VAR	TYPE	OPTIONAL	FLAGS	VALIDATION	EXCEPTION
34$fields = [
35	'hostid' =>					[T_ZBX_INT, O_NO,	P_SYS,	DB_ID,		'(isset({form}) && ({form} == "update"))'],
36	'parent_discoveryid' =>		[T_ZBX_INT, O_MAND, P_SYS,	DB_ID, null],
37	'host' =>		        	[T_ZBX_STR, O_OPT, null,		NOT_EMPTY,	'isset({add}) || isset({update})', _('Host name')],
38	'name' =>	            	[T_ZBX_STR, O_OPT, null,		null,		'isset({add}) || isset({update})'],
39	'status' =>		        	[T_ZBX_INT, O_OPT, null,		IN([HOST_STATUS_NOT_MONITORED, HOST_STATUS_MONITORED]), null],
40	'inventory_mode' =>			[T_ZBX_INT, O_OPT, null, IN([HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC]), null],
41	'templates' =>		    	[T_ZBX_STR, O_OPT, null, NOT_EMPTY,	null],
42	'add_template' =>			[T_ZBX_STR, O_OPT, null,		null,	null],
43	'add_templates' =>		    [T_ZBX_STR, O_OPT, null, NOT_EMPTY,	null],
44	'group_links' =>			[T_ZBX_STR, O_OPT, null, NOT_EMPTY,	null],
45	'group_prototypes' =>		[T_ZBX_STR, O_OPT, null, NOT_EMPTY,	null],
46	'unlink' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT,	null,		null],
47	'group_hostid' =>			[T_ZBX_INT, O_OPT, null,	DB_ID,		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('"hostprototype.massdelete","hostprototype.massdisable",'.
52										'"hostprototype.massenable"'
53									),
54									null
55								],
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	'delete' =>					[T_ZBX_STR, O_OPT, P_SYS|P_ACT, null,	null],
60	'cancel' =>					[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
61	'form' =>					[T_ZBX_STR, O_OPT, P_SYS,	null,		null],
62	'form_refresh' =>			[T_ZBX_INT, O_OPT, null,	null,		null],
63	// sort and sortorder
64	'sort' =>					[T_ZBX_STR, O_OPT, P_SYS, IN('"name","status"'),						null],
65	'sortorder' =>				[T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'),	null]
66];
67check_fields($fields);
68
69// permissions
70if (getRequest('parent_discoveryid')) {
71	$discoveryRule = API::DiscoveryRule()->get([
72		'itemids' => getRequest('parent_discoveryid'),
73		'output' => API_OUTPUT_EXTEND,
74		'selectHosts' => ['flags'],
75		'editable' => true
76	]);
77	$discoveryRule = reset($discoveryRule);
78	if (!$discoveryRule || $discoveryRule['hosts'][0]['flags'] == ZBX_FLAG_DISCOVERY_CREATED) {
79		access_deny();
80	}
81
82	if (getRequest('hostid')) {
83		$hostPrototype = API::HostPrototype()->get([
84			'hostids' => getRequest('hostid'),
85			'output' => API_OUTPUT_EXTEND,
86			'selectGroupLinks' => API_OUTPUT_EXTEND,
87			'selectGroupPrototypes' => API_OUTPUT_EXTEND,
88			'selectTemplates' => ['templateid', 'name'],
89			'selectParentHost' => ['hostid'],
90			'selectInventory' => ['inventory_mode'],
91			'editable' => true
92		]);
93		$hostPrototype = reset($hostPrototype);
94		if (!$hostPrototype) {
95			access_deny();
96		}
97	}
98}
99else {
100	access_deny();
101}
102
103/*
104 * Actions
105 */
106// add templates to the list
107if (getRequest('add_template')) {
108	foreach (getRequest('add_templates', []) as $templateId) {
109		$_REQUEST['templates'][$templateId] = $templateId;
110	}
111}
112// unlink templates
113elseif (getRequest('unlink')) {
114	foreach (getRequest('unlink') as $templateId => $value) {
115		unset($_REQUEST['templates'][$templateId]);
116	}
117}
118elseif (isset($_REQUEST['delete']) && isset($_REQUEST['hostid'])) {
119	DBstart();
120	$result = API::HostPrototype()->delete([getRequest('hostid')]);
121	$result = DBend($result);
122
123	if ($result) {
124		uncheckTableRows($discoveryRule['itemid']);
125	}
126	show_messages($result, _('Host prototype deleted'), _('Cannot delete host prototypes'));
127
128	unset($_REQUEST['hostid'], $_REQUEST['form']);
129}
130elseif (isset($_REQUEST['clone']) && isset($_REQUEST['hostid'])) {
131	unset($_REQUEST['hostid']);
132	if (hasRequest('group_prototypes')) {
133		foreach ($_REQUEST['group_prototypes'] as &$groupPrototype) {
134			unset($groupPrototype['group_prototypeid']);
135		}
136		unset($groupPrototype);
137	}
138	$_REQUEST['form'] = 'clone';
139}
140elseif (hasRequest('add') || hasRequest('update')) {
141	DBstart();
142
143	$newHostPrototype = [
144		'host' => getRequest('host', ''),
145		'name' => (getRequest('name', '') === '') ? getRequest('host', '') : getRequest('name', ''),
146		'status' => getRequest('status', HOST_STATUS_NOT_MONITORED),
147		'groupLinks' => [],
148		'groupPrototypes' => [],
149		'templates' => getRequest('templates', [])
150	];
151
152	// If 'inventory' is present, API requires 'inventory_mode' to have a value, but templated prototypes don't have it.
153	if (hasRequest('inventory_mode')) {
154		$newHostPrototype['inventory']['inventory_mode'] = getRequest('inventory_mode');
155	}
156
157	// API requires 'templateid' property.
158	if ($newHostPrototype['templates']) {
159		$newHostPrototype['templates'] = zbx_toObject($newHostPrototype['templates'], 'templateid');
160	}
161
162	// add custom group prototypes
163	foreach (getRequest('group_prototypes', []) as $groupPrototype) {
164		if (!$groupPrototype['group_prototypeid']) {
165			unset($groupPrototype['group_prototypeid']);
166		}
167
168		if ($groupPrototype['name'] !== '') {
169			$newHostPrototype['groupPrototypes'][] = $groupPrototype;
170		}
171	}
172
173	if (getRequest('hostid')) {
174		$newHostPrototype['hostid'] = getRequest('hostid');
175
176		if (!$hostPrototype['templateid']) {
177			// add group prototypes based on existing host groups
178			$groupPrototypesByGroupId = zbx_toHash($hostPrototype['groupLinks'], 'groupid');
179			unset($groupPrototypesByGroupId[0]);
180			foreach (getRequest('group_links', []) as $groupId) {
181				if (isset($groupPrototypesByGroupId[$groupId])) {
182					$newHostPrototype['groupLinks'][] = [
183						'groupid' => $groupPrototypesByGroupId[$groupId]['groupid'],
184						'group_prototypeid' => $groupPrototypesByGroupId[$groupId]['group_prototypeid']
185					];
186				}
187				else {
188					$newHostPrototype['groupLinks'][] = [
189						'groupid' => $groupId
190					];
191				}
192			}
193		}
194		else {
195			unset($newHostPrototype['groupPrototypes'], $newHostPrototype['groupLinks']);
196		}
197
198		$newHostPrototype = CArrayHelper::unsetEqualValues($newHostPrototype, $hostPrototype, ['hostid']);
199		$result = API::HostPrototype()->update($newHostPrototype);
200
201		show_messages($result, _('Host prototype updated'), _('Cannot update host prototype'));
202	}
203	else {
204		$newHostPrototype['ruleid'] = getRequest('parent_discoveryid');
205
206		// add group prototypes based on existing host groups
207		foreach (getRequest('group_links', []) as $groupId) {
208			$newHostPrototype['groupLinks'][] = [
209				'groupid' => $groupId
210			];
211		}
212
213		$result = API::HostPrototype()->create($newHostPrototype);
214
215		show_messages($result, _('Host prototype added'), _('Cannot add host prototype'));
216	}
217
218	$result = DBend($result);
219
220	if ($result) {
221		uncheckTableRows($discoveryRule['itemid']);
222		unset($_REQUEST['itemid'], $_REQUEST['form']);
223	}
224}
225// GO
226elseif (hasRequest('action') && str_in_array(getRequest('action'), ['hostprototype.massenable', 'hostprototype.massdisable']) && hasRequest('group_hostid')) {
227	$status = (getRequest('action') == 'hostprototype.massenable') ? HOST_STATUS_MONITORED : HOST_STATUS_NOT_MONITORED;
228	$update = [];
229
230	DBstart();
231	foreach ((array) getRequest('group_hostid') as $hostPrototypeId) {
232		$update[] = [
233			'hostid' => $hostPrototypeId,
234			'status' => $status
235		];
236	}
237
238	$result = API::HostPrototype()->update($update);
239	$result = DBend($result);
240
241	$updated = count($update);
242
243	$messageSuccess = _n('Host prototype updated', 'Host prototypes updated', $updated);
244	$messageFailed = _n('Cannot update host prototype', 'Cannot update host prototypes', $updated);
245
246	if ($result) {
247		uncheckTableRows($discoveryRule['itemid']);
248	}
249	show_messages($result, $messageSuccess, $messageFailed);
250}
251elseif (hasRequest('action') && getRequest('action') == 'hostprototype.massdelete' && getRequest('group_hostid')) {
252	DBstart();
253	$result = API::HostPrototype()->delete(getRequest('group_hostid'));
254	$result = DBend($result);
255
256	if ($result) {
257		uncheckTableRows($discoveryRule['itemid']);
258	}
259	show_messages($result, _('Host prototypes deleted'), _('Cannot delete host prototypes'));
260}
261
262if (hasRequest('action') && hasRequest('group_hostid') && !$result) {
263	$host_prototypes = API::HostPrototype()->get([
264		'output' => [],
265		'hostids' => getRequest('group_hostid'),
266		'editable' => true
267	]);
268	uncheckTableRows($discoveryRule['itemid'], zbx_objectValues($host_prototypes, 'hostid'));
269}
270
271$config = select_config();
272
273/*
274 * Display
275 */
276if (hasRequest('form')) {
277	$data = [
278		'discovery_rule' => $discoveryRule,
279		'host_prototype' => [
280			'hostid' => getRequest('hostid', 0),
281			'templateid' => getRequest('hostid') ? $hostPrototype['templateid'] : 0,
282			'host' => getRequest('host'),
283			'name' => getRequest('name'),
284			'status' => getRequest('status', HOST_STATUS_NOT_MONITORED),
285			'templates' => [],
286			'inventory' => [
287				'inventory_mode' => getRequest('inventory_mode', $config['default_inventory_mode'])
288			],
289			'groupPrototypes' => getRequest('group_prototypes', [])
290		],
291		'groups' => [],
292		'show_inherited_macros' => getRequest('show_inherited_macros', 0),
293		'templates' => []
294	];
295
296	// add already linked and new templates
297	$data['host_prototype']['templates'] = API::Template()->get([
298		'output' => ['templateid', 'name'],
299		'templateids' => getRequest('templates', [])
300	]);
301
302	// add parent host
303	$parentHost = API::Host()->get([
304		'output' => API_OUTPUT_EXTEND,
305		'selectGroups' => ['groupid', 'name'],
306		'selectInterfaces' => API_OUTPUT_EXTEND,
307		'selectMacros' => ['macro', 'value'],
308		'hostids' => $discoveryRule['hostid'],
309		'templated_hosts' => true
310	]);
311	$parentHost = reset($parentHost);
312	$data['parent_host'] = $parentHost;
313
314	if (getRequest('group_links')) {
315		$data['groups'] = API::HostGroup()->get([
316			'output' => ['groupid', 'name'],
317			'groupids' => getRequest('group_links'),
318			'editable' => true,
319			'preservekeys' => true
320		]);
321	}
322
323	if ($parentHost['proxy_hostid']) {
324		$proxy = API::Proxy()->get([
325			'output' => ['host', 'proxyid'],
326			'proxyids' => $parentHost['proxy_hostid'],
327			'limit' => 1
328		]);
329		$data['proxy'] = reset($proxy);
330	}
331
332	if (!hasRequest('form_refresh')) {
333		if ($data['host_prototype']['hostid'] != 0) {
334			// When opening existing host prototype, display all values from database.
335			$data['host_prototype'] = array_merge($data['host_prototype'], $hostPrototype);
336
337			if (!array_key_exists('inventory_mode', $data['host_prototype']['inventory'])) {
338				$data['host_prototype']['inventory']['inventory_mode'] = HOST_INVENTORY_DISABLED;
339			}
340
341			$groupids = zbx_objectValues($data['host_prototype']['groupLinks'], 'groupid');
342			$data['groups'] = API::HostGroup()->get([
343				'output' => ['groupid', 'name'],
344				'groupids' => $groupids,
345				'preservekeys' => true
346			]);
347
348			$n = 0;
349			foreach ($groupids as $groupid) {
350				if (!array_key_exists($groupid, $data['groups'])) {
351					$postfix = (++$n > 1) ? ' ('.$n.')' : '';
352					$data['groups'][$groupid] = [
353						'groupid' => $groupid,
354						'name' => _('Inaccessible group').$postfix,
355						'inaccessible' => true
356					];
357				}
358			}
359		}
360		else {
361			// Set default values for new host prototype.
362			$data['host_prototype']['status'] = HOST_STATUS_MONITORED;
363		}
364	}
365
366	$data['templates'] = makeHostPrototypeTemplatesHtml($data['host_prototype']['hostid'],
367		getHostPrototypeParentTemplates([$data['host_prototype']])
368	);
369
370	// Select writable templates
371	$templateids = zbx_objectValues($data['host_prototype']['templates'], 'templateid');
372	$data['host_prototype']['writable_templates'] = [];
373
374	if ($templateids) {
375		$data['host_prototype']['writable_templates'] = API::Template()->get([
376			'output' => ['templateid'],
377			'templateids' => $templateids,
378			'editable' => true,
379			'preservekeys' => true
380		]);
381	}
382
383	// order linked templates
384	CArrayHelper::sort($data['host_prototype']['templates'], ['name']);
385
386	// render view
387	$itemView = new CView('configuration.host.prototype.edit', $data);
388	$itemView->render();
389	$itemView->show();
390}
391else {
392	$sortField = getRequest('sort', CProfile::get('web.'.$page['file'].'.sort', 'name'));
393	$sortOrder = getRequest('sortorder', CProfile::get('web.'.$page['file'].'.sortorder', ZBX_SORT_UP));
394
395	CProfile::update('web.'.$page['file'].'.sort', $sortField, PROFILE_TYPE_STR);
396	CProfile::update('web.'.$page['file'].'.sortorder', $sortOrder, PROFILE_TYPE_STR);
397
398	$data = [
399		'form' => getRequest('form'),
400		'parent_discoveryid' => getRequest('parent_discoveryid'),
401		'discovery_rule' => $discoveryRule,
402		'sort' => $sortField,
403		'sortorder' => $sortOrder
404	];
405
406	// get items
407	$data['hostPrototypes'] = API::HostPrototype()->get([
408		'discoveryids' => $data['parent_discoveryid'],
409		'output' => API_OUTPUT_EXTEND,
410		'selectTemplates' => ['templateid', 'name'],
411		'editable' => true,
412		'sortfield' => $sortField,
413		'limit' => $config['search_limit'] + 1
414	]);
415
416	order_result($data['hostPrototypes'], $sortField, $sortOrder);
417
418	$url = (new CUrl('host_prototypes.php'))
419		->setArgument('parent_discoveryid', $data['parent_discoveryid']);
420
421	$data['paging'] = getPagingLine($data['hostPrototypes'], $sortOrder, $url);
422
423	$data['parent_templates'] = getHostPrototypeParentTemplates($data['hostPrototypes']);
424
425	// Fetch templates linked to the prototypes.
426	$templateids = [];
427	foreach ($data['hostPrototypes'] as $hostPrototype) {
428		$templateids = array_merge($templateids, zbx_objectValues($hostPrototype['templates'], 'templateid'));
429	}
430	$templateids = array_keys(array_flip($templateids));
431
432	$linkedTemplates = API::Template()->get([
433		'output' => ['templateid', 'name'],
434		'templateids' => $templateids,
435		'selectParentTemplates' => ['hostid', 'name']
436	]);
437	$data['linkedTemplates'] = zbx_toHash($linkedTemplates, 'templateid');
438
439	foreach ($data['linkedTemplates'] as $linked_template) {
440		foreach ($linked_template['parentTemplates'] as $parent_template) {
441			$templateids[] = $parent_template['templateid'];
442		}
443	}
444
445	// Select writable template IDs.
446	$data['writable_templates'] = [];
447
448	if ($templateids) {
449		$data['writable_templates'] = API::Template()->get([
450			'output' => ['templateid'],
451			'templateids' => $templateids,
452			'editable' => true,
453			'preservekeys' => true
454		]);
455	}
456
457	// render view
458	$itemView = new CView('configuration.host.prototype.list', $data);
459	$itemView->render();
460	$itemView->show();
461}
462
463require_once dirname(__FILE__).'/include/page_footer.php';
464