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 * Get ipmi auth type label by it's number.
24 *
25 * @param null|int $type
26 *
27 * @return array|string
28 */
29function ipmiAuthTypes($type = null) {
30	$types = [
31		IPMI_AUTHTYPE_DEFAULT => _('Default'),
32		IPMI_AUTHTYPE_NONE => _('None'),
33		IPMI_AUTHTYPE_MD2 => _('MD2'),
34		IPMI_AUTHTYPE_MD5 => _('MD5'),
35		IPMI_AUTHTYPE_STRAIGHT => _('Straight'),
36		IPMI_AUTHTYPE_OEM => _('OEM'),
37		IPMI_AUTHTYPE_RMCP_PLUS => _('RMCP+')
38	];
39
40	if (is_null($type)) {
41		return $types;
42	}
43	elseif (isset($types[$type])) {
44		return $types[$type];
45	}
46	else {
47		return _('Unknown');
48	}
49}
50
51/**
52 * Get ipmi auth privilege label by it's number.
53 *
54 * @param null|int $type
55 *
56 * @return array|string
57 */
58function ipmiPrivileges($type = null) {
59	$types = [
60		IPMI_PRIVILEGE_CALLBACK => _('Callback'),
61		IPMI_PRIVILEGE_USER => _('User'),
62		IPMI_PRIVILEGE_OPERATOR => _('Operator'),
63		IPMI_PRIVILEGE_ADMIN => _('Admin'),
64		IPMI_PRIVILEGE_OEM => _('OEM')
65	];
66
67	if (is_null($type)) {
68		return $types;
69	}
70	elseif (isset($types[$type])) {
71		return $types[$type];
72	}
73	else {
74		return _('Unknown');
75	}
76}
77
78/**
79 * Get info about what host inventory fields we have, their numbers and names.
80 * Example of usage:
81 *      $inventories = getHostInventories();
82 *      echo $inventories[1]['db_field']; // host_networks
83 *      echo $inventories[1]['title']; // Host networks
84 *      echo $inventories[1]['nr']; // 1
85 *
86 * @param bool $orderedByTitle	whether an array should be ordered by field title, not by number
87 *
88 * @return array
89 */
90function getHostInventories($orderedByTitle = false) {
91	/*
92	 * WARNING! Before modifying this array, make sure changes are synced with C
93	 * C analog is located in function DBget_inventory_field() in src/libs/zbxdbhigh/db.c
94	 */
95	$inventoryFields = [
96		1 => [
97			'nr' => 1,
98			'db_field' => 'type',
99			'title' => _('Type')
100		],
101		2 => [
102			'nr' => 2,
103			'db_field' => 'type_full',
104			'title' => _('Type (Full details)')
105		],
106		3 => [
107			'nr' => 3,
108			'db_field' => 'name',
109			'title' => _('Name')
110		],
111		4 => [
112			'nr' => 4,
113			'db_field' => 'alias',
114			'title' => _('Alias')
115		],
116		5 => [
117			'nr' => 5,
118			'db_field' => 'os',
119			'title' => _('OS')
120		],
121		6 => [
122			'nr' => 6,
123			'db_field' => 'os_full',
124			'title' => _('OS (Full details)')
125		],
126		7 => [
127			'nr' => 7,
128			'db_field' => 'os_short',
129			'title' => _('OS (Short)')
130		],
131		8 => [
132			'nr' => 8,
133			'db_field' => 'serialno_a',
134			'title' => _('Serial number A')
135		],
136		9 => [
137			'nr' => 9,
138			'db_field' => 'serialno_b',
139			'title' => _('Serial number B')
140		],
141		10 => [
142			'nr' => 10,
143			'db_field' => 'tag',
144			'title' => _('Tag')
145		],
146		11 => [
147			'nr' => 11,
148			'db_field' => 'asset_tag',
149			'title' => _('Asset tag')
150		],
151		12 => [
152			'nr' => 12,
153			'db_field' => 'macaddress_a',
154			'title' => _('MAC address A')
155		],
156		13 => [
157			'nr' => 13,
158			'db_field' => 'macaddress_b',
159			'title' => _('MAC address B')
160		],
161		14 => [
162			'nr' => 14,
163			'db_field' => 'hardware',
164			'title' => _('Hardware')
165		],
166		15 => [
167			'nr' => 15,
168			'db_field' => 'hardware_full',
169			'title' => _('Hardware (Full details)')
170		],
171		16 => [
172			'nr' => 16,
173			'db_field' => 'software',
174			'title' => _('Software')
175		],
176		17 => [
177			'nr' => 17,
178			'db_field' => 'software_full',
179			'title' => _('Software (Full details)')
180		],
181		18 => [
182			'nr' => 18,
183			'db_field' => 'software_app_a',
184			'title' => _('Software application A')
185		],
186		19 => [
187			'nr' => 19,
188			'db_field' => 'software_app_b',
189			'title' => _('Software application B')
190		],
191		20 => [
192			'nr' => 20,
193			'db_field' => 'software_app_c',
194			'title' => _('Software application C')
195		],
196		21 => [
197			'nr' => 21,
198			'db_field' => 'software_app_d',
199			'title' => _('Software application D')
200		],
201		22 => [
202			'nr' => 22,
203			'db_field' => 'software_app_e',
204			'title' => _('Software application E')
205		],
206		23 => [
207			'nr' => 23,
208			'db_field' => 'contact',
209			'title' => _('Contact')
210		],
211		24 => [
212			'nr' => 24,
213			'db_field' => 'location',
214			'title' => _('Location')
215		],
216		25 => [
217			'nr' => 25,
218			'db_field' => 'location_lat',
219			'title' => _('Location latitude')
220		],
221		26 => [
222			'nr' => 26,
223			'db_field' => 'location_lon',
224			'title' => _('Location longitude')
225		],
226		27 => [
227			'nr' => 27,
228			'db_field' => 'notes',
229			'title' => _('Notes')
230		],
231		28 => [
232			'nr' => 28,
233			'db_field' => 'chassis',
234			'title' => _('Chassis')
235		],
236		29 => [
237			'nr' => 29,
238			'db_field' => 'model',
239			'title' => _('Model')
240		],
241		30 => [
242			'nr' => 30,
243			'db_field' => 'hw_arch',
244			'title' => _('HW architecture')
245		],
246		31 => [
247			'nr' => 31,
248			'db_field' => 'vendor',
249			'title' => _('Vendor')
250		],
251		32 => [
252			'nr' => 32,
253			'db_field' => 'contract_number',
254			'title' => _('Contract number')
255		],
256		33 => [
257			'nr' => 33,
258			'db_field' => 'installer_name',
259			'title' => _('Installer name')
260		],
261		34 => [
262			'nr' => 34,
263			'db_field' => 'deployment_status',
264			'title' => _('Deployment status')
265		],
266		35 => [
267			'nr' => 35,
268			'db_field' => 'url_a',
269			'title' => _('URL A')
270		],
271		36 => [
272			'nr' => 36,
273			'db_field' => 'url_b',
274			'title' => _('URL B')
275		],
276		37 => [
277			'nr' => 37,
278			'db_field' => 'url_c',
279			'title' => _('URL C')
280		],
281		38 => [
282			'nr' => 38,
283			'db_field' => 'host_networks',
284			'title' => _('Host networks')
285		],
286		39 => [
287			'nr' => 39,
288			'db_field' => 'host_netmask',
289			'title' => _('Host subnet mask')
290		],
291		40 => [
292			'nr' => 40,
293			'db_field' => 'host_router',
294			'title' => _('Host router')
295		],
296		41 => [
297			'nr' => 41,
298			'db_field' => 'oob_ip',
299			'title' => _('OOB IP address')
300		],
301		42 => [
302			'nr' => 42,
303			'db_field' => 'oob_netmask',
304			'title' => _('OOB subnet mask')
305		],
306		43 => [
307			'nr' => 43,
308			'db_field' => 'oob_router',
309			'title' => _('OOB router')
310		],
311		44 => [
312			'nr' => 44,
313			'db_field' => 'date_hw_purchase',
314			'title' => _('Date HW purchased')
315		],
316		45 => [
317			'nr' => 45,
318			'db_field' => 'date_hw_install',
319			'title' => _('Date HW installed')
320		],
321		46 => [
322			'nr' => 46,
323			'db_field' => 'date_hw_expiry',
324			'title' => _('Date HW maintenance expires')
325		],
326		47 => [
327			'nr' => 47,
328			'db_field' => 'date_hw_decomm',
329			'title' => _('Date HW decommissioned')
330		],
331		48 => [
332			'nr' => 48,
333			'db_field' => 'site_address_a',
334			'title' => _('Site address A')
335		],
336		49 => [
337			'nr' => 49,
338			'db_field' => 'site_address_b',
339			'title' => _('Site address B')
340		],
341		50 => [
342			'nr' => 50,
343			'db_field' => 'site_address_c',
344			'title' => _('Site address C')
345		],
346		51 => [
347			'nr' => 51,
348			'db_field' => 'site_city',
349			'title' => _('Site city')
350		],
351		52 => [
352			'nr' => 52,
353			'db_field' => 'site_state',
354			'title' => _('Site state / province')
355		],
356		53 => [
357			'nr' => 53,
358			'db_field' => 'site_country',
359			'title' => _('Site country')
360		],
361		54 => [
362			'nr' => 54,
363			'db_field' => 'site_zip',
364			'title' => _('Site ZIP / postal')
365		],
366		55 => [
367			'nr' => 55,
368			'db_field' => 'site_rack',
369			'title' => _('Site rack location')
370		],
371		56 => [
372			'nr' => 56,
373			'db_field' => 'site_notes',
374			'title' => _('Site notes')
375		],
376		57 => [
377			'nr' => 57,
378			'db_field' => 'poc_1_name',
379			'title' => _('Primary POC name')
380		],
381		58 => [
382			'nr' => 58,
383			'db_field' => 'poc_1_email',
384			'title' => _('Primary POC email')
385		],
386		59 => [
387			'nr' => 59,
388			'db_field' => 'poc_1_phone_a',
389			'title' => _('Primary POC phone A')
390		],
391		60 => [
392			'nr' => 60,
393			'db_field' => 'poc_1_phone_b',
394			'title' => _('Primary POC phone B')
395		],
396		61 => [
397			'nr' => 61,
398			'db_field' => 'poc_1_cell',
399			'title' => _('Primary POC cell')
400		],
401		62 => [
402			'nr' => 62,
403			'db_field' => 'poc_1_screen',
404			'title' => _('Primary POC screen name')
405		],
406		63 => [
407			'nr' => 63,
408			'db_field' => 'poc_1_notes',
409			'title' => _('Primary POC notes')
410		],
411		64 => [
412			'nr' => 64,
413			'db_field' => 'poc_2_name',
414			'title' => _('Secondary POC name')
415		],
416		65 => [
417			'nr' => 65,
418			'db_field' => 'poc_2_email',
419			'title' => _('Secondary POC email')
420		],
421		66 => [
422			'nr' => 66,
423			'db_field' => 'poc_2_phone_a',
424			'title' => _('Secondary POC phone A')
425		],
426		67 => [
427			'nr' => 67,
428			'db_field' => 'poc_2_phone_b',
429			'title' => _('Secondary POC phone B')
430		],
431		68 => [
432			'nr' => 68,
433			'db_field' => 'poc_2_cell',
434			'title' => _('Secondary POC cell')
435		],
436		69 => [
437			'nr' => 69,
438			'db_field' => 'poc_2_screen',
439			'title' => _('Secondary POC screen name')
440		],
441		70 => [
442			'nr' => 70,
443			'db_field' => 'poc_2_notes',
444			'title' => _('Secondary POC notes')
445		]
446	];
447
448	// array is ordered by number by default, should we change that and order by title?
449	if ($orderedByTitle) {
450		function sortInventoriesByTitle($a, $b) {
451			return strcmp($a['title'], $b['title']);
452		}
453		uasort($inventoryFields, 'sortInventoriesByTitle');
454	}
455
456	return $inventoryFields;
457}
458
459function hostInterfaceTypeNumToName($type) {
460	switch ($type) {
461		case INTERFACE_TYPE_AGENT:
462			$name = _('agent');
463			break;
464		case INTERFACE_TYPE_SNMP:
465			$name = _('SNMP');
466			break;
467		case INTERFACE_TYPE_JMX:
468			$name = _('JMX');
469			break;
470		case INTERFACE_TYPE_IPMI:
471			$name = _('IPMI');
472			break;
473		default:
474			throw new Exception(_('Unknown interface type.'));
475	}
476
477	return $name;
478}
479
480function get_hostgroup_by_groupid($groupid) {
481	$groups = DBfetch(DBselect('SELECT g.* FROM groups g WHERE g.groupid='.zbx_dbstr($groupid)));
482
483	if ($groups) {
484		return $groups;
485	}
486
487	error(_s('No host groups with groupid "%s".', $groupid));
488
489	return false;
490}
491
492function get_host_by_itemid($itemids) {
493	$res_array = is_array($itemids);
494	zbx_value2array($itemids);
495	$result = false;
496	$hosts = [];
497
498	$db_hostsItems = DBselect(
499		'SELECT i.itemid,h.*'.
500		' FROM hosts h,items i'.
501		' WHERE i.hostid=h.hostid'.
502			' AND '.dbConditionInt('i.itemid', $itemids)
503	);
504	while ($hostItem = DBfetch($db_hostsItems)) {
505		$result = true;
506		$hosts[$hostItem['itemid']] = $hostItem;
507	}
508
509	if (!$res_array) {
510		foreach ($hosts as $itemid => $host) {
511			$result = $host;
512		}
513	}
514	elseif ($result) {
515		$result = $hosts;
516		unset($hosts);
517	}
518
519	return $result;
520}
521
522function get_host_by_hostid($hostid, $no_error_message = 0) {
523	$row = DBfetch(DBselect('SELECT h.* FROM hosts h WHERE h.hostid='.zbx_dbstr($hostid)));
524
525	if ($row) {
526		return $row;
527	}
528
529	if ($no_error_message == 0) {
530		error(_s('No host with hostid "%s".', $hostid));
531	}
532
533	return false;
534}
535
536function updateHostStatus($hostids, $status) {
537	zbx_value2array($hostids);
538
539	$hostIds = [];
540	$oldStatus = ($status == HOST_STATUS_MONITORED ? HOST_STATUS_NOT_MONITORED : HOST_STATUS_MONITORED);
541
542	$db_hosts = DBselect(
543		'SELECT h.hostid,h.host,h.status'.
544		' FROM hosts h'.
545		' WHERE '.dbConditionInt('h.hostid', $hostids).
546			' AND h.status='.zbx_dbstr($oldStatus)
547	);
548	while ($host = DBfetch($db_hosts)) {
549		$hostIds[] = $host['hostid'];
550
551		$host_new = $host;
552		$host_new['status'] = $status;
553		add_audit_ext(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_HOST, $host['hostid'], $host['host'], 'hosts', $host, $host_new);
554		info(_s('Updated status of host "%1$s".', $host['host']));
555	}
556
557	return DB::update('hosts', [
558		'values' => ['status' => $status],
559		'where' => ['hostid' => $hostIds]
560	]);
561}
562
563function get_application_by_applicationid($applicationid, $no_error_message = 0) {
564	$row = DBfetch(DBselect('SELECT a.* FROM applications a WHERE a.applicationid='.zbx_dbstr($applicationid)));
565
566	if ($row) {
567		return $row;
568	}
569
570	if ($no_error_message == 0) {
571		error(_s('No application with ID "%1$s".', $applicationid));
572	}
573
574	return false;
575}
576
577/**
578 * Returns the farthest application ancestor for each given application.
579 *
580 * @param array $applicationIds
581 * @param array $templateApplicationIds		array with parent application IDs as keys and arrays of child application
582 * 											IDs as values
583 *
584 * @return array	an array with child IDs as keys and arrays of ancestor IDs as values
585 */
586function getApplicationSourceParentIds(array $applicationIds, array $templateApplicationIds = []) {
587	$query = DBSelect(
588		'SELECT at.applicationid,at.templateid'.
589		' FROM application_template at'.
590		' WHERE '.dbConditionInt('at.applicationid', $applicationIds)
591	);
592
593	$applicationIds = [];
594	$unsetApplicationIds = [];
595	while ($applicationTemplate = DBfetch($query)) {
596		// check if we already have an application inherited from the current application
597		// if we do - copy all of its child applications to the parent template
598		if (isset($templateApplicationIds[$applicationTemplate['applicationid']])) {
599			$templateApplicationIds[$applicationTemplate['templateid']] = $templateApplicationIds[$applicationTemplate['applicationid']];
600			$unsetApplicationIds[$applicationTemplate['applicationid']] = $applicationTemplate['applicationid'];
601		}
602		// if no - just add the application
603		else {
604			$templateApplicationIds[$applicationTemplate['templateid']][] = $applicationTemplate['applicationid'];
605		}
606		$applicationIds[$applicationTemplate['applicationid']] = $applicationTemplate['templateid'];
607	}
608
609	// unset children of all applications that we found a new parent for
610	foreach ($unsetApplicationIds as $applicationId) {
611		unset($templateApplicationIds[$applicationId]);
612	}
613
614	// continue while we still have new applications to check
615	if ($applicationIds) {
616		return getApplicationSourceParentIds($applicationIds, $templateApplicationIds);
617	}
618	else {
619		// return an inverse hash with application IDs as keys and arrays of parent application IDs as values
620		$result = [];
621		foreach ($templateApplicationIds as $templateId => $applicationIds) {
622			foreach ($applicationIds as $applicationId) {
623				$result[$applicationId][] = $templateId;
624			}
625		}
626
627		return $result;
628	}
629}
630
631/**
632 * Returns the farthest host prototype ancestor for each given host prototype.
633 *
634 * @param array $hostPrototypeIds
635 * @param array $templateHostPrototypeIds	array with parent host prototype IDs as keys and arrays of child host
636 * 											prototype IDs as values
637 *
638 * @return array	an array of child ID - ancestor ID pairs
639 */
640function getHostPrototypeSourceParentIds(array $hostPrototypeIds, array $templateHostPrototypeIds = []) {
641	$query = DBSelect(
642		'SELECT h.hostid,h.templateid'.
643		' FROM hosts h'.
644		' WHERE '.dbConditionInt('h.hostid', $hostPrototypeIds).
645			' AND h.templateid>0'
646	);
647
648	$hostPrototypeIds = [];
649	while ($hostPrototype = DBfetch($query)) {
650		// check if we already have host prototype inherited from the current host prototype
651		// if we do - move all of its child prototypes to the parent template
652		if (isset($templateHostPrototypeIds[$hostPrototype['hostid']])) {
653			$templateHostPrototypeIds[$hostPrototype['templateid']] = $templateHostPrototypeIds[$hostPrototype['hostid']];
654			unset($templateHostPrototypeIds[$hostPrototype['hostid']]);
655		}
656		// if no - just add the prototype
657		else {
658			$templateHostPrototypeIds[$hostPrototype['templateid']][] = $hostPrototype['hostid'];
659			$hostPrototypeIds[] = $hostPrototype['templateid'];
660		}
661	}
662
663	// continue while we still have new host prototypes to check
664	if ($hostPrototypeIds) {
665		return getHostPrototypeSourceParentIds($hostPrototypeIds, $templateHostPrototypeIds);
666	}
667	else {
668		// return an inverse hash with prototype IDs as keys and parent prototype IDs as values
669		$result = [];
670		foreach ($templateHostPrototypeIds as $templateId => $hostIds) {
671			foreach ($hostIds as $hostId) {
672				$result[$hostId] = $templateId;
673			}
674		}
675
676		return $result;
677	}
678}
679
680/**
681 * Get host ids of hosts which $groupids can be unlinked from.
682 * if $hostids is passed, function will check only these hosts.
683 *
684 * @param array $groupids
685 * @param array $hostids
686 *
687 * @return array
688 */
689function getUnlinkableHostIds(array $groupIds, array $hostIds) {
690	if (!$hostIds) {
691		return [];
692	}
693
694	$dbResult = DBselect(
695		'SELECT hg.hostid'.
696		' FROM hosts_groups hg'.
697		' WHERE '.dbConditionInt('hg.groupid', $groupIds, true).
698			' AND '.dbConditionInt('hg.hostid', $hostIds).
699		' GROUP BY hg.hostid'
700	);
701
702	$unlinkableHostIds = [];
703	while ($dbRow = DBfetch($dbResult)) {
704		$unlinkableHostIds[] = $dbRow['hostid'];
705	}
706
707	return $unlinkableHostIds;
708}
709
710function getDeletableHostGroupIds(array $groupIds) {
711	// selecting the list of hosts linked to the host groups
712	$dbResult = DBselect(
713		'SELECT hg.hostid'.
714		' FROM hosts_groups hg'.
715		' WHERE '.dbConditionInt('hg.groupid', $groupIds)
716	);
717
718	$linkedHostIds = [];
719	while ($dbRow = DBfetch($dbResult)) {
720		$linkedHostIds[] = $dbRow['hostid'];
721	}
722
723	// the list of hosts which can be unlinked from the host groups
724	$hostIds = getUnlinkableHostIds($groupIds, $linkedHostIds);
725
726	$dbResult = DBselect(
727		'SELECT g.groupid'.
728		' FROM groups g'.
729		' WHERE g.internal='.ZBX_NOT_INTERNAL_GROUP.
730			' AND '.dbConditionInt('g.groupid', $groupIds).
731			' AND NOT EXISTS ('.
732				'SELECT NULL'.
733				' FROM hosts_groups hg'.
734				' WHERE g.groupid=hg.groupid'.
735					($hostIds ? ' AND '.dbConditionInt('hg.hostid', $hostIds, true) : '').
736			')'
737	);
738
739	$deletableGroupIds = [];
740	while ($dbRow = DBfetch($dbResult)) {
741		$deletableGroupIds[$dbRow['groupid']] = $dbRow['groupid'];
742	}
743
744	return $deletableGroupIds;
745}
746
747function isTemplate($hostId) {
748	$dbHost = DBfetch(DBselect('SELECT h.status FROM hosts h WHERE h.hostid='.zbx_dbstr($hostId)));
749
750	return ($dbHost && $dbHost['status'] == HOST_STATUS_TEMPLATE);
751}
752
753/**
754 * Get list of inherited macros by host ids.
755 *
756 * Returns an array like:
757 *   array(
758 *       '{$MACRO}' => array(
759 *           'macro' => '{$MACRO}',
760 *           'template' => array(                   <- optional
761 *               'value' => 'template-level value'
762 *               'templateid' => 10001,
763 *               'name' => 'Template OS Linux'
764 *           ),
765 *           'global' => array(                     <- optional
766 *               'value' => 'global-level value'
767 *           )
768 *       )
769 *   )
770 *
771 * @param array $hostids
772 *
773 * @return array
774 */
775function getInheritedMacros(array $hostids) {
776	$user_macro_parser = new CUserMacroParser();
777
778	$all_macros = [];
779	$global_macros = [];
780
781	$db_global_macros = API::UserMacro()->get([
782		'output' => ['macro', 'value'],
783		'globalmacro' => true
784	]);
785
786	foreach ($db_global_macros as $db_global_macro) {
787		$all_macros[$db_global_macro['macro']] = true;
788		$global_macros[$db_global_macro['macro']] = $db_global_macro['value'];
789	}
790
791	// hostid => array('name' => name, 'macros' => array(macro => value), 'templateids' => array(templateid))
792	$hosts = [];
793
794	$templateids = $hostids;
795
796	do {
797		$db_templates = API::Template()->get([
798			'output' => ['name'],
799			'selectParentTemplates' => ['templateid'],
800			'selectMacros' => ['macro', 'value'],
801			'templateids' => $templateids,
802			'preservekeys' => true
803		]);
804
805		$templateids = [];
806
807		foreach ($db_templates as $hostid => $db_template) {
808			$hosts[$hostid] = [
809				'templateid' => $hostid,
810				'name' => $db_template['name'],
811				'templateids' => zbx_objectValues($db_template['parentTemplates'], 'templateid'),
812				'macros' => []
813			];
814
815			/*
816			 * Global macros are overwritten by template macros and template macros are overwritten by host macros.
817			 * Macros with contexts require additional checking for contexts, since {$MACRO:} is the same as
818			 * {$MACRO:""}.
819			 */
820			foreach ($db_template['macros'] as $dbMacro) {
821				if (array_key_exists($dbMacro['macro'], $all_macros)) {
822					$hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value'];
823					$all_macros[$dbMacro['macro']] = true;
824				}
825				else {
826					$user_macro_parser->parse($dbMacro['macro']);
827					$tpl_macro = $user_macro_parser->getMacro();
828					$tpl_context = $user_macro_parser->getContext();
829
830					if ($tpl_context === null) {
831						$hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value'];
832						$all_macros[$dbMacro['macro']] = true;
833					}
834					else {
835						$match_found = false;
836
837						foreach ($global_macros as $global_macro => $global_value) {
838							$user_macro_parser->parse($global_macro);
839							$gbl_macro = $user_macro_parser->getMacro();
840							$gbl_context = $user_macro_parser->getContext();
841
842							if ($tpl_macro === $gbl_macro && $tpl_context === $gbl_context) {
843								$match_found = true;
844
845								unset($global_macros[$global_macro], $hosts[$hostid][$global_macro],
846									$all_macros[$global_macro]
847								);
848
849								$hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value'];
850								$all_macros[$dbMacro['macro']] = true;
851								$global_macros[$dbMacro['macro']] = $global_value;
852
853								break;
854							}
855						}
856
857						if (!$match_found) {
858							$hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value'];
859							$all_macros[$dbMacro['macro']] = true;
860						}
861					}
862				}
863			}
864		}
865
866		foreach ($db_templates as $db_template) {
867			// only unprocessed templates will be populated
868			foreach ($db_template['parentTemplates'] as $template) {
869				if (!array_key_exists($template['templateid'], $hosts)) {
870					$templateids[$template['templateid']] = $template['templateid'];
871				}
872			}
873		}
874	} while ($templateids);
875
876	$all_macros = array_keys($all_macros);
877	$all_templates = [];
878	$inherited_macros = [];
879
880	// resolving
881	foreach ($all_macros as $macro) {
882		$inherited_macro = ['macro' => $macro];
883
884		if (array_key_exists($macro, $global_macros)) {
885			$inherited_macro['global'] = [
886				'value' => $global_macros[$macro]
887			];
888		}
889
890		$templateids = $hostids;
891
892		do {
893			natsort($templateids);
894
895			foreach ($templateids as $templateid) {
896				if (array_key_exists($templateid, $hosts) && array_key_exists($macro, $hosts[$templateid]['macros'])) {
897					$inherited_macro['template'] = [
898						'value' => $hosts[$templateid]['macros'][$macro],
899						'templateid' => $hosts[$templateid]['templateid'],
900						'name' => $hosts[$templateid]['name'],
901						'rights' => PERM_READ
902					];
903
904					if (!array_key_exists($hosts[$templateid]['templateid'], $all_templates)) {
905						$all_templates[$hosts[$templateid]['templateid']] = [];
906					}
907					$all_templates[$hosts[$templateid]['templateid']][] = &$inherited_macro['template'];
908
909					break 2;
910				}
911			}
912
913			$parent_templateids = [];
914
915			foreach ($templateids as $templateid) {
916				if (array_key_exists($templateid, $hosts)) {
917					foreach ($hosts[$templateid]['templateids'] as $templateid) {
918						$parent_templateids[$templateid] = $templateid;
919					}
920				}
921			}
922
923			$templateids = $parent_templateids;
924		} while ($templateids);
925
926		$inherited_macros[$macro] = $inherited_macro;
927	}
928
929	// checking permissions
930	if ($all_templates) {
931		$db_templates = API::Template()->get([
932			'output' => ['templateid'],
933			'templateids' => array_keys($all_templates),
934			'editable' => true
935		]);
936
937		foreach ($db_templates as $db_template) {
938			foreach ($all_templates[$db_template['templateid']] as &$template) {
939				$template['rights'] = PERM_READ_WRITE;
940			}
941			unset($template);
942		}
943	}
944
945	return $inherited_macros;
946}
947
948/**
949 * Merge list of inherited and host-level macros.
950 *
951 * Returns an array like:
952 *   array(
953 *       '{$MACRO}' => array(
954 *           'macro' => '{$MACRO}',
955 *           'type' => 0x03,						<- MACRO_TYPE_INHERITED, MACRO_TYPE_HOSTMACRO or MACRO_TYPE_BOTH
956 *           'value' => 'effective value',
957 *           'hostmacroid' => 7532,                 <- optional
958 *           'template' => array(                   <- optional
959 *               'value' => 'template-level value'
960 *               'templateid' => 10001,
961 *               'name' => 'Template OS Linux'
962 *           ),
963 *           'global' => array(                     <- optional
964 *               'value' => 'global-level value'
965 *           )
966 *       )
967 *   )
968 *
969 * @param array $host_macros		the list of host macros
970 * @param array $inherited_macros	the list of inherited macros (the output of the getInheritedMacros() function)
971 *
972 * @return array
973 */
974function mergeInheritedMacros(array $host_macros, array $inherited_macros) {
975	$user_macro_parser = new CUserMacroParser();
976
977	foreach ($inherited_macros as &$inherited_macro) {
978		$inherited_macro['type'] = MACRO_TYPE_INHERITED;
979		$inherited_macro['value'] = array_key_exists('template', $inherited_macro)
980			? $inherited_macro['template']['value']
981			: $inherited_macro['global']['value'];
982	}
983	unset($inherited_macro);
984
985	/*
986	 * Global macros and template macros are overwritten by host macros. Macros with contexts require additional
987	 * checking for contexts, since {$MACRO:} is the same as {$MACRO:""}.
988	 */
989	foreach ($host_macros as &$host_macro) {
990		if (array_key_exists($host_macro['macro'], $inherited_macros)) {
991			$host_macro = array_merge($inherited_macros[$host_macro['macro']], $host_macro);
992			unset($inherited_macros[$host_macro['macro']]);
993		}
994		else {
995			/*
996			 * Cannot use array dereferencing because "$host_macro['macro']" may contain invalid macros
997			 * which results in empty array.
998			 */
999			if ($user_macro_parser->parse($host_macro['macro']) == CParser::PARSE_SUCCESS) {
1000				$hst_macro = $user_macro_parser->getMacro();
1001				$hst_context = $user_macro_parser->getContext();
1002
1003				if ($hst_context === null) {
1004					$host_macro['type'] = 0x00;
1005				}
1006				else {
1007					$match_found = false;
1008
1009					foreach ($inherited_macros as $inherited_macro => $inherited_values) {
1010						// Safe to use array dereferencing since these values come from database.
1011						$user_macro_parser->parse($inherited_macro);
1012						$inh_macro = $user_macro_parser->getMacro();
1013						$inh_context = $user_macro_parser->getContext();
1014
1015						if ($hst_macro === $inh_macro && $hst_context === $inh_context) {
1016							$match_found = true;
1017
1018							$host_macro = array_merge($inherited_macros[$inherited_macro], $host_macro);
1019							unset($inherited_macros[$inherited_macro]);
1020
1021							break;
1022						}
1023					}
1024
1025					if (!$match_found) {
1026						$host_macro['type'] = 0x00;
1027					}
1028				}
1029			}
1030			else {
1031				$host_macro['type'] = 0x00;
1032			}
1033		}
1034
1035		$host_macro['type'] |= MACRO_TYPE_HOSTMACRO;
1036	}
1037	unset($host_macro);
1038
1039	foreach ($inherited_macros as $inherited_macro) {
1040		$host_macros[] = $inherited_macro;
1041	}
1042
1043	return $host_macros;
1044}
1045
1046/**
1047 * Remove inherited macros data.
1048 *
1049 * @param array $macros
1050 *
1051 * @return array
1052 */
1053function cleanInheritedMacros(array $macros) {
1054	foreach ($macros as $idx => $macro) {
1055		if (array_key_exists('type', $macro) && !($macro['type'] & MACRO_TYPE_HOSTMACRO)) {
1056			unset($macros[$idx]);
1057		}
1058		else {
1059			unset($macros[$idx]['type'], $macros[$idx]['inherited']);
1060		}
1061	}
1062
1063	return $macros;
1064}
1065
1066/**
1067 * An array of available host inventory modes.
1068 *
1069 * @return array
1070 */
1071function getHostInventoryModes() {
1072	return [
1073		HOST_INVENTORY_DISABLED => _('Disabled'),
1074		HOST_INVENTORY_MANUAL => _('Manual'),
1075		HOST_INVENTORY_AUTOMATIC => _('Automatic')
1076	];
1077}
1078