1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * A parser for relative time in "now[/<yMwdhm>][<+->N<yMwdhms>[/<yMwdhm>]]" format. 24 */ 25class CRelativeTimeParser extends CParser { 26 27 const ZBX_TOKEN_PRECISION = 0; 28 const ZBX_TOKEN_OFFSET = 1; 29 30 /** 31 * @var array $tokens An array of tokens for relative date. 32 */ 33 private $tokens; 34 35 private $user_macro_parser; 36 private $lld_macro_parser; 37 private $lld_macro_function_parser; 38 39 /** 40 * An options array. 41 * 42 * Supported options: 43 * 'usermacros' => false Enable user macros usage in the periods. 44 * 'lldmacros' => false Enable low-level discovery macros usage in the periods. 45 * 46 * @var array 47 */ 48 public $options = [ 49 'usermacros' => false, 50 'lldmacros' => false 51 ]; 52 53 /** 54 * @param array $options 55 */ 56 public function __construct(array $options = []) { 57 $this->options = $options + $this->options; 58 59 if ($this->options['usermacros']) { 60 $this->user_macro_parser = new CUserMacroParser(); 61 } 62 if ($this->options['lldmacros']) { 63 $this->lld_macro_parser = new CLLDMacroParser(); 64 $this->lld_macro_function_parser = new CLLDMacroFunctionParser(); 65 } 66 } 67 68 /** 69 * Parse the given period. 70 * 71 * @param string $source Source string that needs to be parsed. 72 * @param int $pos Position offset. 73 */ 74 public function parse($source, $pos = 0) { 75 $this->length = 0; 76 $this->match = ''; 77 $this->tokens = []; 78 79 $p = $pos; 80 81 if (!$this->parseRelativeTime($source, $p) && !$this->parseMacros($source, $p)) { 82 return self::PARSE_FAIL; 83 } 84 85 $this->length = $p - $pos; 86 $this->match = substr($source, $pos, $this->length); 87 88 return isset($source[$p]) ? self::PARSE_SUCCESS_CONT : self::PARSE_SUCCESS; 89 } 90 91 /** 92 * Parse relative time. 93 * 94 * @param string $source 95 * @param int $pos 96 * 97 * @return bool 98 */ 99 private function parseRelativeTime($source, &$pos) { 100 if (strncmp(substr($source, $pos), 'now', 3) != 0) { 101 return false; 102 } 103 104 $pos += 3; 105 106 while ($this->parsePrecision($source, $pos) || $this->parseOffset($source, $pos)) { 107 } 108 109 return true; 110 } 111 112 /** 113 * Parse precision. 114 * 115 * @param string $source 116 * @param int $pos 117 * 118 * @return bool 119 */ 120 private function parsePrecision($source, &$pos) { 121 $p = $pos; 122 123 if (!isset($source[$p]) || $source[$p] !== '/') { 124 return false; 125 } 126 127 $p++; 128 129 if (preg_match('/^[yMwdhm]/', substr($source, $p), $matches)) { 130 $this->tokens[] = [ 131 'type' => self::ZBX_TOKEN_PRECISION, 132 'suffix' => $matches[0] 133 ]; 134 135 $p++; 136 } 137 elseif (!$this->parseMacros($source, $p)) { 138 return false; 139 } 140 141 $pos = $p; 142 143 return true; 144 } 145 146 /** 147 * Parse offset. 148 * 149 * @param string $source 150 * @param int $pos 151 * 152 * @return bool 153 */ 154 private function parseOffset($source, &$pos) { 155 $p = $pos; 156 157 if (!preg_match('/^[+-]/', substr($source, $p), $sign_matches)) { 158 return false; 159 } 160 161 $p++; 162 163 if (preg_match('/^(?P<offset_value>[0-9]+)(?P<offset_suffix>[yMwdhms])?/', substr($source, $p), $matches)) { 164 $this->tokens[] = [ 165 'type' => self::ZBX_TOKEN_OFFSET, 166 'sign' => $sign_matches[0], 167 'value' => $matches['offset_value'], 168 'suffix' => array_key_exists('offset_suffix', $matches) ? $matches['offset_suffix'] : 's' 169 ]; 170 171 $p += strlen($matches[0]); 172 } 173 elseif (!$this->parseMacros($source, $p)) { 174 return false; 175 } 176 177 $pos = $p; 178 179 return true; 180 } 181 182 /** 183 * Parse macros. 184 * 185 * @param string $source 186 * @param int $pos 187 * 188 * @return bool 189 */ 190 private function parseMacros($source, &$pos) { 191 if ($this->options['usermacros'] && $this->user_macro_parser->parse($source, $pos) !== CParser::PARSE_FAIL) { 192 $pos += $this->user_macro_parser->length; 193 } 194 elseif ($this->options['lldmacros'] && $this->lld_macro_parser->parse($source, $pos) !== CParser::PARSE_FAIL) { 195 $pos += $this->lld_macro_parser->length; 196 } 197 elseif ($this->options['lldmacros'] 198 && $this->lld_macro_function_parser->parse($source, $pos) !== CParser::PARSE_FAIL) { 199 $pos += $this->lld_macro_function_parser->length; 200 } 201 else { 202 return false; 203 } 204 205 return true; 206 } 207 /** 208 * Returns an array of tokens. 209 * 210 * @return array 211 */ 212 public function getTokens() { 213 return $this->tokens; 214 } 215 216 /** 217 * Timestamp is returned as initialized DateTime object. Returns null when timestamp is not valid. 218 * 219 * @param bool $is_start If set to true date will be modified to lowest value, example (now/w) will be returned 220 * as Monday of this week. When set to false precisiion will modify date to highest value, 221 * same example will return Sunday of this week. 222 * 223 * @return DateTime|null 224 */ 225 public function getDateTime($is_start) { 226 if ($this->match === '') { 227 return null; 228 } 229 230 $date = new DateTime('now'); 231 232 foreach ($this->getTokens() as $token) { 233 switch ($token['type']) { 234 case CRelativeTimeParser::ZBX_TOKEN_PRECISION: 235 if ($token['suffix'] === 'm' || $token['suffix'] === 'h' || $token['suffix'] === 'd') { 236 $formats = $is_start 237 ? [ 238 'd' => 'Y-m-d 00:00:00', 239 'm' => 'Y-m-d H:i:00', 240 'h' => 'Y-m-d H:00:00' 241 ] 242 : [ 243 'd' => 'Y-m-d 23:59:59', 244 'm' => 'Y-m-d H:i:59', 245 'h' => 'Y-m-d H:59:59' 246 ]; 247 248 $date = new DateTime($date->format($formats[$token['suffix']])); 249 } 250 else { 251 $modifiers = $is_start 252 ? [ 253 'w' => 'Monday this week 00:00:00', 254 'M' => 'first day of this month 00:00:00', 255 'y' => 'first day of January this year 00:00:00' 256 ] 257 : [ 258 'w' => 'Sunday this week 23:59:59', 259 'M' => 'last day of this month 23:59:59', 260 'y' => 'last day of December this year 23:59:59' 261 ]; 262 263 $date->modify($modifiers[$token['suffix']]); 264 } 265 break; 266 267 case CRelativeTimeParser::ZBX_TOKEN_OFFSET: 268 $units = [ 269 's' => 'second', 270 'm' => 'minute', 271 'h' => 'hour', 272 'd' => 'day', 273 'w' => 'week', 274 'M' => 'month', 275 'y' => 'year' 276 ]; 277 278 $date->modify($token['sign'].$token['value'].' '.$units[$token['suffix']]); 279 break; 280 } 281 } 282 283 return $date; 284 } 285} 286