[ Index ] |
PHP Cross Reference of phpBB-3.2.11-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * @package s9e\TextFormatter 5 * @copyright Copyright (c) 2010-2019 The s9e Authors 6 * @license http://www.opensource.org/licenses/mit-license.php The MIT License 7 */ 8 namespace s9e\TextFormatter\Plugins\BBCodes\Configurator; 9 use Exception; 10 use InvalidArgumentException; 11 use RuntimeException; 12 use s9e\TextFormatter\Configurator; 13 use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder; 14 use s9e\TextFormatter\Configurator\Items\Attribute; 15 use s9e\TextFormatter\Configurator\Items\ProgrammableCallback; 16 use s9e\TextFormatter\Configurator\Items\Tag; 17 use s9e\TextFormatter\Configurator\Items\Template; 18 class BBCodeMonkey 19 { 20 const REGEXP = '(.).*?(?<!\\\\)(?>\\\\\\\\)*+\\g{-1}[DSUisu]*'; 21 public $allowedFilters = [ 22 'addslashes', 23 'dechex', 24 'intval', 25 'json_encode', 26 'ltrim', 27 'mb_strtolower', 28 'mb_strtoupper', 29 'rawurlencode', 30 'rtrim', 31 'str_rot13', 32 'stripslashes', 33 'strrev', 34 'strtolower', 35 'strtotime', 36 'strtoupper', 37 'trim', 38 'ucfirst', 39 'ucwords', 40 'urlencode' 41 ]; 42 protected $configurator; 43 public $tokenRegexp = [ 44 'ANYTHING' => '[\\s\\S]*?', 45 'COLOR' => '[a-zA-Z]+|#[0-9a-fA-F]+', 46 'EMAIL' => '[^@]+@.+?', 47 'FLOAT' => '(?>0|-?[1-9]\\d*)(?>\\.\\d+)?(?>e[1-9]\\d*)?', 48 'ID' => '[-a-zA-Z0-9_]+', 49 'IDENTIFIER' => '[-a-zA-Z0-9_]+', 50 'INT' => '0|-?[1-9]\\d*', 51 'INTEGER' => '0|-?[1-9]\\d*', 52 'NUMBER' => '\\d+', 53 'RANGE' => '\\d+', 54 'SIMPLETEXT' => '[-a-zA-Z0-9+.,_ ]+', 55 'TEXT' => '[\\s\\S]*?', 56 'UINT' => '0|[1-9]\\d*' 57 ]; 58 public $unfilteredTokens = [ 59 'ANYTHING', 60 'TEXT' 61 ]; 62 public function __construct(Configurator $configurator) 63 { 64 $this->configurator = $configurator; 65 } 66 public function create($usage, $template) 67 { 68 $config = $this->parse($usage); 69 if (!($template instanceof Template)) 70 $template = new Template($template); 71 $template->replaceTokens( 72 '#\\{(?:[A-Z]+[A-Z_0-9]*|@[-\\w]+)\\}#', 73 function ($m) use ($config) 74 { 75 $tokenId = \substr($m[0], 1, -1); 76 if ($tokenId[0] === '@') 77 return ['expression', $tokenId]; 78 if (isset($config['tokens'][$tokenId])) 79 return ['expression', '@' . $config['tokens'][$tokenId]]; 80 if ($tokenId === $config['passthroughToken']) 81 return ['passthrough']; 82 if ($this->isFilter($tokenId)) 83 throw new RuntimeException('Token {' . $tokenId . '} is ambiguous or undefined'); 84 return ['expression', '$' . $tokenId]; 85 } 86 ); 87 $return = [ 88 'bbcode' => $config['bbcode'], 89 'bbcodeName' => $config['bbcodeName'], 90 'tag' => $config['tag'] 91 ]; 92 $return['tag']->template = $template; 93 return $return; 94 } 95 protected function parse($usage) 96 { 97 $tag = new Tag; 98 $bbcode = new BBCode; 99 $config = [ 100 'tag' => $tag, 101 'bbcode' => $bbcode, 102 'passthroughToken' => \null 103 ]; 104 $usage = \preg_replace_callback( 105 '#(\\{(?>HASH)?MAP=)([^:]+:[^,;}]+(?>,[^:]+:[^,;}]+)*)(?=[;}])#', 106 function ($m) 107 { 108 return $m[1] . \base64_encode($m[2]); 109 }, 110 $usage 111 ); 112 $usage = \preg_replace_callback( 113 '#(\\{(?:PARSE|REGEXP)=)(' . self::REGEXP . '(?:,' . self::REGEXP . ')*)#', 114 function ($m) 115 { 116 return $m[1] . \base64_encode($m[2]); 117 }, 118 $usage 119 ); 120 $regexp = '(^' 121 . '\\[(?<bbcodeName>\\S+?)' 122 . '(?<defaultAttribute>=.+?)?' 123 . '(?<attributes>(?:\\s+[^=]+=\\S+?)*?)?' 124 . '\\s*(?:/?\\]|\\]\\s*(?<content>.*?)\\s*(?<endTag>\\[/\\1]))$)i'; 125 if (!\preg_match($regexp, \trim($usage), $m)) 126 throw new InvalidArgumentException('Cannot interpret the BBCode definition'); 127 $config['bbcodeName'] = BBCode::normalizeName($m['bbcodeName']); 128 $definitions = \preg_split('#\\s+#', \trim($m['attributes']), -1, \PREG_SPLIT_NO_EMPTY); 129 if (!empty($m['defaultAttribute'])) 130 \array_unshift($definitions, $m['bbcodeName'] . $m['defaultAttribute']); 131 if (!empty($m['content'])) 132 { 133 $regexp = '#^\\{' . RegexpBuilder::fromList($this->unfilteredTokens) . '[0-9]*\\}$#D'; 134 if (\preg_match($regexp, $m['content'])) 135 $config['passthroughToken'] = \substr($m['content'], 1, -1); 136 else 137 { 138 $definitions[] = 'content=' . $m['content']; 139 $bbcode->contentAttributes[] = 'content'; 140 } 141 } 142 $attributeDefinitions = []; 143 foreach ($definitions as $definition) 144 { 145 $pos = \strpos($definition, '='); 146 $name = \substr($definition, 0, $pos); 147 $value = \preg_replace('(^"(.*?)")s', '$1', \substr($definition, 1 + $pos)); 148 $value = \preg_replace_callback( 149 '#(\\{(?>HASHMAP|MAP|PARSE|REGEXP)=)([A-Za-z0-9+/]+=*)#', 150 function ($m) 151 { 152 return $m[1] . \base64_decode($m[2]); 153 }, 154 $value 155 ); 156 if ($name[0] === '$') 157 { 158 $optionName = \substr($name, 1); 159 $object = ($optionName === 'nestingLimit' || $optionName === 'tagLimit') ? $tag : $bbcode; 160 $object->$optionName = $this->convertValue($value); 161 } 162 elseif ($name[0] === '#') 163 { 164 $ruleName = \substr($name, 1); 165 foreach (\explode(',', $value) as $value) 166 $tag->rules->$ruleName($this->convertValue($value)); 167 } 168 else 169 { 170 $attrName = \strtolower(\trim($name)); 171 $attributeDefinitions[] = [$attrName, $value]; 172 } 173 } 174 $tokens = $this->addAttributes($attributeDefinitions, $bbcode, $tag); 175 if (isset($tokens[$config['passthroughToken']])) 176 $config['passthroughToken'] = \null; 177 $config['tokens'] = \array_filter($tokens); 178 return $config; 179 } 180 protected function addAttributes(array $definitions, BBCode $bbcode, Tag $tag) 181 { 182 $composites = []; 183 $table = []; 184 foreach ($definitions as $_e874cdc7) 185 { 186 list($attrName, $definition) = $_e874cdc7; 187 if (!isset($bbcode->defaultAttribute)) 188 $bbcode->defaultAttribute = $attrName; 189 $tokens = $this->parseTokens($definition); 190 if (empty($tokens)) 191 throw new RuntimeException('No valid tokens found in ' . $attrName . "'s definition " . $definition); 192 if ($tokens[0]['content'] === $definition) 193 { 194 $token = $tokens[0]; 195 if ($token['type'] === 'PARSE') 196 foreach ($token['regexps'] as $regexp) 197 $tag->attributePreprocessors->add($attrName, $regexp); 198 elseif (isset($tag->attributes[$attrName])) 199 throw new RuntimeException("Attribute '" . $attrName . "' is declared twice"); 200 else 201 { 202 if (!empty($token['options']['useContent'])) 203 $bbcode->contentAttributes[] = $attrName; 204 unset($token['options']['useContent']); 205 $tag->attributes[$attrName] = $this->generateAttribute($token); 206 $tokenId = $token['id']; 207 $table[$tokenId] = (isset($table[$tokenId])) 208 ? \false 209 : $attrName; 210 } 211 } 212 else 213 $composites[] = [$attrName, $definition, $tokens]; 214 } 215 foreach ($composites as $_2d84f0a0) 216 { 217 list($attrName, $definition, $tokens) = $_2d84f0a0; 218 $regexp = '/^'; 219 $lastPos = 0; 220 $usedTokens = []; 221 foreach ($tokens as $token) 222 { 223 $tokenId = $token['id']; 224 $tokenType = $token['type']; 225 if ($tokenType === 'PARSE') 226 throw new RuntimeException('{PARSE} tokens can only be used has the sole content of an attribute'); 227 if (isset($usedTokens[$tokenId])) 228 throw new RuntimeException('Token {' . $tokenId . '} used multiple times in attribute ' . $attrName . "'s definition"); 229 $usedTokens[$tokenId] = 1; 230 if (isset($table[$tokenId])) 231 { 232 $matchName = $table[$tokenId]; 233 if ($matchName === \false) 234 throw new RuntimeException('Token {' . $tokenId . "} used in attribute '" . $attrName . "' is ambiguous"); 235 } 236 else 237 { 238 $i = 0; 239 do 240 { 241 $matchName = $attrName . $i; 242 ++$i; 243 } 244 while (isset($tag->attributes[$matchName])); 245 $attribute = $tag->attributes->add($matchName); 246 if (!\in_array($tokenType, $this->unfilteredTokens, \true)) 247 { 248 $filter = $this->configurator->attributeFilters->get('#' . \strtolower($tokenType)); 249 $attribute->filterChain->append($filter); 250 } 251 $table[$tokenId] = $matchName; 252 } 253 $literal = \preg_quote(\substr($definition, $lastPos, $token['pos'] - $lastPos), '/'); 254 $literal = \preg_replace('(\\s+)', '\\s+', $literal); 255 $regexp .= $literal; 256 $expr = (isset($this->tokenRegexp[$tokenType])) 257 ? $this->tokenRegexp[$tokenType] 258 : '.+?'; 259 $regexp .= '(?<' . $matchName . '>' . $expr . ')'; 260 $lastPos = $token['pos'] + \strlen($token['content']); 261 } 262 $regexp .= \preg_quote(\substr($definition, $lastPos), '/') . '$/D'; 263 $tag->attributePreprocessors->add($attrName, $regexp); 264 } 265 $newAttributes = []; 266 foreach ($tag->attributePreprocessors as $attributePreprocessor) 267 foreach ($attributePreprocessor->getAttributes() as $attrName => $regexp) 268 { 269 if (isset($tag->attributes[$attrName])) 270 continue; 271 if (isset($newAttributes[$attrName]) 272 && $newAttributes[$attrName] !== $regexp) 273 throw new RuntimeException("Ambiguous attribute '" . $attrName . "' created using different regexps needs to be explicitly defined"); 274 $newAttributes[$attrName] = $regexp; 275 } 276 foreach ($newAttributes as $attrName => $regexp) 277 { 278 $filter = $this->configurator->attributeFilters->get('#regexp'); 279 $tag->attributes->add($attrName)->filterChain->append($filter)->setRegexp($regexp); 280 } 281 return $table; 282 } 283 protected function convertValue($value) 284 { 285 if ($value === 'true') 286 return \true; 287 if ($value === 'false') 288 return \false; 289 return $value; 290 } 291 protected function parseTokens($definition) 292 { 293 $tokenTypes = [ 294 'choice' => 'CHOICE[0-9]*=(?<choices>.+?)', 295 'map' => '(?:HASH)?MAP[0-9]*=(?<map>.+?)', 296 'parse' => 'PARSE=(?<regexps>' . self::REGEXP . '(?:,' . self::REGEXP . ')*)', 297 'range' => 'RANGE[0-9]*=(?<min>-?[0-9]+),(?<max>-?[0-9]+)', 298 'regexp' => 'REGEXP[0-9]*=(?<regexp>' . self::REGEXP . ')', 299 'other' => '(?<other>[A-Z_]+[0-9]*)' 300 ]; 301 \preg_match_all( 302 '#\\{(' . \implode('|', $tokenTypes) . ')(?<options>\\??(?:;[^;]*)*)\\}#', 303 $definition, 304 $matches, 305 \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE 306 ); 307 $tokens = []; 308 foreach ($matches as $m) 309 { 310 if (isset($m['other'][0]) 311 && \preg_match('#^(?:CHOICE|HASHMAP|MAP|REGEXP|PARSE|RANGE)#', $m['other'][0])) 312 throw new RuntimeException("Malformed token '" . $m['other'][0] . "'"); 313 $token = [ 314 'pos' => $m[0][1], 315 'content' => $m[0][0], 316 'options' => (isset($m['options'][0])) ? $this->parseOptionString($m['options'][0]) : [] 317 ]; 318 $head = $m[1][0]; 319 $pos = \strpos($head, '='); 320 if ($pos === \false) 321 $token['id'] = $head; 322 else 323 { 324 $token['id'] = \substr($head, 0, $pos); 325 foreach ($m as $k => $v) 326 if (!\is_numeric($k) && $k !== 'options' && $v[1] !== -1) 327 $token[$k] = $v[0]; 328 } 329 $token['type'] = \rtrim($token['id'], '0123456789'); 330 if ($token['type'] === 'PARSE') 331 { 332 \preg_match_all('#' . self::REGEXP . '(?:,|$)#', $token['regexps'], $m); 333 $regexps = []; 334 foreach ($m[0] as $regexp) 335 $regexps[] = \rtrim($regexp, ','); 336 $token['regexps'] = $regexps; 337 } 338 $tokens[] = $token; 339 } 340 return $tokens; 341 } 342 protected function generateAttribute(array $token) 343 { 344 $attribute = new Attribute; 345 if (isset($token['options']['preFilter'])) 346 { 347 $this->appendFilters($attribute, $token['options']['preFilter']); 348 unset($token['options']['preFilter']); 349 } 350 if ($token['type'] === 'REGEXP') 351 { 352 $filter = $this->configurator->attributeFilters->get('#regexp'); 353 $attribute->filterChain->append($filter)->setRegexp($token['regexp']); 354 } 355 elseif ($token['type'] === 'RANGE') 356 { 357 $filter = $this->configurator->attributeFilters->get('#range'); 358 $attribute->filterChain->append($filter)->setRange($token['min'], $token['max']); 359 } 360 elseif ($token['type'] === 'CHOICE') 361 { 362 $filter = $this->configurator->attributeFilters->get('#choice'); 363 $attribute->filterChain->append($filter)->setValues( 364 \explode(',', $token['choices']), 365 !empty($token['options']['caseSensitive']) 366 ); 367 unset($token['options']['caseSensitive']); 368 } 369 elseif ($token['type'] === 'HASHMAP' || $token['type'] === 'MAP') 370 { 371 $map = []; 372 foreach (\explode(',', $token['map']) as $pair) 373 { 374 $pos = \strpos($pair, ':'); 375 if ($pos === \false) 376 throw new RuntimeException("Invalid map assignment '" . $pair . "'"); 377 $map[\substr($pair, 0, $pos)] = \substr($pair, 1 + $pos); 378 } 379 if ($token['type'] === 'HASHMAP') 380 { 381 $filter = $this->configurator->attributeFilters->get('#hashmap'); 382 $attribute->filterChain->append($filter)->setMap( 383 $map, 384 !empty($token['options']['strict']) 385 ); 386 } 387 else 388 { 389 $filter = $this->configurator->attributeFilters->get('#map'); 390 $attribute->filterChain->append($filter)->setMap( 391 $map, 392 !empty($token['options']['caseSensitive']), 393 !empty($token['options']['strict']) 394 ); 395 } 396 unset($token['options']['caseSensitive']); 397 unset($token['options']['strict']); 398 } 399 elseif (!\in_array($token['type'], $this->unfilteredTokens, \true)) 400 { 401 $filter = $this->configurator->attributeFilters->get('#' . $token['type']); 402 $attribute->filterChain->append($filter); 403 } 404 if (isset($token['options']['postFilter'])) 405 { 406 $this->appendFilters($attribute, $token['options']['postFilter']); 407 unset($token['options']['postFilter']); 408 } 409 if (isset($token['options']['required'])) 410 $token['options']['required'] = (bool) $token['options']['required']; 411 elseif (isset($token['options']['optional'])) 412 $token['options']['required'] = !$token['options']['optional']; 413 unset($token['options']['optional']); 414 foreach ($token['options'] as $k => $v) 415 $attribute->$k = $v; 416 return $attribute; 417 } 418 protected function appendFilters(Attribute $attribute, $filters) 419 { 420 foreach (\preg_split('#\\s*,\\s*#', $filters) as $filterName) 421 { 422 if (\substr($filterName, 0, 1) !== '#' 423 && !\in_array($filterName, $this->allowedFilters, \true)) 424 throw new RuntimeException("Filter '" . $filterName . "' is not allowed in BBCodes"); 425 $filter = $this->configurator->attributeFilters->get($filterName); 426 $attribute->filterChain->append($filter); 427 } 428 } 429 protected function isFilter($tokenId) 430 { 431 $filterName = \rtrim($tokenId, '0123456789'); 432 if (\in_array($filterName, $this->unfilteredTokens, \true)) 433 return \true; 434 try 435 { 436 if ($this->configurator->attributeFilters->get('#' . $filterName)) 437 return \true; 438 } 439 catch (Exception $e) 440 { 441 } 442 return \false; 443 } 444 protected function parseOptionString($string) 445 { 446 $string = \preg_replace('(^\\?)', ';optional', $string); 447 $options = []; 448 foreach (\preg_split('#;+#', $string, -1, \PREG_SPLIT_NO_EMPTY) as $pair) 449 { 450 $pos = \strpos($pair, '='); 451 if ($pos === \false) 452 { 453 $k = $pair; 454 $v = \true; 455 } 456 else 457 { 458 $k = \substr($pair, 0, $pos); 459 $v = \substr($pair, 1 + $pos); 460 } 461 $options[$k] = $v; 462 } 463 return $options; 464 } 465 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |