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
22class CMacrosResolverGeneral {
23
24	const PATTERN_HOST_INTERNAL = 'HOST\.HOST|HOSTNAME';
25	const PATTERN_MACRO_PARAM = '[1-9]?';
26
27	/**
28	 * Interface priorities.
29	 *
30	 * @var array
31	 */
32	protected $interfacePriorities = [
33		INTERFACE_TYPE_AGENT => 4,
34		INTERFACE_TYPE_SNMP => 3,
35		INTERFACE_TYPE_JMX => 2,
36		INTERFACE_TYPE_IPMI => 1
37	];
38
39	/**
40	 * Work config name.
41	 *
42	 * @var string
43	 */
44	protected $config = '';
45
46	/**
47	 * Get reference macros for trigger.
48	 * If macro reference non existing value it expands to empty string.
49	 *
50	 * @param string $expression
51	 * @param array  $references
52	 *
53	 * @return array
54	 */
55	protected function resolveTriggerReferences($expression, $references) {
56		$matched_macros = $this->getMacroPositions($expression, ['usermacros' => true]);
57
58		// Replace user macros with string 'macro' to make values search easier.
59		foreach (array_reverse($matched_macros, true) as $pos => $macro) {
60			$expression = substr_replace($expression, 'macro', $pos, strlen($macro));
61		}
62
63		// Replace functionids with string 'function' to make values search easier.
64		$expression = preg_replace('/\{[0-9]+\}/', 'function', $expression);
65
66		// Replace whitespace with emptyness to make value search easier.
67		$expression = str_replace(" \r\n\t", '', $expression);
68
69		// Search for numeric values in expression.
70		preg_match_all('/((?<![\)\.0-9]|[\.0-9]['.ZBX_BYTE_SUFFIXES.ZBX_TIME_SUFFIXES.']|function)\-?'.
71				'([.][0-9]+|[0-9]+[.]?[0-9]*)['.ZBX_BYTE_SUFFIXES.ZBX_TIME_SUFFIXES.']?)/', $expression, $values);
72
73		$macro_values = [];
74
75		foreach (array_keys($references) as $reference) {
76			$i = (int) $reference[1] - 1;
77			$macro_values[$reference] = array_key_exists($i, $values[0]) ? $values[0][$i] : '';
78		}
79
80		return $macro_values;
81	}
82
83	/**
84	 * Checking existence of the macros.
85	 *
86	 * @param array  $texts
87	 * @param array  $type
88	 *
89	 * @return bool
90	 */
91	protected function hasMacros(array $texts, array $types) {
92		foreach ($texts as $text) {
93			if ($this->getMacroPositions($text, $types)) {
94				return true;
95			}
96		}
97
98		return false;
99	}
100
101	/**
102	 * Transform types, used in extractMacros() function to types which can be used in getMacroPositions().
103	 *
104	 * @param array  $types
105	 *
106	 * @return array
107	 */
108	protected function transformToPositionTypes(array $types) {
109		foreach (['macros', 'macros_n', 'macro_funcs_n'] as $type) {
110			if (array_key_exists($type, $types)) {
111				$patterns = [];
112				foreach ($types[$type] as $key => $_patterns) {
113					$patterns = array_merge($patterns, $_patterns);
114				}
115				$types[$type] = $patterns;
116			}
117		}
118
119		return $types;
120	}
121
122	/**
123	 * Extract positions of the macros from a string.
124	 *
125	 * @param string $text
126	 * @param array  $types
127	 * @param bool   $types['usermacros']
128	 * @param array  $types['macros'][<macro_patterns>]
129	 * @param array  $types['macros_n'][<macro_patterns>]
130	 * @param array  $types['macro_funcs_n'][<macro_patterns>]
131	 * @param bool   $types['references']
132	 * @param bool   $types['lldmacros']
133	 * @param bool   $types['functionids']
134	 * @param bool   $types['replacements']
135	 *
136	 * @return array
137	 */
138	public function getMacroPositions($text, array $types) {
139		$macros = [];
140		$extract_usermacros = array_key_exists('usermacros', $types);
141		$extract_macros = array_key_exists('macros', $types);
142		$extract_macros_n = array_key_exists('macros_n', $types);
143		$extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types);
144		$extract_references = array_key_exists('references', $types);
145		$extract_lldmacros = array_key_exists('lldmacros', $types);
146		$extract_functionids = array_key_exists('functionids', $types);
147		$extract_replacements = array_key_exists('replacements', $types);
148
149		if ($extract_usermacros) {
150			$user_macro_parser = new CUserMacroParser();
151		}
152
153		if ($extract_macros) {
154			$macro_parser = new CMacroParser($types['macros']);
155		}
156
157		if ($extract_macros_n) {
158			$macro_n_parser = new CMacroParser($types['macros_n'], ['allow_reference' => true]);
159		}
160
161		if ($extract_macro_funcs_n) {
162			$macro_func_n_parser = new CMacroFunctionParser($types['macro_funcs_n'], ['allow_reference' => true]);
163		}
164
165		if ($extract_references) {
166			$reference_parser = new CReferenceParser();
167		}
168
169		if ($extract_lldmacros) {
170			$lld_macro_parser = new CLLDMacroParser();
171			$lld_macro_function_parser = new CLLDMacroFunctionParser();
172		}
173
174		if ($extract_functionids) {
175			$functionid_parser = new CFunctionIdParser();
176		}
177
178		if ($extract_replacements) {
179			$replacement_parser = new CReplacementParser();
180		}
181
182		for ($pos = 0; isset($text[$pos]); $pos++) {
183			if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
184				$macros[$pos] = $user_macro_parser->getMatch();
185				$pos += $user_macro_parser->getLength() - 1;
186			}
187			elseif ($extract_macros && $macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
188				$macros[$pos] = $macro_parser->getMatch();
189				$pos += $macro_parser->getLength() - 1;
190			}
191			elseif ($extract_macros_n && $macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
192				$macros[$pos] = $macro_n_parser->getMatch();
193				$pos += $macro_n_parser->getLength() - 1;
194			}
195			elseif ($extract_macro_funcs_n && $macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
196				$macros[$pos] = $macro_func_n_parser->getMatch();
197				$pos += $macro_func_n_parser->getLength() - 1;
198			}
199			elseif ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
200				$macros[$pos] = $reference_parser->getMatch();
201				$pos += $reference_parser->getLength() - 1;
202			}
203			elseif ($extract_lldmacros && $lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
204				$macros[$pos] = $lld_macro_parser->getMatch();
205				$pos += $lld_macro_parser->getLength() - 1;
206			}
207			elseif ($extract_lldmacros && $lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
208				$macros[$pos] = $lld_macro_function_parser->getMatch();
209				$pos += $lld_macro_function_parser->getLength() - 1;
210			}
211			elseif ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
212				$macros[$pos] = $functionid_parser->getMatch();
213				$pos += $functionid_parser->getLength() - 1;
214			}
215			elseif ($extract_replacements && $replacement_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
216				$macros[$pos] = $replacement_parser->getMatch();
217				$pos += $replacement_parser->getLength() - 1;
218			}
219		}
220
221		return $macros;
222	}
223
224	/**
225	 * Extract macros from a string.
226	 *
227	 * @param array  $texts
228	 * @param array  $types
229	 * @param bool   $types['usermacros']
230	 * @param array  $types['macros'][][<macro_patterns>]
231	 * @param array  $types['macros_n'][][<macro_patterns>]
232	 * @param array  $types['macro_funcs_n'][][<macro_patterns>]
233	 * @param bool   $types['references']
234	 * @param bool   $types['lldmacros']
235	 * @param bool   $types['functionids']
236	 *
237	 * @return array
238	 */
239	protected function extractMacros(array $texts, array $types) {
240		$macros = [];
241		$extract_usermacros = array_key_exists('usermacros', $types);
242		$extract_macros = array_key_exists('macros', $types);
243		$extract_macros_n = array_key_exists('macros_n', $types);
244		$extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types);
245		$extract_references = array_key_exists('references', $types);
246		$extract_lldmacros = array_key_exists('lldmacros', $types);
247		$extract_functionids = array_key_exists('functionids', $types);
248
249		if ($extract_usermacros) {
250			$macros['usermacros'] = [];
251
252			$user_macro_parser = new CUserMacroParser();
253		}
254
255		if ($extract_macros) {
256			$macros['macros'] = [];
257
258			foreach ($types['macros'] as $key => $macro_patterns) {
259				$types['macros'][$key] = new CMacroParser($macro_patterns);
260				$macros['macros'][$key] = [];
261			}
262		}
263
264		if ($extract_macros_n) {
265			$macros['macros_n'] = [];
266
267			foreach ($types['macros_n'] as $key => $macro_patterns) {
268				$types['macros_n'][$key] = new CMacroParser($macro_patterns, ['allow_reference' => true]);
269				$macros['macros_n'][$key] = [];
270			}
271		}
272
273		if ($extract_macro_funcs_n) {
274			$macros['macro_funcs_n'] = [];
275
276			foreach ($types['macro_funcs_n'] as $key => $macro_patterns) {
277				$types['macro_funcs_n'][$key] = new CMacroFunctionParser($macro_patterns, ['allow_reference' => true]);
278				$macros['macro_funcs_n'][$key] = [];
279			}
280		}
281
282		if ($extract_references) {
283			$macros['references'] = [];
284
285			$reference_parser = new CReferenceParser();
286		}
287
288		if ($extract_lldmacros) {
289			$macros['lldmacros'] = [];
290
291			$lld_macro_parser = new CLLDMacroParser();
292			$lld_macro_function_parser = new CLLDMacroFunctionParser();
293		}
294
295		if ($extract_functionids) {
296			$macros['functionids'] = [];
297
298			$functionid_parser = new CFunctionIdParser();
299		}
300
301		foreach ($texts as $text) {
302			for ($pos = 0; isset($text[$pos]); $pos++) {
303				if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
304					$macros['usermacros'][$user_macro_parser->getMatch()] = null;
305					$pos += $user_macro_parser->getLength() - 1;
306					continue;
307				}
308
309				if ($extract_macros) {
310					foreach ($types['macros'] as $key => $macro_parser) {
311						if ($macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
312							$macros['macros'][$key][$macro_parser->getMatch()] = true;
313							$pos += $macro_parser->getLength() - 1;
314							continue 2;
315						}
316					}
317				}
318
319				if ($extract_macros_n) {
320					foreach ($types['macros_n'] as $key => $macro_n_parser) {
321						if ($macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
322							$macros['macros_n'][$key][$macro_n_parser->getMatch()] = [
323								'macro' => $macro_n_parser->getMacro(),
324								'f_num' => $macro_n_parser->getN()
325							];
326							$pos += $macro_n_parser->getLength() - 1;
327							continue 2;
328						}
329					}
330				}
331
332				if ($extract_macro_funcs_n) {
333					foreach ($types['macro_funcs_n'] as $key => $macro_func_n_parser) {
334						if ($macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
335							$macro_n_parser = $macro_func_n_parser->getMacroParser();
336							$function_parser = $macro_func_n_parser->getFunctionParser();
337							$function_parameters = [];
338
339							foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) {
340								switch ($param_raw['type']) {
341									case CFunctionParser::PARAM_UNQUOTED:
342										$function_parameters[] = $param_raw['raw'];
343										break;
344
345									case CFunctionParser::PARAM_QUOTED:
346										$function_parameters[] = CFunctionParser::unquoteParam($param_raw['raw']);
347										break;
348								}
349							}
350
351							$macros['macro_funcs_n'][$key][$macro_func_n_parser->getMatch()] = [
352								'macro' => $macro_n_parser->getMacro(),
353								'f_num' => $macro_n_parser->getN(),
354								'function' => $function_parser->getFunction(),
355								'parameters' => $function_parameters
356							];
357							$pos += $macro_func_n_parser->getLength() - 1;
358							continue 2;
359						}
360					}
361				}
362
363				if ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
364					$macros['references'][$reference_parser->getMatch()] = null;
365					$pos += $reference_parser->getLength() - 1;
366					continue;
367				}
368
369				if ($extract_lldmacros) {
370					if ($lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
371						$macros['lldmacros'][$lld_macro_parser->getMatch()] = null;
372						$pos += $lld_macro_parser->getLength() - 1;
373						continue;
374					}
375					elseif ($lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
376						$macros['lldmacros'][$lld_macro_function_parser->getMatch()] = null;
377						$pos += $lld_macro_function_parser->getLength() - 1;
378						continue;
379					}
380				}
381
382				if ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
383					$macros['functionids'][$functionid_parser->getMatch()] = null;
384					$pos += $functionid_parser->getLength() - 1;
385					continue;
386				}
387			}
388		}
389
390		if ($extract_macros) {
391			foreach ($types['macros'] as $key => $macro_parser) {
392				$macros['macros'][$key] = array_keys($macros['macros'][$key]);
393			}
394		}
395
396		return $macros;
397	}
398
399	/**
400	 * Returns the list of the item key parameters.
401	 *
402	 * @param string $params_raw
403	 *
404	 * @return array
405	 */
406	private function getItemKeyParameters($params_raw) {
407		$item_key_parameters = [];
408
409		foreach ($params_raw as $param_raw) {
410			switch ($param_raw['type']) {
411				case CItemKey::PARAM_ARRAY:
412					$item_key_parameters = array_merge($item_key_parameters,
413						$this->getItemKeyParameters($param_raw['parameters'])
414					);
415					break;
416
417				case CItemKey::PARAM_UNQUOTED:
418					$item_key_parameters[] = $param_raw['raw'];
419					break;
420
421				case CItemKey::PARAM_QUOTED:
422					$item_key_parameters[] = CItemKey::unquoteParam($param_raw['raw']);
423					break;
424			}
425		}
426
427		return $item_key_parameters;
428	}
429
430	/**
431	 * Extract macros from an item key.
432	 *
433	 * @param string $key		an item key
434	 * @param array  $types		the types of macros (see extractMacros() for more details)
435	 *
436	 * @return array			see extractMacros() for more details
437	 */
438	protected function extractItemKeyMacros($key, array $types) {
439		$item_key_parser = new CItemKey();
440
441		$item_key_parameters = [];
442		if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) {
443			$item_key_parameters = $this->getItemKeyParameters($item_key_parser->getParamsRaw());
444		}
445
446		return $this->extractMacros($item_key_parameters, $types);
447	}
448
449	/**
450	 * Extract macros from a trigger function.
451	 *
452	 * @param string $function	a trigger function, for example 'last({$LAST})'
453	 * @param array  $types		the types of macros (see extractMacros() for more details)
454	 *
455	 * @return array			see extractMacros() for more details
456	 */
457	protected function extractFunctionMacros($function, array $types) {
458		$function_parser = new CFunctionParser();
459
460		$function_parameters = [];
461		if ($function_parser->parse($function) == CParser::PARSE_SUCCESS) {
462			foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) {
463				switch ($param_raw['type']) {
464					case CFunctionParser::PARAM_UNQUOTED:
465						$function_parameters[] = $param_raw['raw'];
466						break;
467
468					case CFunctionParser::PARAM_QUOTED:
469						$function_parameters[] = CFunctionParser::unquoteParam($param_raw['raw']);
470						break;
471				}
472			}
473		}
474
475		return $this->extractMacros($function_parameters, $types);
476	}
477
478	/**
479	 * Resolves macros in the item key parameters.
480	 *
481	 * @param string $key_chain		an item key chain
482	 * @param string $params_raw
483	 * @param array  $macros		the list of macros (['{<MACRO>}' => '<value>', ...])
484	 * @param array  $types			the types of macros (see getMacroPositions() for more details)
485	 *
486	 * @return string
487	 */
488	private function resolveItemKeyParamsMacros($key_chain, array $params_raw, array $macros, array $types) {
489		foreach (array_reverse($params_raw) as $param_raw) {
490			$param = $param_raw['raw'];
491			$forced = false;
492
493			switch ($param_raw['type']) {
494				case CItemKey::PARAM_ARRAY:
495					$param = $this->resolveItemKeyParamsMacros($param, $param_raw['parameters'], $macros, $types);
496					break;
497
498				case CItemKey::PARAM_QUOTED:
499					$param = CItemKey::unquoteParam($param);
500					$forced = true;
501					// break; is not missing here
502
503				case CItemKey::PARAM_UNQUOTED:
504					$matched_macros = $this->getMacroPositions($param, $types);
505
506					foreach (array_reverse($matched_macros, true) as $pos => $macro) {
507						$param = substr_replace($param, $macros[$macro], $pos, strlen($macro));
508					}
509
510					$param = quoteItemKeyParam($param, $forced);
511					break;
512			}
513
514			$key_chain = substr_replace($key_chain, $param, $param_raw['pos'], strlen($param_raw['raw']));
515		}
516
517		return $key_chain;
518	}
519
520	/**
521	 * Resolves macros in the item key.
522	 *
523	 * @param string $key		an item key
524	 * @param array  $macros	the list of macros (['{<MACRO>}' => '<value>', ...])
525	 * @param array  $types		the types of macros (see getMacroPositions() for more details)
526	 *
527	 * @return string
528	 */
529	protected function resolveItemKeyMacros($key, array $macros, array $types) {
530		$item_key_parser = new CItemKey();
531
532		if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) {
533			$key = $this->resolveItemKeyParamsMacros($key, $item_key_parser->getParamsRaw(), $macros, $types);
534		}
535
536		return $key;
537	}
538
539	/**
540	 * Resolves macros in the trigger function parameters.
541	 *
542	 * @param string $function	a trigger function
543	 * @param array  $macros	the list of macros (['{<MACRO>}' => '<value>', ...])
544	 * @param array  $types		the types of macros (see getMacroPositions() for more details)
545	 *
546	 * @return string
547	 */
548	protected function resolveFunctionMacros($function, array $macros, array $types) {
549		$function_parser = new CFunctionParser();
550
551		if ($function_parser->parse($function) == CParser::PARSE_SUCCESS) {
552			$params_raw = $function_parser->getParamsRaw();
553			$function_chain = $params_raw['raw'];
554
555			foreach (array_reverse($params_raw['parameters']) as $param_raw) {
556				$param = $param_raw['raw'];
557				$forced = false;
558
559				switch ($param_raw['type']) {
560					case CFunctionParser::PARAM_QUOTED:
561						$param = CFunctionParser::unquoteParam($param);
562						$forced = true;
563						// break; is not missing here
564
565					case CFunctionParser::PARAM_UNQUOTED:
566						$matched_macros = $this->getMacroPositions($param, $types);
567
568						foreach (array_reverse($matched_macros, true) as $pos => $macro) {
569							$param = substr_replace($param, $macros[$macro], $pos, strlen($macro));
570						}
571
572						$param = quoteFunctionParam($param, $forced);
573						break;
574				}
575
576				$function_chain = substr_replace($function_chain, $param, $param_raw['pos'], strlen($param_raw['raw']));
577			}
578
579			$function = substr_replace($function, $function_chain, $params_raw['pos'], strlen($params_raw['raw']));
580		}
581
582		return $function;
583	}
584
585	/**
586	 * Find function ids in trigger expression.
587	 *
588	 * @param string $expression
589	 *
590	 * @return array	where key is function id position in expression and value is function id
591	 */
592	protected function findFunctions($expression) {
593		$functionids = [];
594
595		$functionid_parser = new CFunctionIdParser();
596		$macro_parser = new CMacroParser(['{TRIGGER.VALUE}']);
597		$user_macro_parser = new CUserMacroParser();
598
599		for ($pos = 0, $i = 1; isset($expression[$pos]); $pos++) {
600			if ($functionid_parser->parse($expression, $pos) != CParser::PARSE_FAIL) {
601				$pos += $functionid_parser->getLength() - 1;
602				$functionids[$i++] = substr($functionid_parser->getMatch(), 1, -1);
603			}
604			elseif ($user_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) {
605				$pos += $user_macro_parser->getLength() - 1;
606			}
607			elseif ($macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) {
608				$pos += $macro_parser->getLength() - 1;
609			}
610		}
611
612		if (array_key_exists(1, $functionids)) {
613			$functionids[0] = $functionids[1];
614		}
615
616		return $functionids;
617	}
618
619	/**
620	 * Get interface macros.
621	 *
622	 * @param array $macros
623	 * @param array $macros[<functionid>]
624	 * @param array $macros[<functionid>][<macro>]  an array of the tokens
625	 * @param array $macro_values
626	 *
627	 * @return array
628	 */
629	protected function getIpMacros(array $macros, array $macro_values) {
630		if (!$macros) {
631			return $macro_values;
632		}
633
634		$result = DBselect(
635			'SELECT f.triggerid,f.functionid,n.ip,n.dns,n.type,n.useip,n.port'.
636			' FROM functions f'.
637				' JOIN items i ON f.itemid=i.itemid'.
638				' JOIN interface n ON i.hostid=n.hostid'.
639			' WHERE '.dbConditionInt('f.functionid', array_keys($macros)).
640				' AND n.main=1'
641		);
642
643		// Macro should be resolved to interface with highest priority ($priorities).
644		$interfaces = [];
645
646		while ($row = DBfetch($result)) {
647			if (array_key_exists($row['functionid'], $interfaces)
648					&& $this->interfacePriorities[$interfaces[$row['functionid']]['type']]
649						> $this->interfacePriorities[$row['type']]) {
650				continue;
651			}
652
653			$interfaces[$row['functionid']] = $row;
654		}
655
656		foreach ($interfaces as $interface) {
657			foreach ($macros[$interface['functionid']] as $macro => $tokens) {
658				switch ($macro) {
659					case 'IPADDRESS':
660					case 'HOST.IP':
661						$value = $interface['ip'];
662						break;
663					case 'HOST.DNS':
664						$value = $interface['dns'];
665						break;
666					case 'HOST.CONN':
667						$value = $interface['useip'] ? $interface['ip'] : $interface['dns'];
668						break;
669					case 'HOST.PORT':
670						$value = $interface['port'];
671						break;
672				}
673
674				foreach ($tokens as $token) {
675					$macro_values[$interface['triggerid']][$token['token']] = $value;
676				}
677			}
678		}
679
680		return $macro_values;
681	}
682
683	/**
684	 * Get item macros.
685	 *
686	 * @param array $macros
687	 * @param array $macros[<functionid>]
688	 * @param array $macros[<functionid>][<macro>]  An array of the tokens.
689	 * @param array $macro_values
690	 * @param array $triggers
691	 * @param bool  $events                         Resolve {ITEM.VALUE} macro using 'clock' and 'ns' fields.
692	 *
693	 * @return array
694	 */
695	protected function getItemMacros(array $macros, array $macro_values, array $triggers = [], $events = false) {
696		if (!$macros) {
697			return $macro_values;
698		}
699
700		$functions = DBfetchArray(DBselect(
701			'SELECT f.triggerid,f.functionid,i.itemid,i.value_type,i.units,i.valuemapid'.
702			' FROM functions f'.
703				' JOIN items i ON f.itemid=i.itemid'.
704				' JOIN hosts h ON i.hostid=h.hostid'.
705			' WHERE '.dbConditionInt('f.functionid', array_keys($macros))
706		));
707
708		// False passed to DBfetch to get data without null converted to 0, which is done by default.
709		foreach ($functions as $function) {
710			foreach ($macros[$function['functionid']] as $m => $tokens) {
711				switch ($m) {
712					case 'ITEM.VALUE':
713						if ($events) {
714							$trigger = $triggers[$function['triggerid']];
715							$value = Manager::History()->getValueAt($function, $trigger['clock'], $trigger['ns']);
716							break;
717						}
718						// break; is not missing here
719
720					case 'ITEM.LASTVALUE':
721						$history = Manager::History()->getLastValues([$function], 1, ZBX_HISTORY_PERIOD);
722
723						$value = array_key_exists($function['itemid'], $history)
724							? $history[$function['itemid']][0]['value']
725							: null;
726						break;
727				}
728
729				if ($value !== null) {
730					foreach ($tokens as $token) {
731						if (array_key_exists('function', $token)) {
732							if ($token['function'] !== 'regsub' && $token['function'] !== 'iregsub') {
733								continue;
734							}
735
736							if (count($token['parameters']) != 2) {
737								continue;
738							}
739
740							$ci = ($token['function'] === 'iregsub') ? 'i' : '';
741
742							set_error_handler(function ($errno, $errstr) {});
743							$rc = preg_match('/'.$token['parameters'][0].'/'.$ci, $value, $matches);
744							restore_error_handler();
745
746							if ($rc === false) {
747								continue;
748							}
749
750							$macro_value = $token['parameters'][1];
751							$matched_macros = $this->getMacroPositions($macro_value, ['replacements' => true]);
752
753							foreach (array_reverse($matched_macros, true) as $pos => $macro) {
754								$macro_value = substr_replace($macro_value,
755									array_key_exists($macro[1], $matches) ? $matches[$macro[1]] : '',
756									$pos, strlen($macro)
757								);
758							}
759						}
760						else {
761							$macro_value = formatHistoryValue($value, $function);
762						}
763
764						$macro_values[$function['triggerid']][$token['token']] = $macro_value;
765					}
766				}
767			}
768		}
769
770		return $macro_values;
771	}
772
773	/**
774	 * Get host macros.
775	 *
776	 * @param array $macros
777	 * @param array $macros[<functionid>]
778	 * @param array $macros[<functionid>][<macro>]  an array of the tokens
779	 * @param array $macro_values
780	 *
781	 * @return array
782	 */
783	protected function getHostMacros(array $macros, array $macro_values) {
784		if (!$macros) {
785			return $macro_values;
786		}
787
788		$result = DBselect(
789			'SELECT f.triggerid,f.functionid,h.hostid,h.host,h.name'.
790			' FROM functions f'.
791				' JOIN items i ON f.itemid=i.itemid'.
792				' JOIN hosts h ON i.hostid=h.hostid'.
793			' WHERE '.dbConditionInt('f.functionid', array_keys($macros))
794		);
795
796		while ($row = DBfetch($result)) {
797			foreach ($macros[$row['functionid']] as $macro => $tokens) {
798				switch ($macro) {
799					case 'HOST.ID':
800						$value = $row['hostid'];
801						break;
802
803					case 'HOSTNAME':
804					case 'HOST.HOST':
805						$value = $row['host'];
806						break;
807
808					case 'HOST.NAME':
809						$value = $row['name'];
810						break;
811				}
812
813				foreach ($tokens as $token) {
814					$macro_values[$row['triggerid']][$token['token']] = $value;
815				}
816			}
817		}
818
819		return $macro_values;
820	}
821
822	/**
823	 * Is type available.
824	 *
825	 * @param string $type
826	 *
827	 * @return bool
828	 */
829	protected function isTypeAvailable($type) {
830		return in_array($type, $this->configs[$this->config]['types']);
831	}
832
833	/**
834	 * Get source field.
835	 *
836	 * @return string
837	 */
838	protected function getSource() {
839		return $this->configs[$this->config]['source'];
840	}
841
842	/**
843	 * Get macros with values.
844	 *
845	 * @param array $data
846	 * @param array $data[n]['hostids']  the list of host ids; [<hostid1>, ...]
847	 * @param array $data[n]['macros']   the list of user macros to resolve, ['<usermacro1>' => null, ...]
848	 *
849	 * @return array
850	 */
851	protected function getUserMacros(array $data) {
852		if (!$data) {
853			return $data;
854		}
855
856		// User macros.
857		$hostids = [];
858		foreach ($data as $element) {
859			foreach ($element['hostids'] as $hostid) {
860				$hostids[$hostid] = true;
861			}
862		}
863
864		$user_macro_parser = new CUserMacroParser();
865
866		/*
867		 * @var array $host_templates
868		 * @var array $host_templates[<hostid>]		array of templates
869		 */
870		$host_templates = [];
871
872		/*
873		 * @var array  $host_macros
874		 * @var array  $host_macros[<hostid>]
875		 * @var array  $host_macros[<hostid>][<macro>]				macro base without curly braces
876		 * @var string $host_macros[<hostid>][<macro>]['value']		base macro value (without context); can be null
877		 * @var array  $host_macros[<hostid>][<macro>]['contexts']	context values; ['<context>' => '<value>', ...]
878		 */
879		$host_macros = [];
880
881		if ($hostids) {
882			do {
883				$hostids = array_keys($hostids);
884
885				$db_host_macros = DBselect(
886					'SELECT hm.hostid,hm.macro,hm.value'.
887					' FROM hostmacro hm'.
888					' WHERE '.dbConditionInt('hm.hostid', $hostids)
889				);
890				while ($db_host_macro = DBfetch($db_host_macros)) {
891					if ($user_macro_parser->parse($db_host_macro['macro']) != CParser::PARSE_SUCCESS) {
892						continue;
893					}
894
895					$macro = $user_macro_parser->getMacro();
896					$context = $user_macro_parser->getContext();
897
898					if (!array_key_exists($db_host_macro['hostid'], $host_macros)) {
899						$host_macros[$db_host_macro['hostid']] = [];
900					}
901
902					if (!array_key_exists($macro, $host_macros[$db_host_macro['hostid']])) {
903						$host_macros[$db_host_macro['hostid']][$macro] = ['value' => null, 'contexts' => []];
904					}
905
906					if ($context === null) {
907						$host_macros[$db_host_macro['hostid']][$macro]['value'] = $db_host_macro['value'];
908					}
909					else {
910						$host_macros[$db_host_macro['hostid']][$macro]['contexts'][$context] = $db_host_macro['value'];
911					}
912				}
913
914				foreach ($hostids as $hostid) {
915					$host_templates[$hostid] = [];
916				}
917
918				$templateids = [];
919				$db_host_templates = DBselect(
920					'SELECT ht.hostid,ht.templateid'.
921					' FROM hosts_templates ht'.
922					' WHERE '.dbConditionInt('ht.hostid', $hostids)
923				);
924				while ($db_host_template = DBfetch($db_host_templates)) {
925					$host_templates[$db_host_template['hostid']][] = $db_host_template['templateid'];
926					$templateids[$db_host_template['templateid']] = true;
927				}
928
929				// only unprocessed templates will be populated
930				$hostids = [];
931				foreach (array_keys($templateids) as $templateid) {
932					if (!array_key_exists($templateid, $host_templates)) {
933						$hostids[$templateid] = true;
934					}
935				}
936			} while ($hostids);
937		}
938
939		$all_macros_resolved = true;
940
941		foreach ($data as &$element) {
942			$hostids = [];
943			foreach ($element['hostids'] as $hostid) {
944				$hostids[$hostid] = true;
945			}
946
947			$hostids = array_keys($hostids);
948			natsort($hostids);
949
950			foreach ($element['macros'] as $usermacro => &$value) {
951				if ($user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) {
952					$value = $this->getHostUserMacros($hostids, $user_macro_parser->getMacro(),
953						$user_macro_parser->getContext(), $host_templates, $host_macros
954					);
955
956					if ($value['value'] === null) {
957						$all_macros_resolved = false;
958					}
959				}
960				else {
961					// This macro cannot be resolved.
962					$value = ['value' => $usermacro, 'value_default' => null];
963				}
964			}
965			unset($value);
966		}
967		unset($element);
968
969		if (!$all_macros_resolved) {
970			// Global macros.
971			$db_global_macros = API::UserMacro()->get([
972				'output' => ['macro', 'value'],
973				'globalmacro' => true
974			]);
975
976			/*
977			 * @var array  $global_macros
978			 * @var array  $global_macros[<macro>]				macro base without curly braces
979			 * @var string $global_macros[<macro>]['value']		base macro value (without context); can be null
980			 * @var array  $global_macros[<macro>]['contexts']	context values; ['<context1>' => '<value1>', ...]
981			 */
982			$global_macros = [];
983
984			foreach ($db_global_macros as $db_global_macro) {
985				if ($user_macro_parser->parse($db_global_macro['macro']) == CParser::PARSE_SUCCESS) {
986					$macro = $user_macro_parser->getMacro();
987					$context = $user_macro_parser->getContext();
988
989					if (!array_key_exists($macro, $global_macros)) {
990						$global_macros[$macro] = ['value' => null, 'contexts' => []];
991					}
992
993					if ($context === null) {
994						$global_macros[$macro]['value'] = $db_global_macro['value'];
995					}
996					else {
997						$global_macros[$macro]['contexts'][$context] = $db_global_macro['value'];
998					}
999				}
1000			}
1001
1002			foreach ($data as &$element) {
1003				foreach ($element['macros'] as $usermacro => &$value) {
1004					if ($value['value'] === null && $user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) {
1005						$macro = $user_macro_parser->getMacro();
1006						$context = $user_macro_parser->getContext();
1007
1008						if (array_key_exists($macro, $global_macros)) {
1009							if ($context !== null && array_key_exists($context, $global_macros[$macro]['contexts'])) {
1010								$value['value'] = $global_macros[$macro]['contexts'][$context];
1011							}
1012							elseif ($global_macros[$macro]['value'] !== null) {
1013								if ($context === null) {
1014									$value['value'] = $global_macros[$macro]['value'];
1015								}
1016								elseif ($value['value_default'] === null) {
1017									$value['value_default'] = $global_macros[$macro]['value'];
1018								}
1019							}
1020						}
1021					}
1022				}
1023				unset($value);
1024			}
1025			unset($element);
1026		}
1027
1028		foreach ($data as &$element) {
1029			foreach ($element['macros'] as $usermacro => &$value) {
1030				if ($value['value'] !== null) {
1031					$value = $value['value'];
1032				}
1033				elseif ($value['value_default'] !== null) {
1034					$value = $value['value_default'];
1035				}
1036				else {
1037					// Unresolved macro.
1038					$value = $usermacro;
1039				}
1040			}
1041			unset($value);
1042		}
1043		unset($element);
1044
1045		return $data;
1046	}
1047
1048	/**
1049	 * Get user macro from the requested hosts.
1050	 *
1051	 * Use the base value returned by host macro as default value when expanding expand global macro. This will ensure
1052	 * the following user macro resolving priority:
1053	 *  1) host/template context macro
1054	 *  2) global context macro
1055	 *  3) host/template base (default) macro
1056	 *  4) global base (default) macro
1057	 *
1058	 * @param array  $hostids			The sorted list of hosts where macros will be looked for (hostid => hostid)
1059	 * @param string $macro				Macro base without curly braces, for example: SNMP_COMMUNITY
1060	 * @param string $context			Macro context to resolve
1061	 * @param array  $host_templates	The list of linked templates (see getUserMacros() for more details)
1062	 * @param array  $host_macros		The list of macros on hosts (see getUserMacros() for more details)
1063	 * @param string $value_default		Value
1064	 *
1065	 * @return array
1066	 */
1067	private function getHostUserMacros(array $hostids, $macro, $context, array $host_templates, array $host_macros,
1068			$value_default = null) {
1069		foreach ($hostids as $hostid) {
1070			if (array_key_exists($hostid, $host_macros) && array_key_exists($macro, $host_macros[$hostid])) {
1071				if ($context !== null && array_key_exists($context, $host_macros[$hostid][$macro]['contexts'])) {
1072					return [
1073						'value' => $host_macros[$hostid][$macro]['contexts'][$context],
1074						'value_default' => $value_default
1075					];
1076				}
1077
1078				if ($host_macros[$hostid][$macro]['value'] !== null) {
1079					if ($context === null) {
1080						return ['value' => $host_macros[$hostid][$macro]['value'], 'value_default' => $value_default];
1081					}
1082					elseif ($value_default === null) {
1083						$value_default = $host_macros[$hostid][$macro]['value'];
1084					}
1085				}
1086			}
1087		}
1088
1089		if (!$host_templates) {
1090			return ['value' => null, 'value_default' => $value_default];
1091		}
1092
1093		$templateids = [];
1094
1095		foreach ($hostids as $hostid) {
1096			if (array_key_exists($hostid, $host_templates)) {
1097				foreach ($host_templates[$hostid] as $templateid) {
1098					$templateids[$templateid] = true;
1099				}
1100			}
1101		}
1102
1103		if ($templateids) {
1104			$templateids = array_keys($templateids);
1105			natsort($templateids);
1106
1107			return $this->getHostUserMacros($templateids, $macro, $context, $host_templates, $host_macros,
1108				$value_default
1109			);
1110		}
1111
1112		return ['value' => null, 'value_default' => $value_default];
1113	}
1114}
1115