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		$values = [];
57		$expression_parser = new CExpressionParser([
58			'usermacros' => true,
59			'lldmacros' => true,
60			'collapsed_expression' => true
61		]);
62
63		if ($expression_parser->parse($expression) == CParser::PARSE_SUCCESS) {
64			foreach ($expression_parser->getResult()->getTokens() as $token) {
65				switch ($token['type']) {
66					case CExpressionParserResult::TOKEN_TYPE_NUMBER:
67					case CExpressionParserResult::TOKEN_TYPE_USER_MACRO:
68						$values[] = $token['match'];
69						break;
70
71					case CExpressionParserResult::TOKEN_TYPE_STRING:
72						$values[] = CExpressionParser::unquoteString($token['match']);
73						break;
74				}
75			}
76		}
77
78		foreach ($references as $macro => $value) {
79			$i = (int) $macro[1] - 1;
80			$references[$macro] = array_key_exists($i, $values) ? $values[$i] : '';
81		}
82
83		return $references;
84	}
85
86	/**
87	 * Checking existence of the macros.
88	 *
89	 * @param array  $texts
90	 * @param array  $type
91	 *
92	 * @return bool
93	 */
94	protected function hasMacros(array $texts, array $types) {
95		foreach ($texts as $text) {
96			if ($this->getMacroPositions($text, $types)) {
97				return true;
98			}
99		}
100
101		return false;
102	}
103
104	/**
105	 * Transform types, used in extractMacros() function to types which can be used in getMacroPositions().
106	 *
107	 * @param array  $types
108	 *
109	 * @return array
110	 */
111	protected function transformToPositionTypes(array $types) {
112		foreach (['macros', 'macros_n', 'macros_an', 'macro_funcs_n'] as $type) {
113			if (array_key_exists($type, $types)) {
114				$patterns = [];
115				foreach ($types[$type] as $key => $_patterns) {
116					$patterns = array_merge($patterns, $_patterns);
117				}
118				$types[$type] = $patterns;
119			}
120		}
121
122		return $types;
123	}
124
125	/**
126	 * Extract positions of the macros from a string.
127	 *
128	 * @param string $text
129	 * @param array  $types
130	 * @param bool   $types['usermacros']
131	 * @param array  $types['macros'][<macro_patterns>]
132	 * @param array  $types['macros_n'][<macro_patterns>]
133	 * @param array  $types['macros_an'][<macro_patterns>]
134	 * @param array  $types['macro_funcs_n'][<macro_patterns>]
135	 * @param bool   $types['references']
136	 * @param bool   $types['lldmacros']
137	 * @param bool   $types['functionids']
138	 * @param bool   $types['replacements']
139	 *
140	 * @return array
141	 */
142	public function getMacroPositions($text, array $types) {
143		$macros = [];
144		$extract_usermacros = array_key_exists('usermacros', $types);
145		$extract_macros = array_key_exists('macros', $types);
146		$extract_macros_n = array_key_exists('macros_n', $types);
147		$extract_macros_an = array_key_exists('macros_an', $types);
148		$extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types);
149		$extract_references = array_key_exists('references', $types);
150		$extract_lldmacros = array_key_exists('lldmacros', $types);
151		$extract_functionids = array_key_exists('functionids', $types);
152		$extract_replacements = array_key_exists('replacements', $types);
153
154		if ($extract_usermacros) {
155			$user_macro_parser = new CUserMacroParser();
156		}
157
158		if ($extract_macros) {
159			$macro_parser = new CMacroParser(['macros' => $types['macros']]);
160		}
161
162		if ($extract_macros_n) {
163			$macro_n_parser = new CMacroParser([
164				'macros' => $types['macros_n'],
165				'ref_type' => CMacroParser::REFERENCE_NUMERIC
166			]);
167		}
168
169		if ($extract_macros_an) {
170			$macro_an_parser = new CMacroParser([
171				'macros' => $types['macros_an'],
172				'ref_type' => CMacroParser::REFERENCE_ALPHANUMERIC
173			]);
174		}
175
176		if ($extract_macro_funcs_n) {
177			$macro_func_n_parser = new CMacroFunctionParser([
178				'macros' => $types['macro_funcs_n'],
179				'ref_type' => CMacroParser::REFERENCE_NUMERIC
180			]);
181		}
182
183		if ($extract_references) {
184			$reference_parser = new CReferenceParser();
185		}
186
187		if ($extract_lldmacros) {
188			$lld_macro_parser = new CLLDMacroParser();
189			$lld_macro_function_parser = new CLLDMacroFunctionParser();
190		}
191
192		if ($extract_functionids) {
193			$functionid_parser = new CFunctionIdParser();
194		}
195
196		if ($extract_replacements) {
197			$replacement_parser = new CReplacementParser();
198		}
199
200		for ($pos = 0; isset($text[$pos]); $pos++) {
201			if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
202				$macros[$pos] = $user_macro_parser->getMatch();
203				$pos += $user_macro_parser->getLength() - 1;
204			}
205			elseif ($extract_macros && $macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
206				$macros[$pos] = $macro_parser->getMatch();
207				$pos += $macro_parser->getLength() - 1;
208			}
209			elseif ($extract_macros_n && $macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
210				$macros[$pos] = $macro_n_parser->getMatch();
211				$pos += $macro_n_parser->getLength() - 1;
212			}
213			elseif ($extract_macros_an && $macro_an_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
214				$macros[$pos] = $macro_an_parser->getMatch();
215				$pos += $macro_an_parser->getLength() - 1;
216			}
217			elseif ($extract_macro_funcs_n && $macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
218				$macros[$pos] = $macro_func_n_parser->getMatch();
219				$pos += $macro_func_n_parser->getLength() - 1;
220			}
221			elseif ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
222				$macros[$pos] = $reference_parser->getMatch();
223				$pos += $reference_parser->getLength() - 1;
224			}
225			elseif ($extract_lldmacros && $lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
226				$macros[$pos] = $lld_macro_parser->getMatch();
227				$pos += $lld_macro_parser->getLength() - 1;
228			}
229			elseif ($extract_lldmacros && $lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
230				$macros[$pos] = $lld_macro_function_parser->getMatch();
231				$pos += $lld_macro_function_parser->getLength() - 1;
232			}
233			elseif ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
234				$macros[$pos] = $functionid_parser->getMatch();
235				$pos += $functionid_parser->getLength() - 1;
236			}
237			elseif ($extract_replacements && $replacement_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
238				$macros[$pos] = $replacement_parser->getMatch();
239				$pos += $replacement_parser->getLength() - 1;
240			}
241		}
242
243		return $macros;
244	}
245
246	/**
247	 * Extract macros from a string.
248	 *
249	 * @param array  $texts
250	 * @param array  $types
251	 * @param bool   $types['usermacros']
252	 * @param array  $types['macros'][][<macro_patterns>]
253	 * @param array  $types['macros_n'][][<macro_patterns>]
254	 * @param array  $types['macros_an'][][<macro_patterns>]
255	 * @param array  $types['macro_funcs_n'][][<macro_patterns>]
256	 * @param bool   $types['references']
257	 * @param bool   $types['lldmacros']
258	 * @param bool   $types['functionids']
259	 *
260	 * @return array
261	 */
262	public static function extractMacros(array $texts, array $types) {
263		$macros = [];
264		$extract_usermacros = array_key_exists('usermacros', $types);
265		$extract_macros = array_key_exists('macros', $types);
266		$extract_macros_n = array_key_exists('macros_n', $types);
267		$extract_macros_an = array_key_exists('macros_an', $types);
268		$extract_macro_funcs_n = array_key_exists('macro_funcs_n', $types);
269		$extract_references = array_key_exists('references', $types);
270		$extract_lldmacros = array_key_exists('lldmacros', $types);
271		$extract_functionids = array_key_exists('functionids', $types);
272
273		if ($extract_usermacros) {
274			$macros['usermacros'] = [];
275
276			$user_macro_parser = new CUserMacroParser();
277		}
278
279		if ($extract_macros) {
280			$macros['macros'] = [];
281
282			foreach ($types['macros'] as $key => $macro_patterns) {
283				$types['macros'][$key] = new CMacroParser(['macros' => $macro_patterns]);
284				$macros['macros'][$key] = [];
285			}
286		}
287
288		if ($extract_macros_n) {
289			$macros['macros_n'] = [];
290
291			foreach ($types['macros_n'] as $key => $macro_patterns) {
292				$types['macros_n'][$key] = new CMacroParser([
293					'macros' => $macro_patterns,
294					'ref_type' => CMacroParser::REFERENCE_NUMERIC
295				]);
296				$macros['macros_n'][$key] = [];
297			}
298		}
299
300		if ($extract_macros_an) {
301			$macros['macros_an'] = [];
302
303			foreach ($types['macros_an'] as $key => $macro_patterns) {
304				$types['macros_an'][$key] = new CMacroParser([
305					'macros' => $macro_patterns,
306					'ref_type' => CMacroParser::REFERENCE_ALPHANUMERIC
307				]);
308				$macros['macros_an'][$key] = [];
309			}
310		}
311
312		if ($extract_macro_funcs_n) {
313			$macros['macro_funcs_n'] = [];
314
315			foreach ($types['macro_funcs_n'] as $key => $macro_patterns) {
316				$types['macro_funcs_n'][$key] = new CMacroFunctionParser([
317					'macros' => $macro_patterns,
318					'ref_type' => CMacroParser::REFERENCE_NUMERIC
319				]);
320				$macros['macro_funcs_n'][$key] = [];
321			}
322		}
323
324		if ($extract_references) {
325			$macros['references'] = [];
326
327			$reference_parser = new CReferenceParser();
328		}
329
330		if ($extract_lldmacros) {
331			$macros['lldmacros'] = [];
332
333			$lld_macro_parser = new CLLDMacroParser();
334			$lld_macro_function_parser = new CLLDMacroFunctionParser();
335		}
336
337		if ($extract_functionids) {
338			$macros['functionids'] = [];
339
340			$functionid_parser = new CFunctionIdParser();
341		}
342
343		foreach ($texts as $text) {
344			for ($pos = 0; isset($text[$pos]); $pos++) {
345				if ($extract_usermacros && $user_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
346					$macros['usermacros'][$user_macro_parser->getMatch()] = null;
347					$pos += $user_macro_parser->getLength() - 1;
348					continue;
349				}
350
351				if ($extract_macros) {
352					foreach ($types['macros'] as $key => $macro_parser) {
353						if ($macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
354							$macros['macros'][$key][$macro_parser->getMatch()] = true;
355							$pos += $macro_parser->getLength() - 1;
356							continue 2;
357						}
358					}
359				}
360
361				if ($extract_macros_n) {
362					foreach ($types['macros_n'] as $key => $macro_n_parser) {
363						if ($macro_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
364							$macros['macros_n'][$key][$macro_n_parser->getMatch()] = [
365								'macro' => $macro_n_parser->getMacro(),
366								'f_num' => $macro_n_parser->getReference()
367							];
368							$pos += $macro_n_parser->getLength() - 1;
369							continue 2;
370						}
371					}
372				}
373
374				if ($extract_macros_an) {
375					foreach ($types['macros_an'] as $key => $macro_an_parser) {
376						if ($macro_an_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
377							$macros['macros_an'][$key][$macro_an_parser->getMatch()] = [
378								'macro' => $macro_an_parser->getMacro(),
379								'f_num' => $macro_an_parser->getReference()
380							];
381							$pos += $macro_an_parser->getLength() - 1;
382							continue 2;
383						}
384					}
385				}
386
387				if ($extract_macro_funcs_n) {
388					foreach ($types['macro_funcs_n'] as $key => $macro_func_n_parser) {
389						if ($macro_func_n_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
390							$macro_n_parser = $macro_func_n_parser->getMacroParser();
391							$function_parser = $macro_func_n_parser->getFunctionParser();
392							$function_parameters = [];
393
394							foreach ($function_parser->getParamsRaw()['parameters'] as $param_raw) {
395								switch ($param_raw['type']) {
396									case C10FunctionParser::PARAM_UNQUOTED:
397										$function_parameters[] = $param_raw['raw'];
398										break;
399
400									case C10FunctionParser::PARAM_QUOTED:
401										$function_parameters[] = C10FunctionParser::unquoteParam($param_raw['raw']);
402										break;
403								}
404							}
405
406							$macros['macro_funcs_n'][$key][$macro_func_n_parser->getMatch()] = [
407								'macro' => $macro_n_parser->getMacro(),
408								'f_num' => $macro_n_parser->getReference(),
409								'function' => $function_parser->getFunction(),
410								'parameters' => $function_parameters
411							];
412							$pos += $macro_func_n_parser->getLength() - 1;
413							continue 2;
414						}
415					}
416				}
417
418				if ($extract_references && $reference_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
419					$macros['references'][$reference_parser->getMatch()] = null;
420					$pos += $reference_parser->getLength() - 1;
421					continue;
422				}
423
424				if ($extract_lldmacros) {
425					if ($lld_macro_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
426						$macros['lldmacros'][$lld_macro_parser->getMatch()] = null;
427						$pos += $lld_macro_parser->getLength() - 1;
428						continue;
429					}
430					elseif ($lld_macro_function_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
431						$macros['lldmacros'][$lld_macro_function_parser->getMatch()] = null;
432						$pos += $lld_macro_function_parser->getLength() - 1;
433						continue;
434					}
435				}
436
437				if ($extract_functionids && $functionid_parser->parse($text, $pos) != CParser::PARSE_FAIL) {
438					$macros['functionids'][$functionid_parser->getMatch()] = null;
439					$pos += $functionid_parser->getLength() - 1;
440					continue;
441				}
442			}
443		}
444
445		if ($extract_macros) {
446			foreach ($types['macros'] as $key => $macro_parser) {
447				$macros['macros'][$key] = array_keys($macros['macros'][$key]);
448			}
449		}
450
451		return $macros;
452	}
453
454	/**
455	 * Returns the list of the item key parameters.
456	 *
457	 * @param array $params_raw
458	 *
459	 * @return array
460	 */
461	private function getItemKeyParameters($params_raw) {
462		$item_key_parameters = [];
463
464		foreach ($params_raw as $param_raw) {
465			switch ($param_raw['type']) {
466				case CItemKey::PARAM_ARRAY:
467					$item_key_parameters = array_merge($item_key_parameters,
468						$this->getItemKeyParameters($param_raw['parameters'])
469					);
470					break;
471
472				case CItemKey::PARAM_UNQUOTED:
473					$item_key_parameters[] = $param_raw['raw'];
474					break;
475
476				case CItemKey::PARAM_QUOTED:
477					$item_key_parameters[] = CItemKey::unquoteParam($param_raw['raw']);
478					break;
479			}
480		}
481
482		return $item_key_parameters;
483	}
484
485	/**
486	 * Extract macros from an item key.
487	 *
488	 * @param string $key		an item key
489	 * @param array  $types		the types of macros (see extractMacros() for more details)
490	 *
491	 * @return array			see extractMacros() for more details
492	 */
493	protected function extractItemKeyMacros($key, array $types) {
494		$item_key_parser = new CItemKey();
495
496		$item_key_parameters = [];
497		if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) {
498			$item_key_parameters = $this->getItemKeyParameters($item_key_parser->getParamsRaw());
499		}
500
501		return self::extractMacros($item_key_parameters, $types);
502	}
503
504	/**
505	 * Extract macros from a trigger function.
506	 *
507	 * @param string $function	a history function, for example 'last(/host/key, {$OFFSET})'
508	 * @param array  $types		the types of macros (see extractMacros() for more details)
509	 *
510	 * @return array			see extractMacros() for more details
511	 */
512	protected function extractFunctionMacros($function, array $types) {
513		$hist_function_parser = new CHistFunctionParser(['usermacros' => true, 'lldmacros' => true]);
514		$function_parameters = [];
515
516		if ($hist_function_parser->parse($function) == CParser::PARSE_SUCCESS) {
517			foreach ($hist_function_parser->getParameters() as $parameter) {
518				switch ($parameter['type']) {
519					case CHistFunctionParser::PARAM_TYPE_PERIOD:
520					case CHistFunctionParser::PARAM_TYPE_UNQUOTED:
521						$function_parameters[] = $parameter['match'];
522						break;
523
524					case CHistFunctionParser::PARAM_TYPE_QUOTED:
525						$function_parameters[] = CHistFunctionParser::unquoteParam($parameter['match']);
526						break;
527				}
528			}
529		}
530
531		return self::extractMacros($function_parameters, $types);
532	}
533
534	/**
535	 * Resolves macros in the item key parameters.
536	 *
537	 * @param string $key_chain		an item key chain
538	 * @param string $params_raw
539	 * @param array  $macros		the list of macros (['{<MACRO>}' => '<value>', ...])
540	 * @param array  $types			the types of macros (see getMacroPositions() for more details)
541	 *
542	 * @return string
543	 */
544	private function resolveItemKeyParamsMacros($key_chain, array $params_raw, array $macros, array $types) {
545		foreach (array_reverse($params_raw) as $param_raw) {
546			$param = $param_raw['raw'];
547			$forced = false;
548
549			switch ($param_raw['type']) {
550				case CItemKey::PARAM_ARRAY:
551					$param = $this->resolveItemKeyParamsMacros($param, $param_raw['parameters'], $macros, $types);
552					break;
553
554				case CItemKey::PARAM_QUOTED:
555					$param = CItemKey::unquoteParam($param);
556					$forced = true;
557					// break; is not missing here
558
559				case CItemKey::PARAM_UNQUOTED:
560					$matched_macros = $this->getMacroPositions($param, $types);
561
562					foreach (array_reverse($matched_macros, true) as $pos => $macro) {
563						$param = substr_replace($param, $macros[$macro], $pos, strlen($macro));
564					}
565
566					$param = quoteItemKeyParam($param, $forced);
567					break;
568			}
569
570			$key_chain = substr_replace($key_chain, $param, $param_raw['pos'], strlen($param_raw['raw']));
571		}
572
573		return $key_chain;
574	}
575
576	/**
577	 * Resolves macros in the item key.
578	 *
579	 * @param string $key		an item key
580	 * @param array  $macros	the list of macros (['{<MACRO>}' => '<value>', ...])
581	 * @param array  $types		the types of macros (see getMacroPositions() for more details)
582	 *
583	 * @return string
584	 */
585	protected function resolveItemKeyMacros($key, array $macros, array $types) {
586		$item_key_parser = new CItemKey();
587
588		if ($item_key_parser->parse($key) == CParser::PARSE_SUCCESS) {
589			$key = $this->resolveItemKeyParamsMacros($key, $item_key_parser->getParamsRaw(), $macros, $types);
590		}
591
592		return $key;
593	}
594
595	/**
596	 * Resolves macros in the trigger function parameters.
597	 *
598	 * @param string $function	a trigger function
599	 * @param array  $macros	the list of macros (['{<MACRO>}' => '<value>', ...])
600	 * @param array  $types		the types of macros (see getMacroPositions() for more details)
601	 *
602	 * @return string
603	 */
604	protected function resolveFunctionMacros($function, array $macros, array $types) {
605		$hist_function_parser = new CHistFunctionParser(['usermacros' => true, 'lldmacros' => true]);
606
607		if ($hist_function_parser->parse($function) == CParser::PARSE_SUCCESS) {
608			foreach (array_reverse($hist_function_parser->getParameters()) as $parameter) {
609				$param = $parameter['match'];
610				$forced = false;
611
612				if ($parameter['type'] == CHistFunctionParser::PARAM_TYPE_QUOTED) {
613					$param = CHistFunctionParser::unquoteParam($param);
614					$forced = true;
615				}
616
617				$matched_macros = $this->getMacroPositions($param, $types);
618
619				foreach (array_reverse($matched_macros, true) as $pos => $macro) {
620					$param = substr_replace($param, $macros[$macro], $pos, strlen($macro));
621				}
622
623				if ($parameter['type'] != CHistFunctionParser::PARAM_TYPE_PERIOD) {
624					$param = quoteFunctionParam($param, $forced);
625				}
626
627				$function = substr_replace($function, $param, $parameter['pos'], $parameter['length']);
628			}
629		}
630
631		return $function;
632	}
633
634	/**
635	 * Find function ids in trigger expression.
636	 *
637	 * @param string $expression
638	 *
639	 * @return array	where key is function id position in expression and value is function id
640	 */
641	protected function findFunctions($expression) {
642		$functionids = [];
643
644		$functionid_parser = new CFunctionIdParser();
645		$macro_parser = new CMacroParser(['macros' => ['{TRIGGER.VALUE}']]);
646		$user_macro_parser = new CUserMacroParser();
647
648		for ($pos = 0, $i = 1; isset($expression[$pos]); $pos++) {
649			if ($functionid_parser->parse($expression, $pos) != CParser::PARSE_FAIL) {
650				$pos += $functionid_parser->getLength() - 1;
651				$functionids[$i++] = substr($functionid_parser->getMatch(), 1, -1);
652			}
653			elseif ($user_macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) {
654				$pos += $user_macro_parser->getLength() - 1;
655			}
656			elseif ($macro_parser->parse($expression, $pos) != CParser::PARSE_FAIL) {
657				$pos += $macro_parser->getLength() - 1;
658			}
659		}
660
661		if (array_key_exists(1, $functionids)) {
662			$functionids[0] = $functionids[1];
663		}
664
665		return $functionids;
666	}
667
668	/**
669	 * Get interface macros.
670	 *
671	 * @param array $macros
672	 * @param array $macros[<functionid>]
673	 * @param array $macros[<functionid>][<macro>]  an array of the tokens
674	 * @param array $macro_values
675	 *
676	 * @return array
677	 */
678	protected function getIpMacros(array $macros, array $macro_values) {
679		if (!$macros) {
680			return $macro_values;
681		}
682
683		$result = DBselect(
684			'SELECT f.triggerid,f.functionid,n.ip,n.dns,n.type,n.useip,n.port'.
685			' FROM functions f'.
686				' JOIN items i ON f.itemid=i.itemid'.
687				' JOIN interface n ON i.hostid=n.hostid'.
688			' WHERE '.dbConditionInt('f.functionid', array_keys($macros)).
689				' AND n.main=1'
690		);
691
692		// Macro should be resolved to interface with highest priority ($priorities).
693		$interfaces = [];
694
695		while ($row = DBfetch($result)) {
696			if (array_key_exists($row['functionid'], $interfaces)
697					&& $this->interfacePriorities[$interfaces[$row['functionid']]['type']]
698						> $this->interfacePriorities[$row['type']]) {
699				continue;
700			}
701
702			$interfaces[$row['functionid']] = $row;
703		}
704
705		foreach ($interfaces as $interface) {
706			foreach ($macros[$interface['functionid']] as $macro => $tokens) {
707				switch ($macro) {
708					case 'IPADDRESS':
709					case 'HOST.IP':
710						$value = $interface['ip'];
711						break;
712					case 'HOST.DNS':
713						$value = $interface['dns'];
714						break;
715					case 'HOST.CONN':
716						$value = $interface['useip'] ? $interface['ip'] : $interface['dns'];
717						break;
718					case 'HOST.PORT':
719						$value = $interface['port'];
720						break;
721				}
722
723				foreach ($tokens as $token) {
724					$macro_values[$interface['triggerid']][$token['token']] = $value;
725				}
726			}
727		}
728
729		return $macro_values;
730	}
731
732	/**
733	 * Resolves items value maps, valuemap property will be added to every item.
734	 *
735	 * @param array $items
736	 * @param int   $items[]['itemid']
737	 * @param int   $items[]['valuemapid']
738	 *
739	 * @return array
740	 */
741	protected static function getItemsValueMaps(array $items): array {
742		foreach ($items as &$item) {
743			$item['valuemap'] = [];
744		}
745		unset($item);
746
747		$valuemapids = array_flip(array_column($items, 'valuemapid'));
748		unset($valuemapids[0]);
749
750		if (!$valuemapids) {
751			return $items;
752		}
753
754		$options = [
755			'output' => ['valuemapid', 'type', 'value', 'newvalue'],
756			'filter' => ['valuemapid' => array_keys($valuemapids)],
757			'sortfield' => ['sortorder']
758		];
759		$db_mappings = DBselect(DB::makeSql('valuemap_mapping', $options));
760
761		$db_valuemaps = [];
762
763		while ($db_mapping  = DBfetch($db_mappings)) {
764			$db_valuemaps[$db_mapping['valuemapid']]['mappings'][] = [
765				'type' => $db_mapping['type'],
766				'value' => $db_mapping['value'],
767				'newvalue' => $db_mapping['newvalue']
768			];
769		}
770
771		foreach ($items as &$item) {
772			if (array_key_exists($item['valuemapid'], $db_valuemaps)) {
773				$item['valuemap'] = $db_valuemaps[$item['valuemapid']];
774			}
775		}
776		unset($item);
777
778		return $items;
779	}
780
781	/**
782	 * Get item macros.
783	 *
784	 * @param array $macros
785	 * @param array $macros[<functionid>]
786	 * @param array $macros[<functionid>][<macro>]  An array of the tokens.
787	 * @param array $macro_values
788	 * @param array $triggers
789	 * @param array $options
790	 * @param bool  $options['events]               Resolve {ITEM.VALUE} macro using 'clock' and 'ns' fields.
791	 * @param bool  $options['html]
792	 *
793	 * @return array
794	 */
795	protected function getItemMacros(array $macros, array $macro_values, array $triggers = [], array $options = []) {
796		if (!$macros) {
797			return $macro_values;
798		}
799
800		$options += [
801			'events' => false,
802			'html' => false
803		];
804
805		$functions = DBfetchArray(DBselect(
806			'SELECT f.triggerid,f.functionid,i.itemid,i.hostid,i.name,i.key_,i.value_type,i.units,i.valuemapid'.
807			' FROM functions f'.
808				' JOIN items i ON f.itemid=i.itemid'.
809				' JOIN hosts h ON i.hostid=h.hostid'.
810			' WHERE '.dbConditionInt('f.functionid', array_keys($macros))
811		));
812
813		$functions = CMacrosResolverHelper::resolveItemNames($functions);
814		$functions = self::getItemsValueMaps($functions);
815
816		// False passed to DBfetch to get data without null converted to 0, which is done by default.
817		foreach ($functions as $function) {
818			foreach ($macros[$function['functionid']] as $m => $tokens) {
819				$clock = null;
820				$value = null;
821
822				switch ($m) {
823					case 'ITEM.VALUE':
824						if ($options['events']) {
825							$trigger = $triggers[$function['triggerid']];
826							$history = Manager::History()->getValueAt($function, $trigger['clock'], $trigger['ns']);
827
828							if (is_array($history)) {
829								if (array_key_exists('clock', $history)) {
830									$clock = $history['clock'];
831								}
832								if (array_key_exists('value', $history)) {
833									$value = $history['value'];
834								}
835							}
836							break;
837						}
838						// break; is not missing here
839
840					case 'ITEM.LASTVALUE':
841						$history = Manager::History()->getLastValues([$function], 1, timeUnitToSeconds(
842							CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD)
843						));
844
845						if (array_key_exists($function['itemid'], $history)) {
846							$clock = $history[$function['itemid']][0]['clock'];
847							$value = $history[$function['itemid']][0]['value'];
848						}
849						break;
850				}
851
852				foreach ($tokens as $token) {
853					$macro_value = UNRESOLVED_MACRO_STRING;
854
855					if ($value !== null) {
856						if (array_key_exists('function', $token)) {
857							if ($token['function'] !== 'regsub' && $token['function'] !== 'iregsub') {
858								continue;
859							}
860
861							if (count($token['parameters']) != 2) {
862								continue;
863							}
864
865							$ci = ($token['function'] === 'iregsub') ? 'i' : '';
866
867							set_error_handler(function ($errno, $errstr) {});
868							$rc = preg_match('/'.$token['parameters'][0].'/'.$ci, $value, $matches);
869							restore_error_handler();
870
871							if ($rc === false) {
872								continue;
873							}
874
875							$macro_value = $token['parameters'][1];
876							$matched_macros = $this->getMacroPositions($macro_value, ['replacements' => true]);
877
878							foreach (array_reverse($matched_macros, true) as $pos => $macro) {
879								$macro_value = substr_replace($macro_value,
880									array_key_exists($macro[1], $matches) ? $matches[$macro[1]] : '',
881									$pos, strlen($macro)
882								);
883							}
884						}
885						else {
886							$macro_value = formatHistoryValue($value, $function);
887						}
888					}
889
890					if ($options['html']) {
891						$macro_value = str_replace(["\r\n", "\n"], [" "], $macro_value);
892						$hint_table = (new CTable())
893							->addClass('list-table')
894							->addRow([
895								new CCol($function['name_expanded']),
896								new CCol(
897									($clock !== null)
898										? zbx_date2str(DATE_TIME_FORMAT_SECONDS, $clock)
899										: UNRESOLVED_MACRO_STRING
900								),
901								new CCol($macro_value),
902								new CCol(
903									($function['value_type'] == ITEM_VALUE_TYPE_FLOAT
904											|| $function['value_type'] == ITEM_VALUE_TYPE_UINT64)
905										? new CLink(_('Graph'), (new CUrl('history.php'))
906											->setArgument('action', HISTORY_GRAPH)
907											->setArgument('itemids[]', $function['itemid'])
908											->getUrl()
909										)
910										: new CLink(_('History'), (new CUrl('history.php'))
911											->setArgument('action', HISTORY_VALUES)
912											->setArgument('itemids[]', $function['itemid'])
913											->getUrl()
914										)
915								)
916							]);
917						$macro_value = new CSpan([
918							(new CSpan())
919								->addClass('main-hint')
920								->setHint($hint_table),
921							(new CLinkAction($macro_value))
922								->addClass('hint-item')
923								->setAttribute('data-hintbox', '1')
924						]);
925					}
926
927					$macro_values[$function['triggerid']][$token['token']] = $macro_value;
928				}
929			}
930		}
931
932		return $macro_values;
933	}
934
935	protected function getItemLogMacros(array $macros, array $macro_values) {
936		if (!$macros) {
937			return $macro_values;
938		}
939
940		$functions = DBfetchArray(DBselect(
941			'SELECT f.triggerid,f.functionid,i.itemid,i.hostid,i.name,i.key_,i.value_type'.
942			' FROM functions f'.
943				' JOIN items i ON f.itemid=i.itemid'.
944				' JOIN hosts h ON i.hostid=h.hostid'.
945			' WHERE '.dbConditionInt('f.functionid', array_keys($macros)).
946			' AND i.value_type='.ITEM_VALUE_TYPE_LOG
947		));
948
949		if (!$functions) {
950			return $macro_values;
951		}
952
953		$functions = CMacrosResolverHelper::resolveItemNames($functions);
954
955		foreach ($functions as $function) {
956			foreach ($macros[$function['functionid']] as $m => $tokens) {
957				$value = UNRESOLVED_MACRO_STRING;
958
959				$history = Manager::History()->getLastValues([$function], 1, timeUnitToSeconds(
960					CSettingsHelper::get(CSettingsHelper::HISTORY_PERIOD)
961				));
962
963				if (!array_key_exists($function['itemid'], $history)) {
964					continue;
965				}
966
967				switch ($m) {
968					case 'ITEM.LOG.DATE':
969						$value = date('Y.m.d', $history[$function['itemid']][0]['timestamp']);
970						break;
971					case 'ITEM.LOG.TIME':
972						$value = date('H:i:s', $history[$function['itemid']][0]['timestamp']);;
973						break;
974					case 'ITEM.LOG.AGE':
975						$value = zbx_date2age($history[$function['itemid']][0]['timestamp']);
976						break;
977					case 'ITEM.LOG.SOURCE':
978						$value = $history[$function['itemid']][0]['source'];
979						break;
980					case 'ITEM.LOG.SEVERITY':
981						$value = getSeverityName($history[$function['itemid']][0]['severity']);
982						break;
983					case 'ITEM.LOG.NSEVERITY':
984						$value = $history[$function['itemid']][0]['severity'];
985						break;
986					case 'ITEM.LOG.EVENTID':
987						$value = $history[$function['itemid']][0]['logeventid'];
988						break;
989				}
990
991				foreach ($tokens as $token) {
992					$macro_values[$function['triggerid']][$token['token']] = $value;
993				}
994			}
995		}
996
997		return $macro_values;
998	}
999
1000	/**
1001	 * Get host macros.
1002	 *
1003	 * @param array $macros
1004	 * @param array $macros[<functionid>]
1005	 * @param array $macros[<functionid>][<macro>]  an array of the tokens
1006	 * @param array $macro_values
1007	 *
1008	 * @return array
1009	 */
1010	protected function getHostMacros(array $macros, array $macro_values) {
1011		if (!$macros) {
1012			return $macro_values;
1013		}
1014
1015		$result = DBselect(
1016			'SELECT f.triggerid,f.functionid,h.hostid,h.host,h.name'.
1017			' FROM functions f'.
1018				' JOIN items i ON f.itemid=i.itemid'.
1019				' JOIN hosts h ON i.hostid=h.hostid'.
1020			' WHERE '.dbConditionInt('f.functionid', array_keys($macros))
1021		);
1022
1023		while ($row = DBfetch($result)) {
1024			foreach ($macros[$row['functionid']] as $macro => $tokens) {
1025				switch ($macro) {
1026					case 'HOST.ID':
1027						$value = $row['hostid'];
1028						break;
1029
1030					case 'HOSTNAME':
1031					case 'HOST.HOST':
1032						$value = $row['host'];
1033						break;
1034
1035					case 'HOST.NAME':
1036						$value = $row['name'];
1037						break;
1038				}
1039
1040				foreach ($tokens as $token) {
1041					$macro_values[$row['triggerid']][$token['token']] = $value;
1042				}
1043			}
1044		}
1045
1046		return $macro_values;
1047	}
1048
1049	/**
1050	 * Is type available.
1051	 *
1052	 * @param string $type
1053	 *
1054	 * @return bool
1055	 */
1056	protected function isTypeAvailable($type) {
1057		return in_array($type, $this->configs[$this->config]['types']);
1058	}
1059
1060	/**
1061	 * Get source field.
1062	 *
1063	 * @return string
1064	 */
1065	protected function getSource() {
1066		return $this->configs[$this->config]['source'];
1067	}
1068
1069	/**
1070	 * Get macros with values.
1071	 *
1072	 * @param array $data
1073	 * @param array $data[n]['hostids']  The list of host ids; [<hostid1>, ...].
1074	 * @param array $data[n]['macros']   The list of user macros to resolve, ['<usermacro1>' => null, ...].
1075	 * @param bool  $unset_undefined     Unset undefined macros.
1076	 *
1077	 * @return array
1078	 */
1079	protected function getUserMacros(array $data, bool $unset_undefined = false) {
1080		if (!$data) {
1081			return $data;
1082		}
1083
1084		// User macros.
1085		$hostids = [];
1086		foreach ($data as $element) {
1087			foreach ($element['hostids'] as $hostid) {
1088				$hostids[$hostid] = true;
1089			}
1090		}
1091
1092		$user_macro_parser = new CUserMacroParser();
1093
1094		/*
1095		 * @var array $host_templates
1096		 * @var array $host_templates[<hostid>]		array of templates
1097		 */
1098		$host_templates = [];
1099
1100		/*
1101		 * @var array  $host_macros
1102		 * @var array  $host_macros[<hostid>]
1103		 * @var array  $host_macros[<hostid>][<macro>]				macro base without curly braces
1104		 * @var string $host_macros[<hostid>][<macro>]['value']		base macro value (without context and regex);
1105		 * 															can be null
1106		 * @var array  $host_macros[<hostid>][<macro>]['contexts']	context values; ['<context1>' => '<value1>', ...]
1107		 * @var array  $host_macros[<hostid>][<macro>]['regex']		regex values; ['<regex1>' => '<value1>', ...]
1108		 */
1109		$host_macros = [];
1110
1111		if ($hostids) {
1112			do {
1113				$hostids = array_keys($hostids);
1114
1115				$db_host_macros = API::UserMacro()->get([
1116					'output' => ['macro', 'value', 'type', 'hostid'],
1117					'hostids' => $hostids
1118				]);
1119
1120				foreach ($db_host_macros as $db_host_macro) {
1121					if ($user_macro_parser->parse($db_host_macro['macro']) != CParser::PARSE_SUCCESS) {
1122						continue;
1123					}
1124
1125					$hostid = $db_host_macro['hostid'];
1126					$macro = $user_macro_parser->getMacro();
1127					$context = $user_macro_parser->getContext();
1128					$regex = $user_macro_parser->getRegex();
1129					$value = self::getMacroValue($db_host_macro);
1130
1131					if (!array_key_exists($hostid, $host_macros)) {
1132						$host_macros[$hostid] = [];
1133					}
1134
1135					if (!array_key_exists($macro, $host_macros[$hostid])) {
1136						$host_macros[$hostid][$macro] = ['value' => null, 'contexts' => [], 'regex' => []];
1137					}
1138
1139					if ($context === null && $regex === null) {
1140						$host_macros[$hostid][$macro]['value'] = $value;
1141					}
1142					elseif ($regex !== null) {
1143						$host_macros[$hostid][$macro]['regex'][$regex] = $value;
1144					}
1145					else {
1146						$host_macros[$hostid][$macro]['contexts'][$context] = $value;
1147					}
1148				}
1149
1150				foreach ($hostids as $hostid) {
1151					$host_templates[$hostid] = [];
1152				}
1153
1154				$templateids = [];
1155				$db_host_templates = DBselect(
1156					'SELECT ht.hostid,ht.templateid'.
1157					' FROM hosts_templates ht'.
1158					' WHERE '.dbConditionInt('ht.hostid', $hostids)
1159				);
1160				while ($db_host_template = DBfetch($db_host_templates)) {
1161					$host_templates[$db_host_template['hostid']][] = $db_host_template['templateid'];
1162					$templateids[$db_host_template['templateid']] = true;
1163				}
1164
1165				// only unprocessed templates will be populated
1166				$hostids = [];
1167				foreach (array_keys($templateids) as $templateid) {
1168					if (!array_key_exists($templateid, $host_templates)) {
1169						$hostids[$templateid] = true;
1170					}
1171				}
1172			} while ($hostids);
1173		}
1174
1175		// Reordering only regex array.
1176		$host_macros = self::sortRegexHostMacros($host_macros);
1177
1178		$all_macros_resolved = true;
1179
1180		foreach ($data as &$element) {
1181			$hostids = [];
1182			foreach ($element['hostids'] as $hostid) {
1183				$hostids[$hostid] = true;
1184			}
1185
1186			$hostids = array_keys($hostids);
1187			natsort($hostids);
1188
1189			foreach ($element['macros'] as $usermacro => &$value) {
1190				if ($user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) {
1191					$value = $this->getHostUserMacros($hostids, $user_macro_parser->getMacro(),
1192						$user_macro_parser->getContext(), $host_templates, $host_macros
1193					);
1194
1195					if ($value['value'] === null) {
1196						$all_macros_resolved = false;
1197					}
1198				}
1199				else {
1200					// This macro cannot be resolved.
1201					$value = ['value' => $usermacro, 'value_default' => null];
1202				}
1203			}
1204			unset($value);
1205		}
1206		unset($element);
1207
1208		if (!$all_macros_resolved) {
1209			// Global macros.
1210			$db_global_macros = API::UserMacro()->get([
1211				'output' => ['macro', 'value', 'type'],
1212				'globalmacro' => true
1213			]);
1214
1215			/*
1216			 * @var array  $global_macros
1217			 * @var array  $global_macros[<macro>]				macro base without curly braces
1218			 * @var string $global_macros[<macro>]['value']		base macro value (without context and regex);
1219			 * 													can be null
1220			 * @var array  $global_macros[<macro>]['contexts']	context values; ['<context1>' => '<value1>', ...]
1221			 * @var array  $global_macros[<macro>]['regex']		regex values; ['<regex1>' => '<value1>', ...]
1222			 */
1223			$global_macros = [];
1224
1225			foreach ($db_global_macros as $db_global_macro) {
1226				if ($user_macro_parser->parse($db_global_macro['macro']) == CParser::PARSE_SUCCESS) {
1227					$macro = $user_macro_parser->getMacro();
1228					$context = $user_macro_parser->getContext();
1229					$regex = $user_macro_parser->getRegex();
1230					$value = self::getMacroValue($db_global_macro);
1231
1232					if (!array_key_exists($macro, $global_macros)) {
1233						$global_macros[$macro] = ['value' => null, 'contexts' => [], 'regex' => []];
1234					}
1235
1236					if ($context === null && $regex === null) {
1237						$global_macros[$macro]['value'] = $value;
1238					}
1239					elseif ($regex !== null) {
1240						$global_macros[$macro]['regex'][$regex] = $value;
1241					}
1242					else {
1243						$global_macros[$macro]['contexts'][$context] = $value;
1244					}
1245				}
1246			}
1247
1248			// Reordering only regex array.
1249			$global_macros = self::sortRegexGlobalMacros($global_macros);
1250
1251			foreach ($data as &$element) {
1252				foreach ($element['macros'] as $usermacro => &$value) {
1253					if ($value['value'] === null && $user_macro_parser->parse($usermacro) == CParser::PARSE_SUCCESS) {
1254						$macro = $user_macro_parser->getMacro();
1255						$context = $user_macro_parser->getContext();
1256
1257						if (array_key_exists($macro, $global_macros)) {
1258							if ($context !== null && array_key_exists($context, $global_macros[$macro]['contexts'])) {
1259								$value['value'] = $global_macros[$macro]['contexts'][$context];
1260							}
1261							elseif ($context !== null && count($global_macros[$macro]['regex'])) {
1262								foreach ($global_macros[$macro]['regex'] as $regex => $val) {
1263									if (preg_match('/'.strtr(trim($regex, '/'), ['/' => '\\/']).'/', $context) === 1) {
1264										$value['value'] = $val;
1265										break;
1266									}
1267								}
1268							}
1269
1270							if ($value['value'] === null && $global_macros[$macro]['value'] !== null) {
1271								if ($context === null) {
1272									$value['value'] = $global_macros[$macro]['value'];
1273								}
1274								elseif ($value['value_default'] === null) {
1275									$value['value_default'] = $global_macros[$macro]['value'];
1276								}
1277							}
1278						}
1279					}
1280				}
1281				unset($value);
1282			}
1283			unset($element);
1284		}
1285
1286		foreach ($data as $key => $element) {
1287			foreach ($element['macros'] as $usermacro => $value) {
1288				if ($value['value'] !== null) {
1289					$data[$key]['macros'][$usermacro] = $value['value'];
1290				}
1291				elseif ($value['value_default'] !== null) {
1292					$data[$key]['macros'][$usermacro] = $value['value_default'];
1293				}
1294				// Unresolved macro.
1295				elseif ($unset_undefined) {
1296					unset($data[$key]['macros'][$usermacro]);
1297				}
1298				else {
1299					$data[$key]['macros'][$usermacro] = $usermacro;
1300				}
1301			}
1302		}
1303
1304		return $data;
1305	}
1306
1307	/**
1308	 * Get user macro from the requested hosts.
1309	 *
1310	 * Use the base value returned by host macro as default value when expanding expand global macro. This will ensure
1311	 * the following user macro resolving priority:
1312	 *  1) host/template context macro
1313	 *  2) global context macro
1314	 *  3) host/template base (default) macro
1315	 *  4) global base (default) macro
1316	 *
1317	 * @param array  $hostids			The sorted list of hosts where macros will be looked for (hostid => hostid)
1318	 * @param string $macro				Macro base without curly braces, for example: SNMP_COMMUNITY
1319	 * @param string $context			Macro context to resolve
1320	 * @param array  $host_templates	The list of linked templates (see getUserMacros() for more details)
1321	 * @param array  $host_macros		The list of macros on hosts (see getUserMacros() for more details)
1322	 * @param string $value_default		Value
1323	 *
1324	 * @return array
1325	 */
1326	private function getHostUserMacros(array $hostids, $macro, $context, array $host_templates, array $host_macros,
1327			$value_default = null) {
1328		foreach ($hostids as $hostid) {
1329			if (array_key_exists($hostid, $host_macros) && array_key_exists($macro, $host_macros[$hostid])) {
1330				// Searching context coincidence with macro contexts.
1331				if ($context !== null && array_key_exists($context, $host_macros[$hostid][$macro]['contexts'])) {
1332					return [
1333						'value' => $host_macros[$hostid][$macro]['contexts'][$context],
1334						'value_default' => $value_default
1335					];
1336				}
1337				// Searching context coincidence, if regex array not empty.
1338				elseif ($context !== null && count($host_macros[$hostid][$macro]['regex'])) {
1339					foreach ($host_macros[$hostid][$macro]['regex'] as $regex => $val) {
1340						if (preg_match('/'.strtr(trim($regex, '/'), ['/' => '\\/']).'/', $context) === 1) {
1341							return [
1342								'value' => $val,
1343								'value_default' => $value_default
1344							];
1345						}
1346					}
1347				}
1348
1349				if ($host_macros[$hostid][$macro]['value'] !== null) {
1350					if ($context === null) {
1351						return ['value' => $host_macros[$hostid][$macro]['value'], 'value_default' => $value_default];
1352					}
1353					elseif ($value_default === null) {
1354						$value_default = $host_macros[$hostid][$macro]['value'];
1355					}
1356				}
1357			}
1358		}
1359
1360		if (!$host_templates) {
1361			return ['value' => null, 'value_default' => $value_default];
1362		}
1363
1364		$templateids = [];
1365
1366		foreach ($hostids as $hostid) {
1367			if (array_key_exists($hostid, $host_templates)) {
1368				foreach ($host_templates[$hostid] as $templateid) {
1369					$templateids[$templateid] = true;
1370				}
1371			}
1372		}
1373
1374		if ($templateids) {
1375			$templateids = array_keys($templateids);
1376			natsort($templateids);
1377
1378			return $this->getHostUserMacros($templateids, $macro, $context, $host_templates, $host_macros,
1379				$value_default
1380			);
1381		}
1382
1383		return ['value' => null, 'value_default' => $value_default];
1384	}
1385
1386	/**
1387	 * Get macro value refer by type.
1388	 *
1389	 * @static
1390	 *
1391	 * @param array $macro
1392	 *
1393	 * @return string
1394	 */
1395	public static function getMacroValue(array $macro): string {
1396		return ($macro['type'] == ZBX_MACRO_TYPE_SECRET || $macro['type'] == ZBX_MACRO_TYPE_VAULT)
1397			? ZBX_SECRET_MASK
1398			: $macro['value'];
1399	}
1400
1401	/**
1402	 * Sorting host macros.
1403	 *
1404	 * @param array $host_macros
1405	 *
1406	 * @return array
1407	 */
1408	private static function sortRegexHostMacros(array $host_macros): array {
1409		foreach ($host_macros as &$macros) {
1410			foreach ($macros as &$value) {
1411				$value['regex'] = self::sortRegex($value['regex']);
1412			}
1413			unset($value);
1414		}
1415		unset($macros);
1416
1417		return $host_macros;
1418	}
1419
1420	/**
1421	 * Sorting global macros.
1422	 *
1423	 * @static
1424	 *
1425	 * @param array $global_macros
1426	 *
1427	 * @return array
1428	 */
1429	private static function sortRegexGlobalMacros(array $global_macros): array {
1430		foreach ($global_macros as &$value) {
1431			$value['regex'] = self::sortRegex($value['regex']);
1432		}
1433		unset($value);
1434
1435		return $global_macros;
1436	}
1437
1438	/**
1439	 * Sort regex.
1440	 *
1441	 * @static
1442	 *
1443	 * @param array $macros
1444	 *
1445	 * @return array
1446	 */
1447	private static function sortRegex(array $macros): array {
1448		$keys = array_keys($macros);
1449
1450		usort($keys, 'strcmp');
1451
1452		$new_array = [];
1453
1454		foreach($keys as $key) {
1455			$new_array[$key] = $macros[$key];
1456		}
1457
1458		return $new_array;
1459	}
1460}
1461