Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
BBCodeMonkey.php
001 <?php
002
003 /*
004 * @package s9e\TextFormatter
005 * @copyright Copyright (c) 2010-2016 The s9e Authors
006 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
007 */
008 namespace s9e\TextFormatter\Plugins\BBCodes\Configurator;
009 use Exception;
010 use InvalidArgumentException;
011 use RuntimeException;
012 use s9e\TextFormatter\Configurator;
013 use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
014 use s9e\TextFormatter\Configurator\Items\Attribute;
015 use s9e\TextFormatter\Configurator\Items\ProgrammableCallback;
016 use s9e\TextFormatter\Configurator\Items\Tag;
017 use s9e\TextFormatter\Configurator\Items\Template;
018 class BBCodeMonkey
019 {
020 const REGEXP = '(.).*?(?<!\\\\)(?>\\\\\\\\)*+\\g{-1}[DSUisu]*';
021 public $allowedFilters = array(
022 'addslashes',
023 'dechex',
024 'intval',
025 'json_encode',
026 'ltrim',
027 'mb_strtolower',
028 'mb_strtoupper',
029 'rawurlencode',
030 'rtrim',
031 'str_rot13',
032 'stripslashes',
033 'strrev',
034 'strtolower',
035 'strtotime',
036 'strtoupper',
037 'trim',
038 'ucfirst',
039 'ucwords',
040 'urlencode'
041 );
042 protected $configurator;
043 public $tokenRegexp = array(
044 'COLOR' => '[a-zA-Z]+|#[0-9a-fA-F]+',
045 'EMAIL' => '[^@]+@.+?',
046 'FLOAT' => '(?>0|-?[1-9]\\d*)(?>\\.\\d+)?(?>e[1-9]\\d*)?',
047 'ID' => '[-a-zA-Z0-9_]+',
048 'IDENTIFIER' => '[-a-zA-Z0-9_]+',
049 'INT' => '0|-?[1-9]\\d*',
050 'INTEGER' => '0|-?[1-9]\\d*',
051 'NUMBER' => '\\d+',
052 'RANGE' => '\\d+',
053 'SIMPLETEXT' => '[-a-zA-Z0-9+.,_ ]+',
054 'UINT' => '0|[1-9]\\d*'
055 );
056 public $unfilteredTokens = array(
057 'ANYTHING',
058 'TEXT'
059 );
060 public function __construct(Configurator $configurator)
061 {
062 $this->configurator = $configurator;
063 }
064 public function create($usage, $template)
065 {
066 $_this = $this;
067 $config = $this->parse($usage);
068 if (!($template instanceof Template))
069 $template = new Template($template);
070 $template->replaceTokens(
071 '#\\{(?:[A-Z]+[A-Z_0-9]*|@[-\\w]+)\\}#',
072 function ($m) use ($config, $_this)
073 {
074 $tokenId = \substr($m[0], 1, -1);
075 if ($tokenId[0] === '@')
076 return array('expression', $tokenId);
077 if (isset($config['tokens'][$tokenId]))
078 return array('expression', '@' . $config['tokens'][$tokenId]);
079 if ($tokenId === $config['passthroughToken'])
080 return array('passthrough');
081 if ($_this->isFilter($tokenId))
082 throw new RuntimeException('Token {' . $tokenId . '} is ambiguous or undefined');
083 return array('expression', '$' . $tokenId);
084 }
085 );
086 $return = array(
087 'bbcode' => $config['bbcode'],
088 'bbcodeName' => $config['bbcodeName'],
089 'tag' => $config['tag']
090 );
091 $return['tag']->template = $template;
092 return $return;
093 }
094 protected function parse($usage)
095 {
096 $tag = new Tag;
097 $bbcode = new BBCode;
098 $config = array(
099 'tag' => $tag,
100 'bbcode' => $bbcode,
101 'passthroughToken' => \null
102 );
103 $usage = \preg_replace_callback(
104 '#(\\{(?>HASH)?MAP=)([^:]+:[^,;}]+(?>,[^:]+:[^,;}]+)*)(?=[;}])#',
105 function ($m)
106 {
107 return $m[1] . \base64_encode($m[2]);
108 },
109 $usage
110 );
111 $usage = \preg_replace_callback(
112 '#(\\{(?:PARSE|REGEXP)=)(' . self::REGEXP . '(?:,' . self::REGEXP . ')*)#',
113 function ($m)
114 {
115 return $m[1] . \base64_encode($m[2]);
116 },
117 $usage
118 );
119 $regexp = '(^'
120 . '\\[(?<bbcodeName>\\S+?)'
121 . '(?<defaultAttribute>=\\S+?)?'
122 . '(?<attributes>(?:\\s+[^=]+=\\S+?)*?)?'
123 . '\\s*(?:/?\\]|\\]\\s*(?<content>.*?)\\s*(?<endTag>\\[/\\1]))$)i';
124 if (!\preg_match($regexp, \trim($usage), $m))
125 throw new InvalidArgumentException('Cannot interpret the BBCode definition');
126 $config['bbcodeName'] = BBCode::normalizeName($m['bbcodeName']);
127 $definitions = \preg_split('#\\s+#', \trim($m['attributes']), -1, \PREG_SPLIT_NO_EMPTY);
128 if (!empty($m['defaultAttribute']))
129 \array_unshift($definitions, $m['bbcodeName'] . $m['defaultAttribute']);
130 if (!empty($m['content']))
131 {
132 $regexp = '#^\\{' . RegexpBuilder::fromList($this->unfilteredTokens) . '[0-9]*\\}$#D';
133 if (\preg_match($regexp, $m['content']))
134 $config['passthroughToken'] = \substr($m['content'], 1, -1);
135 else
136 {
137 $definitions[] = 'content=' . $m['content'];
138 $bbcode->contentAttributes[] = 'content';
139 }
140 }
141 $attributeDefinitions = array();
142 foreach ($definitions as $definition)
143 {
144 $pos = \strpos($definition, '=');
145 $name = \substr($definition, 0, $pos);
146 $value = \preg_replace('(^"(.*?)")s', '$1', \substr($definition, 1 + $pos));
147 $value = \preg_replace_callback(
148 '#(\\{(?>HASHMAP|MAP|PARSE|REGEXP)=)([A-Za-z0-9+/]+=*)#',
149 function ($m)
150 {
151 return $m[1] . \base64_decode($m[2]);
152 },
153 $value
154 );
155 if ($name[0] === '$')
156 {
157 $optionName = \substr($name, 1);
158 $bbcode->$optionName = $this->convertValue($value);
159 }
160 elseif ($name[0] === '#')
161 {
162 $ruleName = \substr($name, 1);
163 foreach (\explode(',', $value) as $value)
164 $tag->rules->$ruleName($this->convertValue($value));
165 }
166 else
167 {
168 $attrName = \strtolower(\trim($name));
169 $attributeDefinitions[] = array($attrName, $value);
170 }
171 }
172 $tokens = $this->addAttributes($attributeDefinitions, $bbcode, $tag);
173 if (isset($tokens[$config['passthroughToken']]))
174 $config['passthroughToken'] = \null;
175 $config['tokens'] = \array_filter($tokens);
176 return $config;
177 }
178 protected function addAttributes(array $definitions, BBCode $bbcode, Tag $tag)
179 {
180 $composites = array();
181 $table = array();
182 foreach ($definitions as $_e874cdc7)
183 {
184 list($attrName, $definition) = $_e874cdc7;
185 if (!isset($bbcode->defaultAttribute))
186 $bbcode->defaultAttribute = $attrName;
187 $tokens = self::parseTokens($definition);
188 if (empty($tokens))
189 throw new RuntimeException('No valid tokens found in ' . $attrName . "'s definition " . $definition);
190 if ($tokens[0]['content'] === $definition)
191 {
192 $token = $tokens[0];
193 if ($token['type'] === 'PARSE')
194 foreach ($token['regexps'] as $regexp)
195 $tag->attributePreprocessors->add($attrName, $regexp);
196 elseif (isset($tag->attributes[$attrName]))
197 throw new RuntimeException("Attribute '" . $attrName . "' is declared twice");
198 else
199 {
200 if (!empty($token['options']['useContent']))
201 $bbcode->contentAttributes[] = $attrName;
202 unset($token['options']['useContent']);
203 $tag->attributes[$attrName] = $this->generateAttribute($token);
204 $tokenId = $token['id'];
205 $table[$tokenId] = (isset($table[$tokenId]))
206 ? \false
207 : $attrName;
208 }
209 }
210 else
211 $composites[] = array($attrName, $definition, $tokens);
212 }
213 foreach ($composites as $_2d84f0a0)
214 {
215 list($attrName, $definition, $tokens) = $_2d84f0a0;
216 $regexp = '/^';
217 $lastPos = 0;
218 $usedTokens = array();
219 foreach ($tokens as $token)
220 {
221 $tokenId = $token['id'];
222 $tokenType = $token['type'];
223 if ($tokenType === 'PARSE')
224 throw new RuntimeException('{PARSE} tokens can only be used has the sole content of an attribute');
225 if (isset($usedTokens[$tokenId]))
226 throw new RuntimeException('Token {' . $tokenId . '} used multiple times in attribute ' . $attrName . "'s definition");
227 $usedTokens[$tokenId] = 1;
228 if (isset($table[$tokenId]))
229 {
230 $matchName = $table[$tokenId];
231 if ($matchName === \false)
232 throw new RuntimeException('Token {' . $tokenId . "} used in attribute '" . $attrName . "' is ambiguous");
233 }
234 else
235 {
236 $i = 0;
237 do
238 {
239 $matchName = $attrName . $i;
240 ++$i;
241 }
242 while (isset($tag->attributes[$matchName]));
243 $attribute = $tag->attributes->add($matchName);
244 if (!\in_array($tokenType, $this->unfilteredTokens, \true))
245 {
246 $filter = $this->configurator->attributeFilters->get('#' . \strtolower($tokenType));
247 $attribute->filterChain->append($filter);
248 }
249 $table[$tokenId] = $matchName;
250 }
251 $regexp .= \preg_quote(\substr($definition, $lastPos, $token['pos'] - $lastPos), '/');
252 $expr = (isset($this->tokenRegexp[$tokenType]))
253 ? $this->tokenRegexp[$tokenType]
254 : '.+?';
255 $regexp .= '(?<' . $matchName . '>' . $expr . ')';
256 $lastPos = $token['pos'] + \strlen($token['content']);
257 }
258 $regexp .= \preg_quote(\substr($definition, $lastPos), '/') . '$/D';
259 $tag->attributePreprocessors->add($attrName, $regexp);
260 }
261 $newAttributes = array();
262 foreach ($tag->attributePreprocessors as $attributePreprocessor)
263 foreach ($attributePreprocessor->getAttributes() as $attrName => $regexp)
264 {
265 if (isset($tag->attributes[$attrName]))
266 continue;
267 if (isset($newAttributes[$attrName])
268 && $newAttributes[$attrName] !== $regexp)
269 throw new RuntimeException("Ambiguous attribute '" . $attrName . "' created using different regexps needs to be explicitly defined");
270 $newAttributes[$attrName] = $regexp;
271 }
272 foreach ($newAttributes as $attrName => $regexp)
273 {
274 $filter = $this->configurator->attributeFilters->get('#regexp');
275 $tag->attributes->add($attrName)->filterChain->append($filter)->setRegexp($regexp);
276 }
277 return $table;
278 }
279 protected function convertValue($value)
280 {
281 if ($value === 'true')
282 return \true;
283 if ($value === 'false')
284 return \false;
285 return $value;
286 }
287 protected static function parseTokens($definition)
288 {
289 $tokenTypes = array(
290 'choice' => 'CHOICE[0-9]*=(?<choices>.+?)',
291 'map' => '(?:HASH)?MAP[0-9]*=(?<map>.+?)',
292 'parse' => 'PARSE=(?<regexps>' . self::REGEXP . '(?:,' . self::REGEXP . ')*)',
293 'range' => 'RAN(?:DOM|GE)[0-9]*=(?<min>-?[0-9]+),(?<max>-?[0-9]+)',
294 'regexp' => 'REGEXP[0-9]*=(?<regexp>' . self::REGEXP . ')',
295 'other' => '(?<other>[A-Z_]+[0-9]*)'
296 );
297 \preg_match_all(
298 '#\\{(' . \implode('|', $tokenTypes) . ')(?<options>(?:;[^;]*)*)\\}#',
299 $definition,
300 $matches,
301 \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE
302 );
303 $tokens = array();
304 foreach ($matches as $m)
305 {
306 if (isset($m['other'][0])
307 && \preg_match('#^(?:CHOICE|HASHMAP|MAP|REGEXP|PARSE|RANDOM|RANGE)#', $m['other'][0]))
308 throw new RuntimeException("Malformed token '" . $m['other'][0] . "'");
309 $token = array(
310 'pos' => $m[0][1],
311 'content' => $m[0][0],
312 'options' => array()
313 );
314 $head = $m[1][0];
315 $pos = \strpos($head, '=');
316 if ($pos === \false)
317 $token['id'] = $head;
318 else
319 {
320 $token['id'] = \substr($head, 0, $pos);
321 foreach ($m as $k => $v)
322 if (!\is_numeric($k) && $k !== 'options' && $v[1] !== -1)
323 $token[$k] = $v[0];
324 }
325 $token['type'] = \rtrim($token['id'], '0123456789');
326 $options = (isset($m['options'][0])) ? $m['options'][0] : '';
327 foreach (\preg_split('#;+#', $options, -1, \PREG_SPLIT_NO_EMPTY) as $pair)
328 {
329 $pos = \strpos($pair, '=');
330 if ($pos === \false)
331 {
332 $k = $pair;
333 $v = \true;
334 }
335 else
336 {
337 $k = \substr($pair, 0, $pos);
338 $v = \substr($pair, 1 + $pos);
339 }
340 $token['options'][$k] = $v;
341 }
342 if ($token['type'] === 'PARSE')
343 {
344 \preg_match_all('#' . self::REGEXP . '(?:,|$)#', $token['regexps'], $m);
345 $regexps = array();
346 foreach ($m[0] as $regexp)
347 $regexps[] = \rtrim($regexp, ',');
348 $token['regexps'] = $regexps;
349 }
350 $tokens[] = $token;
351 }
352 return $tokens;
353 }
354 protected function generateAttribute(array $token)
355 {
356 $attribute = new Attribute;
357 if (isset($token['options']['preFilter']))
358 {
359 $this->appendFilters($attribute, $token['options']['preFilter']);
360 unset($token['options']['preFilter']);
361 }
362 if ($token['type'] === 'REGEXP')
363 {
364 $filter = $this->configurator->attributeFilters->get('#regexp');
365 $attribute->filterChain->append($filter)->setRegexp($token['regexp']);
366 }
367 elseif ($token['type'] === 'RANGE')
368 {
369 $filter = $this->configurator->attributeFilters->get('#range');
370 $attribute->filterChain->append($filter)->setRange($token['min'], $token['max']);
371 }
372 elseif ($token['type'] === 'RANDOM')
373 {
374 $attribute->generator = new ProgrammableCallback('mt_rand');
375 $attribute->generator->addParameterByValue((int) $token['min']);
376 $attribute->generator->addParameterByValue((int) $token['max']);
377 }
378 elseif ($token['type'] === 'CHOICE')
379 {
380 $filter = $this->configurator->attributeFilters->get('#choice');
381 $attribute->filterChain->append($filter)->setValues(
382 \explode(',', $token['choices']),
383 !empty($token['options']['caseSensitive'])
384 );
385 unset($token['options']['caseSensitive']);
386 }
387 elseif ($token['type'] === 'HASHMAP' || $token['type'] === 'MAP')
388 {
389 $map = array();
390 foreach (\explode(',', $token['map']) as $pair)
391 {
392 $pos = \strpos($pair, ':');
393 if ($pos === \false)
394 throw new RuntimeException("Invalid map assignment '" . $pair . "'");
395 $map[\substr($pair, 0, $pos)] = \substr($pair, 1 + $pos);
396 }
397 if ($token['type'] === 'HASHMAP')
398 {
399 $filter = $this->configurator->attributeFilters->get('#hashmap');
400 $attribute->filterChain->append($filter)->setMap(
401 $map,
402 !empty($token['options']['strict'])
403 );
404 }
405 else
406 {
407 $filter = $this->configurator->attributeFilters->get('#map');
408 $attribute->filterChain->append($filter)->setMap(
409 $map,
410 !empty($token['options']['caseSensitive']),
411 !empty($token['options']['strict'])
412 );
413 }
414 unset($token['options']['caseSensitive']);
415 unset($token['options']['strict']);
416 }
417 elseif (!\in_array($token['type'], $this->unfilteredTokens, \true))
418 {
419 $filter = $this->configurator->attributeFilters->get('#' . $token['type']);
420 $attribute->filterChain->append($filter);
421 }
422 if (isset($token['options']['postFilter']))
423 {
424 $this->appendFilters($attribute, $token['options']['postFilter']);
425 unset($token['options']['postFilter']);
426 }
427 if (isset($token['options']['required']))
428 $token['options']['required'] = (bool) $token['options']['required'];
429 elseif (isset($token['options']['optional']))
430 $token['options']['required'] = !$token['options']['optional'];
431 unset($token['options']['optional']);
432 foreach ($token['options'] as $k => $v)
433 $attribute->$k = $v;
434 return $attribute;
435 }
436 protected function appendFilters(Attribute $attribute, $filters)
437 {
438 foreach (\preg_split('#\\s*,\\s*#', $filters) as $filterName)
439 {
440 if (\substr($filterName, 0, 1) !== '#'
441 && !\in_array($filterName, $this->allowedFilters, \true))
442 throw new RuntimeException("Filter '" . $filterName . "' is not allowed");
443 $filter = $this->configurator->attributeFilters->get($filterName);
444 $attribute->filterChain->append($filter);
445 }
446 }
447 public function isFilter($tokenId)
448 {
449 $filterName = \rtrim($tokenId, '0123456789');
450 if (\in_array($filterName, $this->unfilteredTokens, \true))
451 return \true;
452 try
453 {
454 if ($this->configurator->attributeFilters->get('#' . $filterName))
455 return \true;
456 }
457 catch (Exception $e)
458 {
459 }
460 return \false;
461 }
462 }