1<?php declare(strict_types = 1); 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * A class for implementing conversions used by the trigger wizard. 24 */ 25class CTextTriggerConstructor { 26 27 const EXPRESSION_TYPE_MATCH = 0; 28 const EXPRESSION_TYPE_NO_MATCH = 1; 29 30 /** 31 * Parser used for parsing trigger expressions. 32 * 33 * @var CExpressionParser 34 */ 35 protected $expression_parser; 36 37 /** 38 * @param CExpressionParser $expression_parser 39 */ 40 public function __construct(CExpressionParser $expression_parser) { 41 $this->expression_parser = $expression_parser; 42 } 43 44 /** 45 * Create a trigger expression from the given expression parts. 46 * 47 * Most of this function was left unchanged to preserve the current behavior of the constructor. 48 * Feel free to rewrite and correct it if necessary. 49 * 50 * @param string $host host name 51 * @param string $item_key item key 52 * @param array $expressions array of expression parts 53 * @param string $expressions[]['value'] expression string 54 * @param int $expressions[]['type'] whether the string should match the expression; supported values: 55 * self::EXPRESSION_TYPE_MATCH and self::EXPRESSION_TYPE_NO_MATCH 56 * 57 * @return bool|string 58 */ 59 public function getExpressionFromParts(string $host, string $item_key, array $expressions) { 60 $result = ''; 61 $query = '/'.$host.'/'.$item_key; 62 63 if (empty($expressions)) { 64 error(_('Expression cannot be empty')); 65 66 return false; 67 } 68 69 // regexp used to split an expressions into tokens 70 $ZBX_PREG_EXPESSION_FUNC_FORMAT = '^(['.ZBX_PREG_PRINT.']*) (and|or) (not )?(-)? ?[(]*(([a-zA-Z_.\$]{6,7})(\\((['.ZBX_PREG_PRINT.']+?){0,1}\\)))(['.ZBX_PREG_PRINT.']*)$'; 71 $functions = ['regexp' => 1, 'iregexp' => 1]; 72 $expr_array = []; 73 $cexpor = 0; 74 $startpos = -1; 75 76 foreach ($expressions as $expression) { 77 if ($expression['type'] == self::EXPRESSION_TYPE_MATCH) { 78 if (!empty($result)) { 79 $result .= ' or '; 80 } 81 if ($cexpor == 0) { 82 $startpos = mb_strlen($result); 83 } 84 $cexpor++; 85 $eq_global = '<>0'; 86 } 87 else { 88 if (($cexpor > 1) & ($startpos >= 0)) { 89 $head = mb_substr($result, 0, $startpos); 90 $tail = mb_substr($result, $startpos); 91 $result = $head.'('.$tail.')'; 92 } 93 $cexpor = 0; 94 $eq_global = '=0'; 95 if (!empty($result)) { 96 $result .= ' and '; 97 } 98 } 99 100 $expr = ' and '.$expression['value']; 101 102 // strip extra spaces around "and" and "or" operators 103 $expr = preg_replace('/\s+(and|or)\s+/U', ' $1 ', $expr); 104 105 $expr_array = []; 106 $sub_expr_count = 0; 107 $sub_expr = ''; 108 $multi = preg_match('/.+(and|or).+/', $expr); 109 110 // split an expression into separate tokens 111 // start from the first part of the expression, then move to the next one 112 while (preg_match('/'.$ZBX_PREG_EXPESSION_FUNC_FORMAT.'/i', $expr, $arr)) { 113 $arr[6] = strtolower($arr[6]); 114 if (!isset($functions[$arr[6]])) { 115 error(_('Incorrect function is used').'. ['.$expression['value'].']'); 116 117 return false; 118 } 119 $expr_array[$sub_expr_count]['eq'] = trim($arr[2]); 120 $expr_array[$sub_expr_count]['not'] = trim($arr[3]); 121 $expr_array[$sub_expr_count]['minus'] = trim($arr[4]); 122 $expr_array[$sub_expr_count]['func'] = $arr[6]; 123 $expr_array[$sub_expr_count]['pattern'] = $arr[8]; 124 125 $sub_expr_count++; 126 $expr = $arr[1]; 127 } 128 129 if (empty($expr_array)) { 130 error(_('Incorrect trigger expression').'. ['.$expression['value'].']'); 131 132 return false; 133 } 134 135 $expr_array[$sub_expr_count-1]['eq'] = ''; 136 137 $sub_eq = ''; 138 if ($multi > 0) { 139 $sub_eq = $eq_global; 140 } 141 142 foreach ($expr_array as $id => $expr) { 143 $eq = ($expr['eq'] === '') ? '' : ' '.$expr['eq'].' '; 144 $not = ($expr['not'] === '') ? '' : $expr['not'].' '; 145 $function = 'find('.$query.',,"'.$expr['func'].'","'.$expr['pattern'].'")'; 146 if ($multi > 0) { 147 $sub_expr = $eq.'('.$not.$expr['minus'].$function.')'.$sub_eq.$sub_expr; 148 } 149 else { 150 $sub_expr = $eq.$expr['eq'].$not.$expr['minus'].$function.')'.$sub_eq.$sub_expr; 151 } 152 } 153 154 if ($multi > 0) { 155 $result .= '('.$sub_expr.')'; 156 } 157 else { 158 $result .= '(('.$sub_expr.')'.$eq_global.')'; 159 } 160 } 161 162 if (($cexpor > 1) & ($startpos >= 0)) { 163 $head = mb_substr($result, 0, $startpos); 164 $tail = mb_substr($result, $startpos); 165 $result = $head.'('.$tail.')'; 166 } 167 168 return $result; 169 } 170 171 /** 172 * Break a trigger expression generated by the constructor. 173 * 174 * To be successfully parsed, each item function macro must be wrapped in additional parentheses, for example, 175 * ((find(item.item,,regex,param))=0) 176 * 177 * Most of this function was left unchanged to preserve the current behavior of the constructor. 178 * Feel free to rewrite and correct it if necessary. 179 * 180 * @param string $expression trigger expression 181 * 182 * @return array an array of expression parts, see self::getExpressionFromParts() for the structure of the part 183 * array 184 */ 185 public function getPartsFromExpression($expression) { 186 // strip extra parentheses 187 $expression = preg_replace('/\(\(\((.+?)\)\) and/i', '(($1) and', $expression); 188 $expression = preg_replace('/\(\(\((.+?)\)\)$/i', '(($1)', $expression); 189 190 $this->expression_parser->parse($expression); 191 192 $expressions = []; 193 $splitTokens = $this->splitTokensByFirstLevel($this->expression_parser->getResult()->getTokens()); 194 foreach ($splitTokens as $key => $tokens) { 195 $expr = []; 196 197 // replace whole function macros with their functions 198 foreach ($tokens as $token) { 199 switch ($token['type']) { 200 case CExpressionParserResult::TOKEN_TYPE_OPERATOR: 201 $value = ($token['match'] === 'and' || $token['match'] === 'or' || $token['match'] === 'not') 202 ? ' '.$token['match'].' ' 203 : $token['match']; 204 break; 205 206 case CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION: 207 if ($token['data']['function'] === 'find' && count($token['data']['parameters']) == 4) { 208 $function = CExpressionParser::unquoteString($token['data']['parameters'][2]['match']); 209 $pattern = CExpressionParser::unquoteString($token['data']['parameters'][3]['match']); 210 $value = $function.'('.$pattern.')'; 211 break; 212 } 213 // break; is not missing here 214 215 default: 216 $value = $token['match']; 217 } 218 219 $expr[] = $value; 220 } 221 222 $expr = implode($expr); 223 224 // trim surrounding parentheses 225 $expr = preg_replace('/^\((.*)\)$/u', '$1', $expr); 226 227 // trim parentheses around item function macros 228 $value = preg_replace('/\((.*)\)(=|<>)0/U', '$1', $expr); 229 230 // trim surrounding parentheses 231 $value = preg_replace('/^\((.*)\)$/u', '$1', $value); 232 233 $expressions[$key]['value'] = trim($value); 234 $expressions[$key]['type'] = (strpos($expr, '<>0', mb_strlen($expr) - 4) === false) 235 ? self::EXPRESSION_TYPE_NO_MATCH 236 : self::EXPRESSION_TYPE_MATCH; 237 } 238 239 return $expressions; 240 } 241 242 /** 243 * Split the trigger expression tokens into separate arrays. 244 * 245 * The tokens are split at the first occurrence of the "and" or "or" operators with respect to parentheses. 246 * 247 * @param array $tokens an array of tokens from the CExpressionParserResult 248 * 249 * @return array an array of token arrays grouped by expression 250 */ 251 protected function splitTokensByFirstLevel(array $tokens) { 252 $expressions = []; 253 $currentExpression = []; 254 255 $level = 0; 256 foreach ($tokens as $token) { 257 switch ($token['type']) { 258 case CExpressionParserResult::TOKEN_TYPE_OPERATOR: 259 // look for an "or" or "and" operator on the top parentheses level 260 // if such an expression is found, save all of the tokens before it as a separate expression 261 if ($level == 0 && ($token['match'] === 'or' || $token['match'] === 'and')) { 262 $expressions[] = $currentExpression; 263 $currentExpression = []; 264 265 // continue to the next token 266 continue 2; 267 } 268 269 break; 270 case CExpressionParserResult::TOKEN_TYPE_OPEN_BRACE: 271 $level++; 272 273 break; 274 case CExpressionParserResult::TOKEN_TYPE_CLOSE_BRACE: 275 $level--; 276 277 break; 278 } 279 280 $currentExpression[] = $token; 281 } 282 283 $expressions[] = $currentExpression; 284 285 return $expressions; 286 } 287 288} 289