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
22abstract class CHostBase extends CApiService {
23
24	protected $tableName = 'hosts';
25	protected $tableAlias = 'h';
26
27	/**
28	 * Links the templates to the given hosts.
29	 *
30	 * @param array $templateIds
31	 * @param array $targetIds		an array of host IDs to link the templates to
32	 *
33	 * @return array 	an array of added hosts_templates rows, with 'hostid' and 'templateid' set for each row
34	 */
35	protected function link(array $templateIds, array $targetIds) {
36		if (empty($templateIds)) {
37			return;
38		}
39
40		// permission check
41		$templateIds = array_unique($templateIds);
42
43		$count = API::Template()->get([
44			'countOutput' => true,
45			'templateids' => $templateIds
46		]);
47
48		if ($count != count($templateIds)) {
49			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
50		}
51
52		// check if someone passed duplicate templates in the same query
53		$templateIdDuplicates = zbx_arrayFindDuplicates($templateIds);
54		if (!zbx_empty($templateIdDuplicates)) {
55			$duplicatesFound = [];
56			foreach ($templateIdDuplicates as $value => $count) {
57				$duplicatesFound[] = _s('template ID "%1$s" is passed %2$s times', $value, $count);
58			}
59			self::exception(
60				ZBX_API_ERROR_PARAMETERS,
61				_s('Cannot pass duplicate template IDs for the linkage: %s.', implode(', ', $duplicatesFound))
62			);
63		}
64
65		// get DB templates which exists in all targets
66		$res = DBselect('SELECT * FROM hosts_templates WHERE '.dbConditionInt('hostid', $targetIds));
67		$mas = [];
68		while ($row = DBfetch($res)) {
69			if (!isset($mas[$row['templateid']])) {
70				$mas[$row['templateid']] = [];
71			}
72			$mas[$row['templateid']][$row['hostid']] = 1;
73		}
74		$targetIdCount = count($targetIds);
75		$commonDBTemplateIds = [];
76		foreach ($mas as $templateId => $targetList) {
77			if (count($targetList) == $targetIdCount) {
78				$commonDBTemplateIds[] = $templateId;
79			}
80		}
81
82		// check if there are any template with triggers which depends on triggers in templates which will be not linked
83		$commonTemplateIds = array_unique(array_merge($commonDBTemplateIds, $templateIds));
84		foreach ($templateIds as $templateid) {
85			$triggerids = [];
86			$dbTriggers = get_triggers_by_hostid($templateid);
87			while ($trigger = DBfetch($dbTriggers)) {
88				$triggerids[$trigger['triggerid']] = $trigger['triggerid'];
89			}
90
91			$sql = 'SELECT DISTINCT h.host'.
92				' FROM trigger_depends td,functions f,items i,hosts h'.
93				' WHERE ('.
94				dbConditionInt('td.triggerid_down', $triggerids).
95				' AND f.triggerid=td.triggerid_up'.
96				' )'.
97				' AND i.itemid=f.itemid'.
98				' AND h.hostid=i.hostid'.
99				' AND '.dbConditionInt('h.hostid', $commonTemplateIds, true).
100				' AND h.status='.HOST_STATUS_TEMPLATE;
101			if ($dbDepHost = DBfetch(DBselect($sql))) {
102				$tmpTpls = API::Template()->get([
103					'templateids' => $templateid,
104					'output'=> API_OUTPUT_EXTEND
105				]);
106				$tmpTpl = reset($tmpTpls);
107
108				self::exception(ZBX_API_ERROR_PARAMETERS,
109					_s('Trigger in template "%1$s" has dependency with trigger in template "%2$s".', $tmpTpl['host'], $dbDepHost['host']));
110			}
111		}
112
113		$res = DBselect(
114			'SELECT ht.hostid,ht.templateid'.
115				' FROM hosts_templates ht'.
116				' WHERE '.dbConditionInt('ht.hostid', $targetIds).
117				' AND '.dbConditionInt('ht.templateid', $templateIds)
118		);
119		$linked = [];
120		while ($row = DBfetch($res)) {
121			$linked[$row['templateid']][$row['hostid']] = true;
122		}
123
124		// add template linkages, if problems rollback later
125		$hostsLinkageInserts = [];
126
127		foreach ($templateIds as $templateid) {
128			$linked_targets = array_key_exists($templateid, $linked) ? $linked[$templateid] : [];
129
130			foreach ($targetIds as $targetid) {
131				if (array_key_exists($targetid, $linked_targets)) {
132					continue;
133				}
134
135				$hostsLinkageInserts[] = ['hostid' => $targetid, 'templateid' => $templateid];
136			}
137		}
138
139		if ($hostsLinkageInserts) {
140			self::checkCircularLinkage($hostsLinkageInserts);
141			self::checkDoubleLinkage($hostsLinkageInserts);
142
143			DB::insertBatch('hosts_templates', $hostsLinkageInserts);
144		}
145
146		// check if all trigger templates are linked to host.
147		// we try to find template that is not linked to hosts ($targetids)
148		// and exists trigger which reference that template and template from ($templateids)
149		$sql = 'SELECT DISTINCT h.host'.
150			' FROM functions f,items i,triggers t,hosts h'.
151			' WHERE f.itemid=i.itemid'.
152			' AND f.triggerid=t.triggerid'.
153			' AND i.hostid=h.hostid'.
154			' AND h.status='.HOST_STATUS_TEMPLATE.
155			' AND NOT EXISTS (SELECT 1 FROM hosts_templates ht WHERE ht.templateid=i.hostid AND '.dbConditionInt('ht.hostid', $targetIds).')'.
156			' AND EXISTS (SELECT 1 FROM functions ff,items ii WHERE ff.itemid=ii.itemid AND ff.triggerid=t.triggerid AND '.dbConditionInt('ii.hostid', $templateIds). ')';
157		if ($dbNotLinkedTpl = DBfetch(DBSelect($sql, 1))) {
158			self::exception(ZBX_API_ERROR_PARAMETERS,
159				_s('Trigger has items from template "%1$s" that is not linked to host.', $dbNotLinkedTpl['host'])
160			);
161		}
162
163		return $hostsLinkageInserts;
164	}
165
166	protected function unlink($templateids, $targetids = null) {
167		$cond = ['templateid' => $templateids];
168		if (!is_null($targetids)) {
169			$cond['hostid'] =  $targetids;
170		}
171		DB::delete('hosts_templates', $cond);
172
173		if (!is_null($targetids)) {
174			$hosts = API::Host()->get([
175				'hostids' => $targetids,
176				'output' => ['hostid', 'host'],
177				'nopermissions' => true
178			]);
179		}
180		else{
181			$hosts = API::Host()->get([
182				'templateids' => $templateids,
183				'output' => ['hostid', 'host'],
184				'nopermissions' => true
185			]);
186		}
187
188		if (!empty($hosts)) {
189			$templates = API::Template()->get([
190				'templateids' => $templateids,
191				'output' => ['hostid', 'host'],
192				'nopermissions' => true
193			]);
194
195			$hosts = implode(', ', zbx_objectValues($hosts, 'host'));
196			$templates = implode(', ', zbx_objectValues($templates, 'host'));
197
198			info(_s('Templates "%1$s" unlinked from hosts "%2$s".', $templates, $hosts));
199		}
200	}
201
202	/**
203	 * Searches for circular linkages for specific template.
204	 *
205	 * @param array  $links[<templateid>][<hostid>]  The list of linkages.
206	 * @param string $templateid                     ID of the template to check circular linkages.
207	 * @param array  $hostids[<hostid>]
208	 *
209	 * @throws APIException if circular linkage is found.
210	 */
211	private static function checkTemplateCircularLinkage(array $links, $templateid, array $hostids) {
212		if (array_key_exists($templateid, $hostids)) {
213			self::exception(ZBX_API_ERROR_PARAMETERS, _('Circular template linkage is not allowed.'));
214		}
215
216		foreach ($hostids as $hostid => $foo) {
217			if (array_key_exists($hostid, $links)) {
218				self::checkTemplateCircularLinkage($links, $templateid, $links[$hostid]);
219			}
220		}
221	}
222
223	/**
224	 * Searches for circular linkages.
225	 *
226	 * @param array  $host_templates
227	 * @param string $host_templates[]['templateid']
228	 * @param string $host_templates[]['hostid']
229	 */
230	private static function checkCircularLinkage(array $host_templates) {
231		$links = [];
232
233		foreach ($host_templates as $host_template) {
234			$links[$host_template['templateid']][$host_template['hostid']] = true;
235		}
236
237		$templateids = array_keys($links);
238		$_templateids = $templateids;
239
240		do {
241			$result = DBselect(
242				'SELECT ht.templateid,ht.hostid'.
243				' FROM hosts_templates ht'.
244				' WHERE '.dbConditionId('ht.hostid', $_templateids)
245			);
246
247			$_templateids = [];
248
249			while ($row = DBfetch($result)) {
250				if (!array_key_exists($row['templateid'], $links)) {
251					$_templateids[$row['templateid']] = true;
252				}
253
254				$links[$row['templateid']][$row['hostid']] = true;
255			}
256
257			$_templateids = array_keys($_templateids);
258		}
259		while ($_templateids);
260
261		foreach ($templateids as $templateid) {
262			self::checkTemplateCircularLinkage($links, $templateid, $links[$templateid]);
263		}
264	}
265
266	/**
267	 * Searches for double linkages.
268	 *
269	 * @param array  $links[<hostid>][<templateid>]  The list of linked template IDs by host ID.
270	 * @param string $hostid
271	 *
272	 * @throws APIException if double linkage is found.
273	 *
274	 * @return array  An array of the linked templates for the selected host.
275	 */
276	private static function checkTemplateDoubleLinkage(array $links, $hostid) {
277		$templateids = $links[$hostid];
278
279		foreach ($links[$hostid] as $templateid => $foo) {
280			if (array_key_exists($templateid, $links)) {
281				$_templateids = self::checkTemplateDoubleLinkage($links, $templateid);
282
283				if (array_intersect_key($templateids, $_templateids)) {
284					self::exception(ZBX_API_ERROR_PARAMETERS,
285						_('Template cannot be linked to another template more than once even through other templates.')
286					);
287				}
288
289				$templateids += $_templateids;
290			}
291		}
292
293		return $templateids;
294	}
295
296	/**
297	 * Searches for double linkages.
298	 *
299	 * @param array  $host_templates
300	 * @param string $host_templates[]['templateid']
301	 * @param string $host_templates[]['hostid']
302	 */
303	private static function checkDoubleLinkage(array $host_templates) {
304		$links = [];
305		$templateids = [];
306		$hostids = [];
307
308		foreach ($host_templates as $host_template) {
309			$links[$host_template['hostid']][$host_template['templateid']] = true;
310			$templateids[$host_template['templateid']] = true;
311			$hostids[$host_template['hostid']] = true;
312		}
313
314		$_hostids = array_keys($hostids);
315
316		do {
317			$result = DBselect(
318				'SELECT ht.hostid'.
319				' FROM hosts_templates ht'.
320				' WHERE '.dbConditionId('ht.templateid', $_hostids)
321			);
322
323			$_hostids = [];
324
325			while ($row = DBfetch($result)) {
326				if (!array_key_exists($row['hostid'], $hostids)) {
327					$_hostids[$row['hostid']] = true;
328				}
329
330				$hostids[$row['hostid']] = true;
331			}
332
333			$_hostids = array_keys($_hostids);
334		}
335		while ($_hostids);
336
337		$_templateids = array_keys($templateids + $hostids);
338		$templateids = [];
339
340		do {
341			$result = DBselect(
342				'SELECT ht.templateid,ht.hostid'.
343				' FROM hosts_templates ht'.
344				' WHERE '.dbConditionId('hostid', $_templateids)
345			);
346
347			$_templateids = [];
348
349			while ($row = DBfetch($result)) {
350				if (!array_key_exists($row['templateid'], $templateids)) {
351					$_templateids[$row['templateid']] = true;
352				}
353
354				$templateids[$row['templateid']] = true;
355				$links[$row['hostid']][$row['templateid']] = true;
356			}
357
358			$_templateids = array_keys($_templateids);
359		}
360		while ($_templateids);
361
362		foreach ($hostids as $hostid => $foo) {
363			self::checkTemplateDoubleLinkage($links, $hostid);
364		}
365	}
366}
367