Complex classes like BBCodeMonkey often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use BBCodeMonkey, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 20 | class BBCodeMonkey |
||
| 21 | { |
||
| 22 | /** |
||
| 23 | * Expression that matches a regexp such as /foo/i |
||
| 24 | */ |
||
| 25 | const REGEXP = '(.).*?(?<!\\\\)(?>\\\\\\\\)*+\\g{-1}[DSUisu]*'; |
||
| 26 | |||
| 27 | /** |
||
| 28 | * @var array List of pre- and post- filters that are explicitly allowed in BBCode definitions. |
||
| 29 | * We use a whitelist approach because there are so many different risky callbacks |
||
| 30 | * that it would be too easy to let something dangerous slip by, e.g.: unlink, |
||
| 31 | * system, etc... |
||
| 32 | */ |
||
| 33 | public $allowedFilters = [ |
||
| 34 | 'addslashes', |
||
| 35 | 'dechex', |
||
| 36 | 'intval', |
||
| 37 | 'json_encode', |
||
| 38 | 'ltrim', |
||
| 39 | 'mb_strtolower', |
||
| 40 | 'mb_strtoupper', |
||
| 41 | 'rawurlencode', |
||
| 42 | 'rtrim', |
||
| 43 | 'str_rot13', |
||
| 44 | 'stripslashes', |
||
| 45 | 'strrev', |
||
| 46 | 'strtolower', |
||
| 47 | 'strtotime', |
||
| 48 | 'strtoupper', |
||
| 49 | 'trim', |
||
| 50 | 'ucfirst', |
||
| 51 | 'ucwords', |
||
| 52 | 'urlencode' |
||
| 53 | ]; |
||
| 54 | |||
| 55 | /** |
||
| 56 | * @var Configurator Instance of Configurator; |
||
| 57 | */ |
||
| 58 | protected $configurator; |
||
| 59 | |||
| 60 | /** |
||
| 61 | * @var array Regexps used in the named subpatterns generated automatically for composite |
||
| 62 | * attributes. For instance, "foo={NUMBER},{NUMBER}" will be transformed into |
||
| 63 | * 'foo={PARSE=#^(?<foo0>\\d+),(?<foo1>\\d+)$#D}' |
||
| 64 | */ |
||
| 65 | public $tokenRegexp = [ |
||
| 66 | 'COLOR' => '[a-zA-Z]+|#[0-9a-fA-F]+', |
||
| 67 | 'EMAIL' => '[^@]+@.+?', |
||
| 68 | 'FLOAT' => '(?>0|-?[1-9]\\d*)(?>\\.\\d+)?(?>e[1-9]\\d*)?', |
||
| 69 | 'ID' => '[-a-zA-Z0-9_]+', |
||
| 70 | 'IDENTIFIER' => '[-a-zA-Z0-9_]+', |
||
| 71 | 'INT' => '0|-?[1-9]\\d*', |
||
| 72 | 'INTEGER' => '0|-?[1-9]\\d*', |
||
| 73 | 'NUMBER' => '\\d+', |
||
| 74 | 'RANGE' => '\\d+', |
||
| 75 | 'SIMPLETEXT' => '[-a-zA-Z0-9+.,_ ]+', |
||
| 76 | 'UINT' => '0|[1-9]\\d*' |
||
| 77 | ]; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * @var array List of token types that are used to represent raw, unfiltered content |
||
| 81 | */ |
||
| 82 | public $unfilteredTokens = [ |
||
| 83 | 'ANYTHING', |
||
| 84 | 'TEXT' |
||
| 85 | ]; |
||
| 86 | |||
| 87 | /** |
||
| 88 | * Constructor |
||
| 89 | * |
||
| 90 | * @param Configurator $configurator Instance of Configurator |
||
| 91 | */ |
||
| 92 | 91 | public function __construct(Configurator $configurator) |
|
| 96 | |||
| 97 | /** |
||
| 98 | * Create a BBCode and its underlying tag and template(s) based on its reference usage |
||
| 99 | * |
||
| 100 | * @param string $usage BBCode usage, e.g. [B]{TEXT}[/b] |
||
| 101 | * @param string|Template $template BBCode's template |
||
| 102 | * @return array An array containing three elements: 'bbcode', 'bbcodeName' |
||
| 103 | * and 'tag' |
||
| 104 | */ |
||
| 105 | 90 | public function create($usage, $template) |
|
| 106 | { |
||
| 107 | // Parse the BBCode usage |
||
| 108 | 90 | $config = $this->parse($usage); |
|
| 109 | |||
| 110 | // Create a template object for manipulation |
||
| 111 | 76 | if (!($template instanceof Template)) |
|
| 112 | 76 | { |
|
| 113 | 75 | $template = new Template($template); |
|
| 114 | 75 | } |
|
| 115 | |||
| 116 | // Replace the passthrough token in the BBCode's template |
||
| 117 | 76 | $template->replaceTokens( |
|
| 118 | 76 | '#\\{(?:[A-Z]+[A-Z_0-9]*|@[-\\w]+)\\}#', |
|
| 119 | function ($m) use ($config) |
||
| 120 | { |
||
| 121 | 27 | $tokenId = substr($m[0], 1, -1); |
|
| 122 | |||
| 123 | // Acknowledge {@foo} as an XPath expression even outside of attribute value |
||
| 124 | // templates |
||
| 125 | 27 | if ($tokenId[0] === '@') |
|
| 126 | 27 | { |
|
| 127 | 1 | return ['expression', $tokenId]; |
|
| 128 | } |
||
| 129 | |||
| 130 | // Test whether this is a known token |
||
| 131 | 26 | if (isset($config['tokens'][$tokenId])) |
|
| 132 | 26 | { |
|
| 133 | // Replace with the corresponding attribute |
||
| 134 | 12 | return ['expression', '@' . $config['tokens'][$tokenId]]; |
|
| 135 | } |
||
| 136 | |||
| 137 | // Test whether the token is used as passthrough |
||
| 138 | 17 | if ($tokenId === $config['passthroughToken']) |
|
| 139 | 17 | { |
|
| 140 | 11 | return ['passthrough']; |
|
| 141 | } |
||
| 142 | |||
| 143 | // Undefined token. If it's the name of a filter, consider it's an error |
||
| 144 | 6 | if ($this->isFilter($tokenId)) |
|
| 145 | 6 | { |
|
| 146 | 4 | throw new RuntimeException('Token {' . $tokenId . '} is ambiguous or undefined'); |
|
| 147 | } |
||
| 148 | |||
| 149 | // Use the token's name as parameter name |
||
| 150 | 2 | return ['expression', '$' . $tokenId]; |
|
| 151 | } |
||
| 152 | 76 | ); |
|
| 153 | |||
| 154 | // Prepare the return array |
||
| 155 | $return = [ |
||
| 156 | 72 | 'bbcode' => $config['bbcode'], |
|
| 157 | 72 | 'bbcodeName' => $config['bbcodeName'], |
|
| 158 | 72 | 'tag' => $config['tag'] |
|
| 159 | 72 | ]; |
|
| 160 | |||
| 161 | // Set the template for this BBCode's tag |
||
| 162 | 72 | $return['tag']->template = $template; |
|
| 163 | |||
| 164 | 72 | return $return; |
|
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Create a BBCode based on its reference usage |
||
| 169 | * |
||
| 170 | * @param string $usage BBCode usage, e.g. [B]{TEXT}[/b] |
||
| 171 | * @return array |
||
| 172 | */ |
||
| 173 | 90 | protected function parse($usage) |
|
| 174 | { |
||
| 175 | 90 | $tag = new Tag; |
|
| 176 | 90 | $bbcode = new BBCode; |
|
| 177 | |||
| 178 | // This is the config we will return |
||
| 179 | $config = [ |
||
| 180 | 90 | 'tag' => $tag, |
|
| 181 | 90 | 'bbcode' => $bbcode, |
|
| 182 | 'passthroughToken' => null |
||
| 183 | 90 | ]; |
|
| 184 | |||
| 185 | // Encode maps to avoid special characters to interfere with definitions |
||
| 186 | 90 | $usage = preg_replace_callback( |
|
| 187 | 90 | '#(\\{(?>HASH)?MAP=)([^:]+:[^,;}]+(?>,[^:]+:[^,;}]+)*)(?=[;}])#', |
|
| 188 | function ($m) |
||
| 189 | { |
||
| 190 | 8 | return $m[1] . base64_encode($m[2]); |
|
| 191 | 90 | }, |
|
| 192 | $usage |
||
| 193 | 90 | ); |
|
| 194 | |||
| 195 | // Encode regexps to avoid special characters to interfere with definitions |
||
| 196 | 90 | $usage = preg_replace_callback( |
|
| 197 | 90 | '#(\\{(?:PARSE|REGEXP)=)(' . self::REGEXP . '(?:,' . self::REGEXP . ')*)#', |
|
| 198 | function ($m) |
||
| 199 | { |
||
| 200 | 14 | return $m[1] . base64_encode($m[2]); |
|
| 201 | 90 | }, |
|
| 202 | $usage |
||
| 203 | 90 | ); |
|
| 204 | |||
| 205 | $regexp = '(^' |
||
| 206 | // [BBCODE |
||
| 207 | . '\\[(?<bbcodeName>\\S+?)' |
||
| 208 | // ={TOKEN} |
||
| 209 | 90 | . '(?<defaultAttribute>=\\S+?)?' |
|
| 210 | // foo={TOKEN} bar={TOKEN1},{TOKEN2} |
||
| 211 | 90 | . '(?<attributes>(?:\\s+[^=]+=\\S+?)*?)?' |
|
| 212 | // ] or /] or ]{TOKEN}[/BBCODE] |
||
| 213 | 90 | . '\\s*(?:/?\\]|\\]\\s*(?<content>.*?)\\s*(?<endTag>\\[/\\1]))' |
|
| 214 | 90 | . '$)i'; |
|
| 215 | |||
| 216 | 90 | if (!preg_match($regexp, trim($usage), $m)) |
|
| 217 | 90 | { |
|
| 218 | 1 | throw new InvalidArgumentException('Cannot interpret the BBCode definition'); |
|
| 219 | } |
||
| 220 | |||
| 221 | // Save the BBCode's name |
||
| 222 | 89 | $config['bbcodeName'] = BBCode::normalizeName($m['bbcodeName']); |
|
| 223 | |||
| 224 | // Prepare the attributes definition, e.g. "foo={BAR}" |
||
| 225 | 88 | $definitions = preg_split('#\\s+#', trim($m['attributes']), -1, PREG_SPLIT_NO_EMPTY); |
|
| 226 | |||
| 227 | // If there's a default attribute, we prepend it to the list using the BBCode's name as |
||
| 228 | // attribute name |
||
| 229 | 88 | if (!empty($m['defaultAttribute'])) |
|
| 230 | 88 | { |
|
| 231 | 45 | array_unshift($definitions, $m['bbcodeName'] . $m['defaultAttribute']); |
|
| 232 | 45 | } |
|
| 233 | |||
| 234 | // Append the content token to the attributes list under the name "content" if it's anything |
||
| 235 | // but raw {TEXT} (or other unfiltered tokens) |
||
| 236 | 88 | if (!empty($m['content'])) |
|
| 237 | 88 | { |
|
| 238 | 38 | $regexp = '#^\\{' . RegexpBuilder::fromList($this->unfilteredTokens) . '[0-9]*\\}$#D'; |
|
| 239 | |||
| 240 | 38 | if (preg_match($regexp, $m['content'])) |
|
| 241 | 38 | { |
|
| 242 | 33 | $config['passthroughToken'] = substr($m['content'], 1, -1); |
|
| 243 | 33 | } |
|
| 244 | else |
||
| 245 | { |
||
| 246 | 5 | $definitions[] = 'content=' . $m['content']; |
|
| 247 | 5 | $bbcode->contentAttributes[] = 'content'; |
|
| 248 | } |
||
| 249 | 38 | } |
|
| 250 | |||
| 251 | // Separate the attribute definitions from the BBCode options |
||
| 252 | 88 | $attributeDefinitions = []; |
|
| 253 | 88 | foreach ($definitions as $definition) |
|
| 254 | { |
||
| 255 | 70 | $pos = strpos($definition, '='); |
|
| 256 | 70 | $name = substr($definition, 0, $pos); |
|
| 257 | 70 | $value = substr($definition, 1 + $pos); |
|
| 258 | |||
| 259 | // Decode base64-encoded tokens |
||
| 260 | 70 | $value = preg_replace_callback( |
|
| 261 | 70 | '#(\\{(?>HASHMAP|MAP|PARSE|REGEXP)=)([A-Za-z0-9+/]+=*)#', |
|
| 262 | 70 | function ($m) |
|
| 263 | { |
||
| 264 | 24 | return $m[1] . base64_decode($m[2]); |
|
| 265 | 70 | }, |
|
| 266 | $value |
||
| 267 | 70 | ); |
|
| 268 | |||
| 269 | // If name starts with $ then it's a BBCode options, if it starts with # it's a rule and |
||
| 270 | // otherwise it's an attribute definition |
||
| 271 | 70 | if ($name[0] === '$') |
|
| 272 | 70 | { |
|
| 273 | 3 | $optionName = substr($name, 1); |
|
| 274 | 3 | $bbcode->$optionName = $this->convertValue($value); |
|
| 275 | 3 | } |
|
| 276 | 67 | elseif ($name[0] === '#') |
|
| 277 | { |
||
| 278 | 4 | $ruleName = substr($name, 1); |
|
| 279 | |||
| 280 | // Supports #denyChild=foo,bar |
||
| 281 | 4 | foreach (explode(',', $value) as $value) |
|
| 282 | { |
||
| 283 | 4 | $tag->rules->$ruleName($this->convertValue($value)); |
|
| 284 | 4 | } |
|
| 285 | 4 | } |
|
| 286 | else |
||
| 287 | { |
||
| 288 | 63 | $attrName = strtolower(trim($name)); |
|
| 289 | 63 | $attributeDefinitions[] = [$attrName, $value]; |
|
| 290 | } |
||
| 291 | 88 | } |
|
| 292 | |||
| 293 | // Add the attributes and get the token translation table |
||
| 294 | 88 | $tokens = $this->addAttributes($attributeDefinitions, $bbcode, $tag); |
|
| 295 | |||
| 296 | // Test whether the passthrough token is used for something else, in which case we need |
||
| 297 | // to unset it |
||
| 298 | 76 | if (isset($tokens[$config['passthroughToken']])) |
|
| 299 | 76 | { |
|
| 300 | 2 | $config['passthroughToken'] = null; |
|
| 301 | 2 | } |
|
| 302 | |||
| 303 | // Add the list of known (and only the known) tokens to the config |
||
| 304 | 76 | $config['tokens'] = array_filter($tokens); |
|
| 305 | |||
| 306 | 76 | return $config; |
|
| 307 | } |
||
| 308 | |||
| 309 | /** |
||
| 310 | * Parse a string of attribute definitions and add the attributes/options to the tag/BBCode |
||
| 311 | * |
||
| 312 | * Attributes come in two forms. Most commonly, in the form of a single token, e.g. |
||
| 313 | * [a href={URL} title={TEXT}] |
||
| 314 | * |
||
| 315 | * Sometimes, however, we need to parse more than one single token. For instance, the phpBB |
||
| 316 | * [FLASH] BBCode uses two tokens separated by a comma: |
||
| 317 | * [flash={NUMBER},{NUMBER}]{URL}[/flash] |
||
| 318 | * |
||
| 319 | * In addition, some custom BBCodes circulating for phpBB use a combination of token and static |
||
| 320 | * text such as: |
||
| 321 | * [youtube]http://www.youtube.com/watch?v={SIMPLETEXT}[/youtube] |
||
| 322 | * |
||
| 323 | * Any attribute that is not a single token is implemented as an attribute preprocessor, with |
||
| 324 | * each token generating a matching attribute. Tentatively, those of those attributes are |
||
| 325 | * created by taking the attribute preprocessor's name and appending a unique number counting the |
||
| 326 | * number of created attributes. In the [FLASH] example above, an attribute preprocessor named |
||
| 327 | * "flash" would be created as well as two attributes named "flash0" and "flash1" respectively. |
||
| 328 | * |
||
| 329 | * @link https://www.phpbb.com/community/viewtopic.php?f=46&t=2127991 |
||
| 330 | * @link https://www.phpbb.com/community/viewtopic.php?f=46&t=579376 |
||
| 331 | * |
||
| 332 | * @param array $definitions List of attributes definitions as [[name, definition]*] |
||
| 333 | * @param BBCode $bbcode Owner BBCode |
||
| 334 | * @param Tag $tag Owner tag |
||
| 335 | * @return array Array of [token id => attribute name] where FALSE in place of the |
||
| 336 | * name indicates that the token is ambiguous (e.g. used multiple |
||
| 337 | * times) |
||
| 338 | */ |
||
| 339 | 88 | protected function addAttributes(array $definitions, BBCode $bbcode, Tag $tag) |
|
| 534 | |||
| 535 | /** |
||
| 536 | * Convert a human-readable value to a typed PHP value |
||
| 537 | * |
||
| 538 | * @param string $value Original value |
||
| 539 | * @return bool|string Converted value |
||
| 540 | */ |
||
| 541 | 7 | protected function convertValue($value) |
|
| 542 | { |
||
| 543 | 7 | if ($value === 'true') |
|
| 544 | 7 | { |
|
| 545 | 2 | return true; |
|
| 546 | } |
||
| 547 | |||
| 548 | 5 | if ($value === 'false') |
|
| 549 | 5 | { |
|
| 550 | 2 | return false; |
|
| 551 | } |
||
| 552 | |||
| 553 | 3 | return $value; |
|
| 554 | } |
||
| 555 | |||
| 556 | /** |
||
| 557 | * Parse and return all the tokens contained in a definition |
||
| 558 | * |
||
| 559 | * @param string $definition |
||
| 560 | * @return array |
||
| 561 | */ |
||
| 562 | 63 | protected static function parseTokens($definition) |
|
| 563 | { |
||
| 564 | $tokenTypes = [ |
||
| 565 | 63 | 'choice' => 'CHOICE[0-9]*=(?<choices>.+?)', |
|
| 566 | 63 | 'map' => '(?:HASH)?MAP[0-9]*=(?<map>.+?)', |
|
| 567 | 63 | 'parse' => 'PARSE=(?<regexps>' . self::REGEXP . '(?:,' . self::REGEXP . ')*)', |
|
| 568 | 63 | 'range' => 'RAN(?:DOM|GE)[0-9]*=(?<min>-?[0-9]+),(?<max>-?[0-9]+)', |
|
| 569 | 63 | 'regexp' => 'REGEXP[0-9]*=(?<regexp>' . self::REGEXP . ')', |
|
| 570 | 'other' => '(?<other>[A-Z_]+[0-9]*)' |
||
| 571 | 63 | ]; |
|
| 572 | |||
| 573 | // Capture the content of every token in that attribute's definition. Usually there will |
||
| 574 | // only be one, as in "foo={URL}" but some older BBCodes use a form of composite |
||
| 575 | // attributes such as [FLASH={NUMBER},{NUMBER}] |
||
| 576 | 63 | preg_match_all( |
|
| 577 | 63 | '#\\{(' . implode('|', $tokenTypes) . ')(?<options>(?:;[^;]*)*)\\}#', |
|
| 578 | 63 | $definition, |
|
| 579 | 63 | $matches, |
|
| 580 | 63 | PREG_SET_ORDER | PREG_OFFSET_CAPTURE |
|
| 581 | 63 | ); |
|
| 582 | |||
| 583 | 63 | $tokens = []; |
|
| 584 | 63 | foreach ($matches as $m) |
|
| 585 | { |
||
| 586 | 62 | if (isset($m['other'][0]) |
|
| 587 | 62 | && preg_match('#^(?:CHOICE|HASHMAP|MAP|REGEXP|PARSE|RANDOM|RANGE)#', $m['other'][0])) |
|
| 588 | 62 | { |
|
| 589 | 2 | throw new RuntimeException("Malformed token '" . $m['other'][0] . "'"); |
|
| 590 | } |
||
| 591 | |||
| 592 | $token = [ |
||
| 593 | 60 | 'pos' => $m[0][1], |
|
| 594 | 60 | 'content' => $m[0][0], |
|
| 595 | 60 | 'options' => [] |
|
| 596 | 60 | ]; |
|
| 597 | |||
| 598 | // Get this token's type by looking at the start of the match |
||
| 599 | 60 | $head = $m[1][0]; |
|
| 600 | 60 | $pos = strpos($head, '='); |
|
| 601 | |||
| 602 | 60 | if ($pos === false) |
|
| 603 | 60 | { |
|
| 604 | // {FOO} |
||
| 605 | 32 | $token['id'] = $head; |
|
| 606 | 32 | } |
|
| 607 | else |
||
| 608 | { |
||
| 609 | // {FOO=...} |
||
| 610 | 29 | $token['id'] = substr($head, 0, $pos); |
|
| 611 | |||
| 612 | // Copy the content of named subpatterns into the token's config |
||
| 613 | 29 | foreach ($m as $k => $v) |
|
| 614 | { |
||
| 615 | 29 | if (!is_numeric($k) && $k !== 'options' && $v[1] !== -1) |
|
| 616 | 29 | { |
|
| 617 | 29 | $token[$k] = $v[0]; |
|
| 618 | 29 | } |
|
| 619 | 29 | } |
|
| 620 | } |
||
| 621 | |||
| 622 | // The token's type is its id minus the number, e.g. NUMBER1 => NUMBER |
||
| 623 | 60 | $token['type'] = rtrim($token['id'], '0123456789'); |
|
| 624 | |||
| 625 | // Parse the options |
||
| 626 | 60 | $options = (isset($m['options'][0])) ? $m['options'][0] : ''; |
|
| 627 | 60 | foreach (preg_split('#;+#', $options, -1, PREG_SPLIT_NO_EMPTY) as $pair) |
|
| 628 | { |
||
| 629 | 16 | $pos = strpos($pair, '='); |
|
| 630 | |||
| 631 | 16 | if ($pos === false) |
|
| 632 | 16 | { |
|
| 633 | // Options with no value are set to true, e.g. {FOO;useContent} |
||
| 634 | 11 | $k = $pair; |
|
| 635 | 11 | $v = true; |
|
| 636 | 11 | } |
|
| 637 | else |
||
| 638 | { |
||
| 639 | 6 | $k = substr($pair, 0, $pos); |
|
| 640 | 6 | $v = substr($pair, 1 + $pos); |
|
| 641 | } |
||
| 642 | |||
| 643 | 16 | $token['options'][$k] = $v; |
|
| 644 | 60 | } |
|
| 645 | |||
| 646 | // {PARSE} tokens can have several regexps separated with commas, we split them up here |
||
| 647 | 60 | if ($token['type'] === 'PARSE') |
|
| 648 | 60 | { |
|
| 649 | // Match all occurences of a would-be regexp followed by a comma or the end of the |
||
| 650 | // string |
||
| 651 | 9 | preg_match_all('#' . self::REGEXP . '(?:,|$)#', $token['regexps'], $m); |
|
| 652 | |||
| 653 | 9 | $regexps = []; |
|
| 654 | 9 | foreach ($m[0] as $regexp) |
|
| 655 | { |
||
| 656 | // remove the potential comma at the end |
||
| 657 | 9 | $regexps[] = rtrim($regexp, ','); |
|
| 658 | 9 | } |
|
| 659 | |||
| 660 | 9 | $token['regexps'] = $regexps; |
|
| 661 | 9 | } |
|
| 662 | |||
| 663 | 60 | $tokens[] = $token; |
|
| 664 | 61 | } |
|
| 665 | |||
| 666 | 61 | return $tokens; |
|
| 667 | } |
||
| 668 | |||
| 669 | /** |
||
| 670 | * Generate an attribute based on a token |
||
| 671 | * |
||
| 672 | * @param array $token Token this attribute is based on |
||
| 673 | * @return Attribute |
||
| 674 | */ |
||
| 675 | 46 | protected function generateAttribute(array $token) |
|
| 676 | { |
||
| 677 | 46 | $attribute = new Attribute; |
|
| 678 | |||
| 679 | 46 | if (isset($token['options']['preFilter'])) |
|
| 680 | 46 | { |
|
| 681 | 2 | $this->appendFilters($attribute, $token['options']['preFilter']); |
|
| 682 | 1 | unset($token['options']['preFilter']); |
|
| 683 | 1 | } |
|
| 684 | |||
| 685 | 45 | if ($token['type'] === 'REGEXP') |
|
| 686 | 45 | { |
|
| 687 | 5 | $filter = $this->configurator->attributeFilters->get('#regexp'); |
|
| 688 | 5 | $attribute->filterChain->append($filter)->setRegexp($token['regexp']); |
|
| 689 | 5 | } |
|
| 690 | 40 | elseif ($token['type'] === 'RANGE') |
|
| 691 | { |
||
| 692 | 1 | $filter = $this->configurator->attributeFilters->get('#range'); |
|
| 693 | 1 | $attribute->filterChain->append($filter)->setRange($token['min'], $token['max']); |
|
| 694 | 1 | } |
|
| 695 | 39 | elseif ($token['type'] === 'RANDOM') |
|
| 696 | { |
||
| 697 | 1 | $attribute->generator = new ProgrammableCallback('mt_rand'); |
|
| 698 | 1 | $attribute->generator->addParameterByValue((int) $token['min']); |
|
| 699 | 1 | $attribute->generator->addParameterByValue((int) $token['max']); |
|
| 700 | 1 | } |
|
| 701 | 38 | elseif ($token['type'] === 'CHOICE') |
|
| 702 | { |
||
| 703 | 3 | $filter = $this->configurator->attributeFilters->get('#choice'); |
|
| 704 | 3 | $attribute->filterChain->append($filter)->setValues( |
|
| 705 | 3 | explode(',', $token['choices']), |
|
| 706 | 3 | !empty($token['options']['caseSensitive']) |
|
| 707 | 3 | ); |
|
| 708 | 3 | unset($token['options']['caseSensitive']); |
|
| 709 | 3 | } |
|
| 710 | 35 | elseif ($token['type'] === 'HASHMAP' || $token['type'] === 'MAP') |
|
| 711 | { |
||
| 712 | // Build the map from the string |
||
| 713 | 10 | $map = []; |
|
| 714 | 10 | foreach (explode(',', $token['map']) as $pair) |
|
| 715 | { |
||
| 716 | 10 | $pos = strpos($pair, ':'); |
|
| 717 | |||
| 718 | 10 | if ($pos === false) |
|
| 719 | 10 | { |
|
| 720 | 2 | throw new RuntimeException("Invalid map assignment '" . $pair . "'"); |
|
| 721 | } |
||
| 722 | |||
| 723 | 10 | $map[substr($pair, 0, $pos)] = substr($pair, 1 + $pos); |
|
| 724 | 10 | } |
|
| 725 | |||
| 726 | // Create the filter then append it to the attribute |
||
| 727 | 8 | if ($token['type'] === 'HASHMAP') |
|
| 728 | 8 | { |
|
| 729 | 2 | $filter = $this->configurator->attributeFilters->get('#hashmap'); |
|
| 730 | 2 | $attribute->filterChain->append($filter)->setMap( |
|
| 731 | 2 | $map, |
|
| 732 | 2 | !empty($token['options']['strict']) |
|
| 733 | 2 | ); |
|
| 734 | 2 | } |
|
| 735 | else |
||
| 736 | { |
||
| 737 | 6 | $filter = $this->configurator->attributeFilters->get('#map'); |
|
| 738 | 6 | $attribute->filterChain->append($filter)->setMap( |
|
| 739 | 6 | $map, |
|
| 740 | 6 | !empty($token['options']['caseSensitive']), |
|
| 741 | 6 | !empty($token['options']['strict']) |
|
| 742 | 6 | ); |
|
| 743 | } |
||
| 744 | |||
| 745 | // Remove options that are not needed anymore |
||
| 746 | 8 | unset($token['options']['caseSensitive']); |
|
| 747 | 8 | unset($token['options']['strict']); |
|
| 748 | 8 | } |
|
| 749 | 25 | elseif (!in_array($token['type'], $this->unfilteredTokens, true)) |
|
| 750 | { |
||
| 751 | 16 | $filter = $this->configurator->attributeFilters->get('#' . $token['type']); |
|
| 752 | 16 | $attribute->filterChain->append($filter); |
|
| 753 | 16 | } |
|
| 754 | |||
| 755 | 43 | if (isset($token['options']['postFilter'])) |
|
| 756 | 43 | { |
|
| 757 | 3 | $this->appendFilters($attribute, $token['options']['postFilter']); |
|
| 758 | 2 | unset($token['options']['postFilter']); |
|
| 759 | 2 | } |
|
| 760 | |||
| 761 | // Set the "required" option if "required" or "optional" is set, then remove |
||
| 762 | // the "optional" option |
||
| 763 | 42 | if (isset($token['options']['required'])) |
|
| 764 | 42 | { |
|
| 765 | 1 | $token['options']['required'] = (bool) $token['options']['required']; |
|
| 766 | 1 | } |
|
| 767 | 41 | elseif (isset($token['options']['optional'])) |
|
| 768 | { |
||
| 769 | 2 | $token['options']['required'] = !$token['options']['optional']; |
|
| 770 | 2 | } |
|
| 771 | 42 | unset($token['options']['optional']); |
|
| 772 | |||
| 773 | 42 | foreach ($token['options'] as $k => $v) |
|
| 774 | { |
||
| 775 | 3 | $attribute->$k = $v; |
|
| 776 | 42 | } |
|
| 777 | |||
| 778 | 42 | return $attribute; |
|
| 779 | } |
||
| 780 | |||
| 781 | /** |
||
| 782 | * Append a list of filters to an attribute's filterChain |
||
| 783 | * |
||
| 784 | * @param Attribute $attribute |
||
| 785 | * @param string $filters List of filters, separated with commas |
||
| 786 | * @return void |
||
| 787 | */ |
||
| 788 | 5 | protected function appendFilters(Attribute $attribute, $filters) |
|
| 802 | |||
| 803 | /** |
||
| 804 | * Test whether a token's name is the name of a filter |
||
| 805 | * |
||
| 806 | * @param string $tokenId Token ID, e.g. "TEXT1" |
||
| 807 | * @return bool |
||
| 808 | */ |
||
| 809 | 6 | protected function isFilter($tokenId) |
|
| 833 | } |