MediaWiki REL1_28
Preprocessor_Hash.php
Go to the documentation of this file.
1<?php
42// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
44 // @codingStandardsIgnoreEnd
45
49 public $parser;
50
51 const CACHE_PREFIX = 'preprocess-hash';
52 const CACHE_VERSION = 2;
53
54 public function __construct( $parser ) {
55 $this->parser = $parser;
56 }
57
61 public function newFrame() {
62 return new PPFrame_Hash( $this );
63 }
64
69 public function newCustomFrame( $args ) {
70 return new PPCustomFrame_Hash( $this, $args );
71 }
72
77 public function newPartNodeArray( $values ) {
78 $list = [];
79
80 foreach ( $values as $k => $val ) {
81 if ( is_int( $k ) ) {
82 $store = [ [ 'part', [
83 [ 'name', [ [ '@index', [ $k ] ] ] ],
84 [ 'value', [ strval( $val ) ] ],
85 ] ] ];
86 } else {
87 $store = [ [ 'part', [
88 [ 'name', [ strval( $k ) ] ],
89 '=',
90 [ 'value', [ strval( $val ) ] ],
91 ] ] ];
92 }
93
94 $list[] = new PPNode_Hash_Tree( $store, 0 );
95 }
96
97 $node = new PPNode_Hash_Array( $list );
98 return $node;
99 }
100
119 public function preprocessToObj( $text, $flags = 0 ) {
120 $tree = $this->cacheGetTree( $text, $flags );
121 if ( $tree !== false ) {
122 $store = json_decode( $tree );
123 if ( is_array( $store ) ) {
124 return new PPNode_Hash_Tree( $store, 0 );
125 }
126 }
127
128 $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
129
130 $xmlishElements = $this->parser->getStripList();
131 $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
132 $enableOnlyinclude = false;
133 if ( $forInclusion ) {
134 $ignoredTags = [ 'includeonly', '/includeonly' ];
135 $ignoredElements = [ 'noinclude' ];
136 $xmlishElements[] = 'noinclude';
137 if ( strpos( $text, '<onlyinclude>' ) !== false
138 && strpos( $text, '</onlyinclude>' ) !== false
139 ) {
140 $enableOnlyinclude = true;
141 }
142 } else {
143 $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
144 $ignoredElements = [ 'includeonly' ];
145 $xmlishElements[] = 'includeonly';
146 }
147 $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
148
149 // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
150 $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
151
152 $stack = new PPDStack_Hash;
153
154 $searchBase = "[{<\n";
155 // For fast reverse searches
156 $revText = strrev( $text );
157 $lengthText = strlen( $text );
158
159 // Input pointer, starts out pointing to a pseudo-newline before the start
160 $i = 0;
161 // Current accumulator. See the doc comment for Preprocessor_Hash for the format.
162 $accum =& $stack->getAccum();
163 // True to find equals signs in arguments
164 $findEquals = false;
165 // True to take notice of pipe characters
166 $findPipe = false;
167 $headingIndex = 1;
168 // True if $i is inside a possible heading
169 $inHeading = false;
170 // True if there are no more greater-than (>) signs right of $i
171 $noMoreGT = false;
172 // Map of tag name => true if there are no more closing tags of given type right of $i
173 $noMoreClosingTag = [];
174 // True to ignore all input up to the next <onlyinclude>
175 $findOnlyinclude = $enableOnlyinclude;
176 // Do a line-start run without outputting an LF character
177 $fakeLineStart = true;
178
179 while ( true ) {
180 // $this->memCheck();
181
182 if ( $findOnlyinclude ) {
183 // Ignore all input up to the next <onlyinclude>
184 $startPos = strpos( $text, '<onlyinclude>', $i );
185 if ( $startPos === false ) {
186 // Ignored section runs to the end
187 $accum[] = [ 'ignore', [ substr( $text, $i ) ] ];
188 break;
189 }
190 $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
191 $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
192 $i = $tagEndPos;
193 $findOnlyinclude = false;
194 }
195
196 if ( $fakeLineStart ) {
197 $found = 'line-start';
198 $curChar = '';
199 } else {
200 # Find next opening brace, closing brace or pipe
201 $search = $searchBase;
202 if ( $stack->top === false ) {
203 $currentClosing = '';
204 } else {
205 $currentClosing = $stack->top->close;
206 $search .= $currentClosing;
207 }
208 if ( $findPipe ) {
209 $search .= '|';
210 }
211 if ( $findEquals ) {
212 // First equals will be for the template
213 $search .= '=';
214 }
215 $rule = null;
216 # Output literal section, advance input counter
217 $literalLength = strcspn( $text, $search, $i );
218 if ( $literalLength > 0 ) {
219 self::addLiteral( $accum, substr( $text, $i, $literalLength ) );
220 $i += $literalLength;
221 }
222 if ( $i >= $lengthText ) {
223 if ( $currentClosing == "\n" ) {
224 // Do a past-the-end run to finish off the heading
225 $curChar = '';
226 $found = 'line-end';
227 } else {
228 # All done
229 break;
230 }
231 } else {
232 $curChar = $text[$i];
233 if ( $curChar == '|' ) {
234 $found = 'pipe';
235 } elseif ( $curChar == '=' ) {
236 $found = 'equals';
237 } elseif ( $curChar == '<' ) {
238 $found = 'angle';
239 } elseif ( $curChar == "\n" ) {
240 if ( $inHeading ) {
241 $found = 'line-end';
242 } else {
243 $found = 'line-start';
244 }
245 } elseif ( $curChar == $currentClosing ) {
246 $found = 'close';
247 } elseif ( isset( $this->rules[$curChar] ) ) {
248 $found = 'open';
249 $rule = $this->rules[$curChar];
250 } else {
251 # Some versions of PHP have a strcspn which stops on null characters
252 # Ignore and continue
253 ++$i;
254 continue;
255 }
256 }
257 }
258
259 if ( $found == 'angle' ) {
260 $matches = false;
261 // Handle </onlyinclude>
262 if ( $enableOnlyinclude
263 && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
264 ) {
265 $findOnlyinclude = true;
266 continue;
267 }
268
269 // Determine element name
270 if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
271 // Element name missing or not listed
272 self::addLiteral( $accum, '<' );
273 ++$i;
274 continue;
275 }
276 // Handle comments
277 if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
278
279 // To avoid leaving blank lines, when a sequence of
280 // space-separated comments is both preceded and followed by
281 // a newline (ignoring spaces), then
282 // trim leading and trailing spaces and the trailing newline.
283
284 // Find the end
285 $endPos = strpos( $text, '-->', $i + 4 );
286 if ( $endPos === false ) {
287 // Unclosed comment in input, runs to end
288 $inner = substr( $text, $i );
289 $accum[] = [ 'comment', [ $inner ] ];
290 $i = $lengthText;
291 } else {
292 // Search backwards for leading whitespace
293 $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
294
295 // Search forwards for trailing whitespace
296 // $wsEnd will be the position of the last space (or the '>' if there's none)
297 $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
298
299 // Keep looking forward as long as we're finding more
300 // comments.
301 $comments = [ [ $wsStart, $wsEnd ] ];
302 while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
303 $c = strpos( $text, '-->', $wsEnd + 4 );
304 if ( $c === false ) {
305 break;
306 }
307 $c = $c + 2 + strspn( $text, " \t", $c + 3 );
308 $comments[] = [ $wsEnd + 1, $c ];
309 $wsEnd = $c;
310 }
311
312 // Eat the line if possible
313 // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
314 // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
315 // it's a possible beneficial b/c break.
316 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
317 && substr( $text, $wsEnd + 1, 1 ) == "\n"
318 ) {
319 // Remove leading whitespace from the end of the accumulator
320 $wsLength = $i - $wsStart;
321 $endIndex = count( $accum ) - 1;
322
323 // Sanity check
324 if ( $wsLength > 0
325 && $endIndex >= 0
326 && is_string( $accum[$endIndex] )
327 && strspn( $accum[$endIndex], " \t", -$wsLength ) === $wsLength
328 ) {
329 $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
330 }
331
332 // Dump all but the last comment to the accumulator
333 foreach ( $comments as $j => $com ) {
334 $startPos = $com[0];
335 $endPos = $com[1] + 1;
336 if ( $j == ( count( $comments ) - 1 ) ) {
337 break;
338 }
339 $inner = substr( $text, $startPos, $endPos - $startPos );
340 $accum[] = [ 'comment', [ $inner ] ];
341 }
342
343 // Do a line-start run next time to look for headings after the comment
344 $fakeLineStart = true;
345 } else {
346 // No line to eat, just take the comment itself
347 $startPos = $i;
348 $endPos += 2;
349 }
350
351 if ( $stack->top ) {
352 $part = $stack->top->getCurrentPart();
353 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
354 $part->visualEnd = $wsStart;
355 }
356 // Else comments abutting, no change in visual end
357 $part->commentEnd = $endPos;
358 }
359 $i = $endPos + 1;
360 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
361 $accum[] = [ 'comment', [ $inner ] ];
362 }
363 continue;
364 }
365 $name = $matches[1];
366 $lowerName = strtolower( $name );
367 $attrStart = $i + strlen( $name ) + 1;
368
369 // Find end of tag
370 $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
371 if ( $tagEndPos === false ) {
372 // Infinite backtrack
373 // Disable tag search to prevent worst-case O(N^2) performance
374 $noMoreGT = true;
375 self::addLiteral( $accum, '<' );
376 ++$i;
377 continue;
378 }
379
380 // Handle ignored tags
381 if ( in_array( $lowerName, $ignoredTags ) ) {
382 $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
383 $i = $tagEndPos + 1;
384 continue;
385 }
386
387 $tagStartPos = $i;
388 if ( $text[$tagEndPos - 1] == '/' ) {
389 // Short end tag
390 $attrEnd = $tagEndPos - 1;
391 $inner = null;
392 $i = $tagEndPos + 1;
393 $close = null;
394 } else {
395 $attrEnd = $tagEndPos;
396 // Find closing tag
397 if (
398 !isset( $noMoreClosingTag[$name] ) &&
399 preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
400 $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
401 ) {
402 $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
403 $i = $matches[0][1] + strlen( $matches[0][0] );
404 $close = $matches[0][0];
405 } else {
406 // No end tag
407 if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
408 // Let it run out to the end of the text.
409 $inner = substr( $text, $tagEndPos + 1 );
410 $i = $lengthText;
411 $close = null;
412 } else {
413 // Don't match the tag, treat opening tag as literal and resume parsing.
414 $i = $tagEndPos + 1;
415 self::addLiteral( $accum,
416 substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
417 // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
418 $noMoreClosingTag[$name] = true;
419 continue;
420 }
421 }
422 }
423 // <includeonly> and <noinclude> just become <ignore> tags
424 if ( in_array( $lowerName, $ignoredElements ) ) {
425 $accum[] = [ 'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
426 continue;
427 }
428
429 if ( $attrEnd <= $attrStart ) {
430 $attr = '';
431 } else {
432 // Note that the attr element contains the whitespace between name and attribute,
433 // this is necessary for precise reconstruction during pre-save transform.
434 $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
435 }
436
437 $children = [
438 [ 'name', [ $name ] ],
439 [ 'attr', [ $attr ] ] ];
440 if ( $inner !== null ) {
441 $children[] = [ 'inner', [ $inner ] ];
442 }
443 if ( $close !== null ) {
444 $children[] = [ 'close', [ $close ] ];
445 }
446 $accum[] = [ 'ext', $children ];
447 } elseif ( $found == 'line-start' ) {
448 // Is this the start of a heading?
449 // Line break belongs before the heading element in any case
450 if ( $fakeLineStart ) {
451 $fakeLineStart = false;
452 } else {
453 self::addLiteral( $accum, $curChar );
454 $i++;
455 }
456
457 $count = strspn( $text, '=', $i, 6 );
458 if ( $count == 1 && $findEquals ) {
459 // DWIM: This looks kind of like a name/value separator.
460 // Let's let the equals handler have it and break the potential
461 // heading. This is heuristic, but AFAICT the methods for
462 // completely correct disambiguation are very complex.
463 } elseif ( $count > 0 ) {
464 $piece = [
465 'open' => "\n",
466 'close' => "\n",
467 'parts' => [ new PPDPart_Hash( str_repeat( '=', $count ) ) ],
468 'startPos' => $i,
469 'count' => $count ];
470 $stack->push( $piece );
471 $accum =& $stack->getAccum();
472 extract( $stack->getFlags() );
473 $i += $count;
474 }
475 } elseif ( $found == 'line-end' ) {
476 $piece = $stack->top;
477 // A heading must be open, otherwise \n wouldn't have been in the search list
478 assert( $piece->open === "\n" );
479 $part = $piece->getCurrentPart();
480 // Search back through the input to see if it has a proper close.
481 // Do this using the reversed string since the other solutions
482 // (end anchor, etc.) are inefficient.
483 $wsLength = strspn( $revText, " \t", $lengthText - $i );
484 $searchStart = $i - $wsLength;
485 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
486 // Comment found at line end
487 // Search for equals signs before the comment
488 $searchStart = $part->visualEnd;
489 $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
490 }
491 $count = $piece->count;
492 $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
493 if ( $equalsLength > 0 ) {
494 if ( $searchStart - $equalsLength == $piece->startPos ) {
495 // This is just a single string of equals signs on its own line
496 // Replicate the doHeadings behavior /={count}(.+)={count}/
497 // First find out how many equals signs there really are (don't stop at 6)
498 $count = $equalsLength;
499 if ( $count < 3 ) {
500 $count = 0;
501 } else {
502 $count = min( 6, intval( ( $count - 1 ) / 2 ) );
503 }
504 } else {
505 $count = min( $equalsLength, $count );
506 }
507 if ( $count > 0 ) {
508 // Normal match, output <h>
509 $element = [ [ 'possible-h',
510 array_merge(
511 [
512 [ '@level', [ $count ] ],
513 [ '@i', [ $headingIndex++ ] ]
514 ],
515 $accum
516 )
517 ] ];
518 } else {
519 // Single equals sign on its own line, count=0
520 $element = $accum;
521 }
522 } else {
523 // No match, no <h>, just pass down the inner text
524 $element = $accum;
525 }
526 // Unwind the stack
527 $stack->pop();
528 $accum =& $stack->getAccum();
529 extract( $stack->getFlags() );
530
531 // Append the result to the enclosing accumulator
532 array_splice( $accum, count( $accum ), 0, $element );
533
534 // Note that we do NOT increment the input pointer.
535 // This is because the closing linebreak could be the opening linebreak of
536 // another heading. Infinite loops are avoided because the next iteration MUST
537 // hit the heading open case above, which unconditionally increments the
538 // input pointer.
539 } elseif ( $found == 'open' ) {
540 # count opening brace characters
541 $count = strspn( $text, $curChar, $i );
542
543 # we need to add to stack only if opening brace count is enough for one of the rules
544 if ( $count >= $rule['min'] ) {
545 # Add it to the stack
546 $piece = [
547 'open' => $curChar,
548 'close' => $rule['end'],
549 'count' => $count,
550 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ),
551 ];
552
553 $stack->push( $piece );
554 $accum =& $stack->getAccum();
555 extract( $stack->getFlags() );
556 } else {
557 # Add literal brace(s)
558 self::addLiteral( $accum, str_repeat( $curChar, $count ) );
559 }
560 $i += $count;
561 } elseif ( $found == 'close' ) {
562 $piece = $stack->top;
563 # lets check if there are enough characters for closing brace
564 $maxCount = $piece->count;
565 $count = strspn( $text, $curChar, $i, $maxCount );
566
567 # check for maximum matching characters (if there are 5 closing
568 # characters, we will probably need only 3 - depending on the rules)
569 $rule = $this->rules[$piece->open];
570 if ( $count > $rule['max'] ) {
571 # The specified maximum exists in the callback array, unless the caller
572 # has made an error
573 $matchingCount = $rule['max'];
574 } else {
575 # Count is less than the maximum
576 # Skip any gaps in the callback array to find the true largest match
577 # Need to use array_key_exists not isset because the callback can be null
578 $matchingCount = $count;
579 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
580 --$matchingCount;
581 }
582 }
583
584 if ( $matchingCount <= 0 ) {
585 # No matching element found in callback array
586 # Output a literal closing brace and continue
587 self::addLiteral( $accum, str_repeat( $curChar, $count ) );
588 $i += $count;
589 continue;
590 }
591 $name = $rule['names'][$matchingCount];
592 if ( $name === null ) {
593 // No element, just literal text
594 $element = $piece->breakSyntax( $matchingCount );
595 self::addLiteral( $element, str_repeat( $rule['end'], $matchingCount ) );
596 } else {
597 # Create XML element
598 $parts = $piece->parts;
599 $titleAccum = $parts[0]->out;
600 unset( $parts[0] );
601
602 $children = [];
603
604 # The invocation is at the start of the line if lineStart is set in
605 # the stack, and all opening brackets are used up.
606 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
607 $children[] = [ '@lineStart', [ 1 ] ];
608 }
609 $titleNode = [ 'title', $titleAccum ];
610 $children[] = $titleNode;
611 $argIndex = 1;
612 foreach ( $parts as $part ) {
613 if ( isset( $part->eqpos ) ) {
614 $equalsNode = $part->out[$part->eqpos];
615 $nameNode = [ 'name', array_slice( $part->out, 0, $part->eqpos ) ];
616 $valueNode = [ 'value', array_slice( $part->out, $part->eqpos + 1 ) ];
617 $partNode = [ 'part', [ $nameNode, $equalsNode, $valueNode ] ];
618 $children[] = $partNode;
619 } else {
620 $nameNode = [ 'name', [ [ '@index', [ $argIndex++ ] ] ] ];
621 $valueNode = [ 'value', $part->out ];
622 $partNode = [ 'part', [ $nameNode, $valueNode ] ];
623 $children[] = $partNode;
624 }
625 }
626 $element = [ [ $name, $children ] ];
627 }
628
629 # Advance input pointer
630 $i += $matchingCount;
631
632 # Unwind the stack
633 $stack->pop();
634 $accum =& $stack->getAccum();
635
636 # Re-add the old stack element if it still has unmatched opening characters remaining
637 if ( $matchingCount < $piece->count ) {
638 $piece->parts = [ new PPDPart_Hash ];
639 $piece->count -= $matchingCount;
640 # do we still qualify for any callback with remaining count?
641 $min = $this->rules[$piece->open]['min'];
642 if ( $piece->count >= $min ) {
643 $stack->push( $piece );
644 $accum =& $stack->getAccum();
645 } else {
646 self::addLiteral( $accum, str_repeat( $piece->open, $piece->count ) );
647 }
648 }
649
650 extract( $stack->getFlags() );
651
652 # Add XML element to the enclosing accumulator
653 array_splice( $accum, count( $accum ), 0, $element );
654 } elseif ( $found == 'pipe' ) {
655 $findEquals = true; // shortcut for getFlags()
656 $stack->addPart();
657 $accum =& $stack->getAccum();
658 ++$i;
659 } elseif ( $found == 'equals' ) {
660 $findEquals = false; // shortcut for getFlags()
661 $accum[] = [ 'equals', [ '=' ] ];
662 $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
663 ++$i;
664 }
665 }
666
667 # Output any remaining unclosed brackets
668 foreach ( $stack->stack as $piece ) {
669 array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
670 }
671
672 # Enable top-level headings
673 foreach ( $stack->rootAccum as &$node ) {
674 if ( is_array( $node ) && $node[PPNode_Hash_Tree::NAME] === 'possible-h' ) {
675 $node[PPNode_Hash_Tree::NAME] = 'h';
676 }
677 }
678
679 $rootStore = [ [ 'root', $stack->rootAccum ] ];
680 $rootNode = new PPNode_Hash_Tree( $rootStore, 0 );
681
682 // Cache
683 $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
684 if ( $tree !== false ) {
685 $this->cacheSetTree( $text, $flags, $tree );
686 }
687
688 return $rootNode;
689 }
690
691 private static function addLiteral( array &$accum, $text ) {
692 $n = count( $accum );
693 if ( $n && is_string( $accum[$n - 1] ) ) {
694 $accum[$n - 1] .= $text;
695 } else {
696 $accum[] = $text;
697 }
698 }
699}
700
705// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
706class PPDStack_Hash extends PPDStack {
707 // @codingStandardsIgnoreEnd
708
709 public function __construct() {
710 $this->elementClass = 'PPDStackElement_Hash';
711 parent::__construct();
712 $this->rootAccum = [];
713 }
714}
715
719// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
721 // @codingStandardsIgnoreEnd
722
723 public function __construct( $data = [] ) {
724 $this->partClass = 'PPDPart_Hash';
725 parent::__construct( $data );
726 }
727
734 public function breakSyntax( $openingCount = false ) {
735 if ( $this->open == "\n" ) {
736 $accum = $this->parts[0]->out;
737 } else {
738 if ( $openingCount === false ) {
739 $openingCount = $this->count;
740 }
741 $accum = [ str_repeat( $this->open, $openingCount ) ];
742 $lastIndex = 0;
743 $first = true;
744 foreach ( $this->parts as $part ) {
745 if ( $first ) {
746 $first = false;
747 } elseif ( is_string( $accum[$lastIndex] ) ) {
748 $accum[$lastIndex] .= '|';
749 } else {
750 $accum[++$lastIndex] = '|';
751 }
752 foreach ( $part->out as $node ) {
753 if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
754 $accum[$lastIndex] .= $node;
755 } else {
756 $accum[++$lastIndex] = $node;
757 }
758 }
759 }
760 }
761 return $accum;
762 }
763}
764
768// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
769class PPDPart_Hash extends PPDPart {
770 // @codingStandardsIgnoreEnd
771
772 public function __construct( $out = '' ) {
773 if ( $out !== '' ) {
774 $accum = [ $out ];
775 } else {
776 $accum = [];
777 }
778 parent::__construct( $accum );
779 }
780}
781
786// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
787class PPFrame_Hash implements PPFrame {
788 // @codingStandardsIgnoreEnd
789
793 public $parser;
794
799
803 public $title;
805
811
816 public $depth;
817
818 private $volatile = false;
819 private $ttl = null;
820
825
830 public function __construct( $preprocessor ) {
831 $this->preprocessor = $preprocessor;
832 $this->parser = $preprocessor->parser;
833 $this->title = $this->parser->mTitle;
834 $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
835 $this->loopCheckHash = [];
836 $this->depth = 0;
837 $this->childExpansionCache = [];
838 }
839
850 public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
851 $namedArgs = [];
852 $numberedArgs = [];
853 if ( $title === false ) {
855 }
856 if ( $args !== false ) {
857 if ( $args instanceof PPNode_Hash_Array ) {
858 $args = $args->value;
859 } elseif ( !is_array( $args ) ) {
860 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
861 }
862 foreach ( $args as $arg ) {
863 $bits = $arg->splitArg();
864 if ( $bits['index'] !== '' ) {
865 // Numbered parameter
866 $index = $bits['index'] - $indexOffset;
867 if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
868 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
869 wfEscapeWikiText( $this->title ),
871 wfEscapeWikiText( $index ) )->text() );
872 $this->parser->addTrackingCategory( 'duplicate-args-category' );
873 }
874 $numberedArgs[$index] = $bits['value'];
875 unset( $namedArgs[$index] );
876 } else {
877 // Named parameter
878 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
879 if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
880 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
881 wfEscapeWikiText( $this->title ),
883 wfEscapeWikiText( $name ) )->text() );
884 $this->parser->addTrackingCategory( 'duplicate-args-category' );
885 }
886 $namedArgs[$name] = $bits['value'];
887 unset( $numberedArgs[$name] );
888 }
889 }
890 }
891 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
892 }
893
901 public function cachedExpand( $key, $root, $flags = 0 ) {
902 // we don't have a parent, so we don't have a cache
903 return $this->expand( $root, $flags );
904 }
905
912 public function expand( $root, $flags = 0 ) {
913 static $expansionDepth = 0;
914 if ( is_string( $root ) ) {
915 return $root;
916 }
917
918 if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
919 $this->parser->limitationWarn( 'node-count-exceeded',
920 $this->parser->mPPNodeCount,
921 $this->parser->mOptions->getMaxPPNodeCount()
922 );
923 return '<span class="error">Node-count limit exceeded</span>';
924 }
925 if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
926 $this->parser->limitationWarn( 'expansion-depth-exceeded',
927 $expansionDepth,
928 $this->parser->mOptions->getMaxPPExpandDepth()
929 );
930 return '<span class="error">Expansion depth limit exceeded</span>';
931 }
932 ++$expansionDepth;
933 if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
934 $this->parser->mHighestExpansionDepth = $expansionDepth;
935 }
936
937 $outStack = [ '', '' ];
938 $iteratorStack = [ false, $root ];
939 $indexStack = [ 0, 0 ];
940
941 while ( count( $iteratorStack ) > 1 ) {
942 $level = count( $outStack ) - 1;
943 $iteratorNode =& $iteratorStack[$level];
944 $out =& $outStack[$level];
945 $index =& $indexStack[$level];
946
947 if ( is_array( $iteratorNode ) ) {
948 if ( $index >= count( $iteratorNode ) ) {
949 // All done with this iterator
950 $iteratorStack[$level] = false;
951 $contextNode = false;
952 } else {
953 $contextNode = $iteratorNode[$index];
954 $index++;
955 }
956 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
957 if ( $index >= $iteratorNode->getLength() ) {
958 // All done with this iterator
959 $iteratorStack[$level] = false;
960 $contextNode = false;
961 } else {
962 $contextNode = $iteratorNode->item( $index );
963 $index++;
964 }
965 } else {
966 // Copy to $contextNode and then delete from iterator stack,
967 // because this is not an iterator but we do have to execute it once
968 $contextNode = $iteratorStack[$level];
969 $iteratorStack[$level] = false;
970 }
971
972 $newIterator = false;
973 $contextName = false;
974 $contextChildren = false;
975
976 if ( $contextNode === false ) {
977 // nothing to do
978 } elseif ( is_string( $contextNode ) ) {
979 $out .= $contextNode;
980 } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
981 $newIterator = $contextNode;
982 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
983 // No output
984 } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
985 $out .= $contextNode->value;
986 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
987 $contextName = $contextNode->name;
988 $contextChildren = $contextNode->getRawChildren();
989 } elseif ( is_array( $contextNode ) ) {
990 // Node descriptor array
991 if ( count( $contextNode ) !== 2 ) {
992 throw new MWException( __METHOD__.
993 ': found an array where a node descriptor should be' );
994 }
995 list( $contextName, $contextChildren ) = $contextNode;
996 } else {
997 throw new MWException( __METHOD__ . ': Invalid parameter type' );
998 }
999
1000 // Handle node descriptor array or tree object
1001 if ( $contextName === false ) {
1002 // Not a node, already handled above
1003 } elseif ( $contextName[0] === '@' ) {
1004 // Attribute: no output
1005 } elseif ( $contextName === 'template' ) {
1006 # Double-brace expansion
1007 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1008 if ( $flags & PPFrame::NO_TEMPLATES ) {
1009 $newIterator = $this->virtualBracketedImplode(
1010 '{{', '|', '}}',
1011 $bits['title'],
1012 $bits['parts']
1013 );
1014 } else {
1015 $ret = $this->parser->braceSubstitution( $bits, $this );
1016 if ( isset( $ret['object'] ) ) {
1017 $newIterator = $ret['object'];
1018 } else {
1019 $out .= $ret['text'];
1020 }
1021 }
1022 } elseif ( $contextName === 'tplarg' ) {
1023 # Triple-brace expansion
1024 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1025 if ( $flags & PPFrame::NO_ARGS ) {
1026 $newIterator = $this->virtualBracketedImplode(
1027 '{{{', '|', '}}}',
1028 $bits['title'],
1029 $bits['parts']
1030 );
1031 } else {
1032 $ret = $this->parser->argSubstitution( $bits, $this );
1033 if ( isset( $ret['object'] ) ) {
1034 $newIterator = $ret['object'];
1035 } else {
1036 $out .= $ret['text'];
1037 }
1038 }
1039 } elseif ( $contextName === 'comment' ) {
1040 # HTML-style comment
1041 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1042 # Not in RECOVER_COMMENTS mode (msgnw) though.
1043 if ( ( $this->parser->ot['html']
1044 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1047 ) {
1048 $out .= '';
1049 } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1050 # Add a strip marker in PST mode so that pstPass2() can
1051 # run some old-fashioned regexes on the result.
1052 # Not in RECOVER_COMMENTS mode (extractSections) though.
1053 $out .= $this->parser->insertStripItem( $contextChildren[0] );
1054 } else {
1055 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1056 $out .= $contextChildren[0];
1057 }
1058 } elseif ( $contextName === 'ignore' ) {
1059 # Output suppression used by <includeonly> etc.
1060 # OT_WIKI will only respect <ignore> in substed templates.
1061 # The other output types respect it unless NO_IGNORE is set.
1062 # extractSections() sets NO_IGNORE and so never respects it.
1063 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1064 || ( $flags & PPFrame::NO_IGNORE )
1065 ) {
1066 $out .= $contextChildren[0];
1067 } else {
1068 // $out .= '';
1069 }
1070 } elseif ( $contextName === 'ext' ) {
1071 # Extension tag
1072 $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
1073 [ 'attr' => null, 'inner' => null, 'close' => null ];
1074 if ( $flags & PPFrame::NO_TAGS ) {
1075 $s = '<' . $bits['name']->getFirstChild()->value;
1076 if ( $bits['attr'] ) {
1077 $s .= $bits['attr']->getFirstChild()->value;
1078 }
1079 if ( $bits['inner'] ) {
1080 $s .= '>' . $bits['inner']->getFirstChild()->value;
1081 if ( $bits['close'] ) {
1082 $s .= $bits['close']->getFirstChild()->value;
1083 }
1084 } else {
1085 $s .= '/>';
1086 }
1087 $out .= $s;
1088 } else {
1089 $out .= $this->parser->extensionSubstitution( $bits, $this );
1090 }
1091 } elseif ( $contextName === 'h' ) {
1092 # Heading
1093 if ( $this->parser->ot['html'] ) {
1094 # Expand immediately and insert heading index marker
1095 $s = $this->expand( $contextChildren, $flags );
1096 $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
1097 $titleText = $this->title->getPrefixedDBkey();
1098 $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
1099 $serial = count( $this->parser->mHeadings ) - 1;
1100 $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1101 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
1102 $this->parser->mStripState->addGeneral( $marker, '' );
1103 $out .= $s;
1104 } else {
1105 # Expand in virtual stack
1106 $newIterator = $contextChildren;
1107 }
1108 } else {
1109 # Generic recursive expansion
1110 $newIterator = $contextChildren;
1111 }
1112
1113 if ( $newIterator !== false ) {
1114 $outStack[] = '';
1115 $iteratorStack[] = $newIterator;
1116 $indexStack[] = 0;
1117 } elseif ( $iteratorStack[$level] === false ) {
1118 // Return accumulated value to parent
1119 // With tail recursion
1120 while ( $iteratorStack[$level] === false && $level > 0 ) {
1121 $outStack[$level - 1] .= $out;
1122 array_pop( $outStack );
1123 array_pop( $iteratorStack );
1124 array_pop( $indexStack );
1125 $level--;
1126 }
1127 }
1128 }
1129 --$expansionDepth;
1130 return $outStack[0];
1131 }
1132
1139 public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1140 $args = array_slice( func_get_args(), 2 );
1141
1142 $first = true;
1143 $s = '';
1144 foreach ( $args as $root ) {
1145 if ( $root instanceof PPNode_Hash_Array ) {
1146 $root = $root->value;
1147 }
1148 if ( !is_array( $root ) ) {
1149 $root = [ $root ];
1150 }
1151 foreach ( $root as $node ) {
1152 if ( $first ) {
1153 $first = false;
1154 } else {
1155 $s .= $sep;
1156 }
1157 $s .= $this->expand( $node, $flags );
1158 }
1159 }
1160 return $s;
1161 }
1162
1170 public function implode( $sep /*, ... */ ) {
1171 $args = array_slice( func_get_args(), 1 );
1172
1173 $first = true;
1174 $s = '';
1175 foreach ( $args as $root ) {
1176 if ( $root instanceof PPNode_Hash_Array ) {
1177 $root = $root->value;
1178 }
1179 if ( !is_array( $root ) ) {
1180 $root = [ $root ];
1181 }
1182 foreach ( $root as $node ) {
1183 if ( $first ) {
1184 $first = false;
1185 } else {
1186 $s .= $sep;
1187 }
1188 $s .= $this->expand( $node );
1189 }
1190 }
1191 return $s;
1192 }
1193
1202 public function virtualImplode( $sep /*, ... */ ) {
1203 $args = array_slice( func_get_args(), 1 );
1204 $out = [];
1205 $first = true;
1206
1207 foreach ( $args as $root ) {
1208 if ( $root instanceof PPNode_Hash_Array ) {
1209 $root = $root->value;
1210 }
1211 if ( !is_array( $root ) ) {
1212 $root = [ $root ];
1213 }
1214 foreach ( $root as $node ) {
1215 if ( $first ) {
1216 $first = false;
1217 } else {
1218 $out[] = $sep;
1219 }
1220 $out[] = $node;
1221 }
1222 }
1223 return new PPNode_Hash_Array( $out );
1224 }
1225
1235 public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1236 $args = array_slice( func_get_args(), 3 );
1237 $out = [ $start ];
1238 $first = true;
1239
1240 foreach ( $args as $root ) {
1241 if ( $root instanceof PPNode_Hash_Array ) {
1242 $root = $root->value;
1243 }
1244 if ( !is_array( $root ) ) {
1245 $root = [ $root ];
1246 }
1247 foreach ( $root as $node ) {
1248 if ( $first ) {
1249 $first = false;
1250 } else {
1251 $out[] = $sep;
1252 }
1253 $out[] = $node;
1254 }
1255 }
1256 $out[] = $end;
1257 return new PPNode_Hash_Array( $out );
1258 }
1259
1260 public function __toString() {
1261 return 'frame{}';
1262 }
1263
1268 public function getPDBK( $level = false ) {
1269 if ( $level === false ) {
1270 return $this->title->getPrefixedDBkey();
1271 } else {
1272 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
1273 }
1274 }
1275
1279 public function getArguments() {
1280 return [];
1281 }
1282
1286 public function getNumberedArguments() {
1287 return [];
1288 }
1289
1293 public function getNamedArguments() {
1294 return [];
1295 }
1296
1302 public function isEmpty() {
1303 return true;
1304 }
1305
1310 public function getArgument( $name ) {
1311 return false;
1312 }
1313
1321 public function loopCheck( $title ) {
1322 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1323 }
1324
1330 public function isTemplate() {
1331 return false;
1332 }
1333
1339 public function getTitle() {
1340 return $this->title;
1341 }
1342
1348 public function setVolatile( $flag = true ) {
1349 $this->volatile = $flag;
1350 }
1351
1357 public function isVolatile() {
1358 return $this->volatile;
1359 }
1360
1366 public function setTTL( $ttl ) {
1367 if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1368 $this->ttl = $ttl;
1369 }
1370 }
1371
1377 public function getTTL() {
1378 return $this->ttl;
1379 }
1380}
1381
1386// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1388 // @codingStandardsIgnoreEnd
1389
1392
1400 public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1401 $namedArgs = [], $title = false
1402 ) {
1403 parent::__construct( $preprocessor );
1404
1405 $this->parent = $parent;
1406 $this->numberedArgs = $numberedArgs;
1407 $this->namedArgs = $namedArgs;
1408 $this->title = $title;
1409 $pdbk = $title ? $title->getPrefixedDBkey() : false;
1410 $this->titleCache = $parent->titleCache;
1411 $this->titleCache[] = $pdbk;
1412 $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1413 if ( $pdbk !== false ) {
1414 $this->loopCheckHash[$pdbk] = true;
1415 }
1416 $this->depth = $parent->depth + 1;
1417 $this->numberedExpansionCache = $this->namedExpansionCache = [];
1418 }
1419
1420 public function __toString() {
1421 $s = 'tplframe{';
1422 $first = true;
1423 $args = $this->numberedArgs + $this->namedArgs;
1424 foreach ( $args as $name => $value ) {
1425 if ( $first ) {
1426 $first = false;
1427 } else {
1428 $s .= ', ';
1429 }
1430 $s .= "\"$name\":\"" .
1431 str_replace( '"', '\\"', $value->__toString() ) . '"';
1432 }
1433 $s .= '}';
1434 return $s;
1435 }
1436
1444 public function cachedExpand( $key, $root, $flags = 0 ) {
1445 if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1446 return $this->parent->childExpansionCache[$key];
1447 }
1448 $retval = $this->expand( $root, $flags );
1449 if ( !$this->isVolatile() ) {
1450 $this->parent->childExpansionCache[$key] = $retval;
1451 }
1452 return $retval;
1453 }
1454
1460 public function isEmpty() {
1461 return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1462 }
1463
1467 public function getArguments() {
1468 $arguments = [];
1469 foreach ( array_merge(
1470 array_keys( $this->numberedArgs ),
1471 array_keys( $this->namedArgs ) ) as $key ) {
1472 $arguments[$key] = $this->getArgument( $key );
1473 }
1474 return $arguments;
1475 }
1476
1480 public function getNumberedArguments() {
1481 $arguments = [];
1482 foreach ( array_keys( $this->numberedArgs ) as $key ) {
1483 $arguments[$key] = $this->getArgument( $key );
1484 }
1485 return $arguments;
1486 }
1487
1491 public function getNamedArguments() {
1492 $arguments = [];
1493 foreach ( array_keys( $this->namedArgs ) as $key ) {
1494 $arguments[$key] = $this->getArgument( $key );
1495 }
1496 return $arguments;
1497 }
1498
1503 public function getNumberedArgument( $index ) {
1504 if ( !isset( $this->numberedArgs[$index] ) ) {
1505 return false;
1506 }
1507 if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1508 # No trimming for unnamed arguments
1509 $this->numberedExpansionCache[$index] = $this->parent->expand(
1510 $this->numberedArgs[$index],
1512 );
1513 }
1514 return $this->numberedExpansionCache[$index];
1515 }
1516
1521 public function getNamedArgument( $name ) {
1522 if ( !isset( $this->namedArgs[$name] ) ) {
1523 return false;
1524 }
1525 if ( !isset( $this->namedExpansionCache[$name] ) ) {
1526 # Trim named arguments post-expand, for backwards compatibility
1527 $this->namedExpansionCache[$name] = trim(
1528 $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1529 }
1530 return $this->namedExpansionCache[$name];
1531 }
1532
1537 public function getArgument( $name ) {
1538 $text = $this->getNumberedArgument( $name );
1539 if ( $text === false ) {
1540 $text = $this->getNamedArgument( $name );
1541 }
1542 return $text;
1543 }
1544
1550 public function isTemplate() {
1551 return true;
1552 }
1553
1554 public function setVolatile( $flag = true ) {
1555 parent::setVolatile( $flag );
1556 $this->parent->setVolatile( $flag );
1557 }
1558
1559 public function setTTL( $ttl ) {
1560 parent::setTTL( $ttl );
1561 $this->parent->setTTL( $ttl );
1562 }
1563}
1564
1569// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1571 // @codingStandardsIgnoreEnd
1572
1573 public $args;
1574
1575 public function __construct( $preprocessor, $args ) {
1576 parent::__construct( $preprocessor );
1577 $this->args = $args;
1578 }
1579
1580 public function __toString() {
1581 $s = 'cstmframe{';
1582 $first = true;
1583 foreach ( $this->args as $name => $value ) {
1584 if ( $first ) {
1585 $first = false;
1586 } else {
1587 $s .= ', ';
1588 }
1589 $s .= "\"$name\":\"" .
1590 str_replace( '"', '\\"', $value->__toString() ) . '"';
1591 }
1592 $s .= '}';
1593 return $s;
1594 }
1595
1599 public function isEmpty() {
1600 return !count( $this->args );
1601 }
1602
1607 public function getArgument( $index ) {
1608 if ( !isset( $this->args[$index] ) ) {
1609 return false;
1610 }
1611 return $this->args[$index];
1612 }
1613
1614 public function getArguments() {
1615 return $this->args;
1616 }
1617}
1618
1622// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1623class PPNode_Hash_Tree implements PPNode {
1624 // @codingStandardsIgnoreEnd
1625
1626 public $name;
1627
1634
1638 private $store;
1639
1643 private $index;
1644
1649 const NAME = 0;
1650
1655 const CHILDREN = 1;
1656
1664 public function __construct( array $store, $index ) {
1665 $this->store = $store;
1666 $this->index = $index;
1667 list( $this->name, $this->rawChildren ) = $this->store[$index];
1668 }
1669
1678 public static function factory( array $store, $index ) {
1679 if ( !isset( $store[$index] ) ) {
1680 return false;
1681 }
1682
1683 $descriptor = $store[$index];
1684 if ( is_string( $descriptor ) ) {
1685 $class = 'PPNode_Hash_Text';
1686 } elseif ( is_array( $descriptor ) ) {
1687 if ( $descriptor[self::NAME][0] === '@' ) {
1688 $class = 'PPNode_Hash_Attr';
1689 } else {
1690 $class = 'PPNode_Hash_Tree';
1691 }
1692 } else {
1693 throw new MWException( __METHOD__.': invalid node descriptor' );
1694 }
1695 return new $class( $store, $index );
1696 }
1697
1701 public function __toString() {
1702 $inner = '';
1703 $attribs = '';
1704 for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
1705 if ( $node instanceof PPNode_Hash_Attr ) {
1706 $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
1707 } else {
1708 $inner .= $node->__toString();
1709 }
1710 }
1711 if ( $inner === '' ) {
1712 return "<{$this->name}$attribs/>";
1713 } else {
1714 return "<{$this->name}$attribs>$inner</{$this->name}>";
1715 }
1716 }
1717
1721 public function getChildren() {
1722 $children = [];
1723 foreach ( $this->rawChildren as $i => $child ) {
1724 $children[] = self::factory( $this->rawChildren, $i );
1725 }
1726 return new PPNode_Hash_Array( $children );
1727 }
1728
1736 public function getFirstChild() {
1737 if ( !isset( $this->rawChildren[0] ) ) {
1738 return false;
1739 } else {
1740 return self::factory( $this->rawChildren, 0 );
1741 }
1742 }
1743
1751 public function getNextSibling() {
1752 return self::factory( $this->store, $this->index + 1 );
1753 }
1754
1761 public function getChildrenOfType( $name ) {
1762 $children = [];
1763 foreach ( $this->rawChildren as $i => $child ) {
1764 if ( is_array( $child ) && $child[self::NAME] === $name ) {
1765 $children[] = self::factory( $this->rawChildren, $i );
1766 }
1767 }
1768 return new PPNode_Hash_Array( $children );
1769 }
1770
1775 public function getRawChildren() {
1776 return $this->rawChildren;
1777 }
1778
1782 public function getLength() {
1783 return false;
1784 }
1785
1790 public function item( $i ) {
1791 return false;
1792 }
1793
1797 public function getName() {
1798 return $this->name;
1799 }
1800
1810 public function splitArg() {
1811 return self::splitRawArg( $this->rawChildren );
1812 }
1813
1817 public static function splitRawArg( array $children ) {
1818 $bits = [];
1819 foreach ( $children as $i => $child ) {
1820 if ( !is_array( $child ) ) {
1821 continue;
1822 }
1823 if ( $child[self::NAME] === 'name' ) {
1824 $bits['name'] = new self( $children, $i );
1825 if ( isset( $child[self::CHILDREN][0][self::NAME] )
1826 && $child[self::CHILDREN][0][self::NAME] === '@index'
1827 ) {
1828 $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
1829 }
1830 } elseif ( $child[self::NAME] === 'value' ) {
1831 $bits['value'] = new self( $children, $i );
1832 }
1833 }
1834
1835 if ( !isset( $bits['name'] ) ) {
1836 throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
1837 }
1838 if ( !isset( $bits['index'] ) ) {
1839 $bits['index'] = '';
1840 }
1841 return $bits;
1842 }
1843
1851 public function splitExt() {
1852 return self::splitRawExt( $this->rawChildren );
1853 }
1854
1858 public static function splitRawExt( array $children ) {
1859 $bits = [];
1860 foreach ( $children as $i => $child ) {
1861 if ( !is_array( $child ) ) {
1862 continue;
1863 }
1864 switch ( $child[self::NAME] ) {
1865 case 'name':
1866 $bits['name'] = new self( $children, $i );
1867 break;
1868 case 'attr':
1869 $bits['attr'] = new self( $children, $i );
1870 break;
1871 case 'inner':
1872 $bits['inner'] = new self( $children, $i );
1873 break;
1874 case 'close':
1875 $bits['close'] = new self( $children, $i );
1876 break;
1877 }
1878 }
1879 if ( !isset( $bits['name'] ) ) {
1880 throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
1881 }
1882 return $bits;
1883 }
1884
1891 public function splitHeading() {
1892 if ( $this->name !== 'h' ) {
1893 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1894 }
1895 return self::splitRawHeading( $this->rawChildren );
1896 }
1897
1901 public static function splitRawHeading( array $children ) {
1902 $bits = [];
1903 foreach ( $children as $i => $child ) {
1904 if ( !is_array( $child ) ) {
1905 continue;
1906 }
1907 if ( $child[self::NAME] === '@i' ) {
1908 $bits['i'] = $child[self::CHILDREN][0];
1909 } elseif ( $child[self::NAME] === '@level' ) {
1910 $bits['level'] = $child[self::CHILDREN][0];
1911 }
1912 }
1913 if ( !isset( $bits['i'] ) ) {
1914 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1915 }
1916 return $bits;
1917 }
1918
1925 public function splitTemplate() {
1926 return self::splitRawTemplate( $this->rawChildren );
1927 }
1928
1932 public static function splitRawTemplate( array $children ) {
1933 $parts = [];
1934 $bits = [ 'lineStart' => '' ];
1935 foreach ( $children as $i => $child ) {
1936 if ( !is_array( $child ) ) {
1937 continue;
1938 }
1939 switch ( $child[self::NAME] ) {
1940 case 'title':
1941 $bits['title'] = new self( $children, $i );
1942 break;
1943 case 'part':
1944 $parts[] = new self( $children, $i );
1945 break;
1946 case '@lineStart':
1947 $bits['lineStart'] = '1';
1948 break;
1949 }
1950 }
1951 if ( !isset( $bits['title'] ) ) {
1952 throw new MWException( 'Invalid node passed to ' . __METHOD__ );
1953 }
1954 $bits['parts'] = new PPNode_Hash_Array( $parts );
1955 return $bits;
1956 }
1957}
1958
1962// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1963class PPNode_Hash_Text implements PPNode {
1964 // @codingStandardsIgnoreEnd
1965
1966 public $value;
1967 private $store, $index;
1968
1976 public function __construct( array $store, $index ) {
1977 $this->value = $store[$index];
1978 if ( !is_scalar( $this->value ) ) {
1979 throw new MWException( __CLASS__ . ' given object instead of string' );
1980 }
1981 $this->store = $store;
1982 $this->index = $index;
1983 }
1984
1985 public function __toString() {
1986 return htmlspecialchars( $this->value );
1987 }
1988
1989 public function getNextSibling() {
1990 return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
1991 }
1992
1993 public function getChildren() {
1994 return false;
1995 }
1996
1997 public function getFirstChild() {
1998 return false;
1999 }
2000
2001 public function getChildrenOfType( $name ) {
2002 return false;
2003 }
2004
2005 public function getLength() {
2006 return false;
2007 }
2008
2009 public function item( $i ) {
2010 return false;
2011 }
2012
2013 public function getName() {
2014 return '#text';
2015 }
2016
2017 public function splitArg() {
2018 throw new MWException( __METHOD__ . ': not supported' );
2019 }
2020
2021 public function splitExt() {
2022 throw new MWException( __METHOD__ . ': not supported' );
2023 }
2024
2025 public function splitHeading() {
2026 throw new MWException( __METHOD__ . ': not supported' );
2027 }
2028}
2029
2033// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2034class PPNode_Hash_Array implements PPNode {
2035 // @codingStandardsIgnoreEnd
2036
2037 public $value;
2038
2039 public function __construct( $value ) {
2040 $this->value = $value;
2041 }
2042
2043 public function __toString() {
2044 return var_export( $this, true );
2045 }
2046
2047 public function getLength() {
2048 return count( $this->value );
2049 }
2050
2051 public function item( $i ) {
2052 return $this->value[$i];
2053 }
2054
2055 public function getName() {
2056 return '#nodelist';
2057 }
2058
2059 public function getNextSibling() {
2060 return false;
2061 }
2062
2063 public function getChildren() {
2064 return false;
2065 }
2066
2067 public function getFirstChild() {
2068 return false;
2069 }
2070
2071 public function getChildrenOfType( $name ) {
2072 return false;
2073 }
2074
2075 public function splitArg() {
2076 throw new MWException( __METHOD__ . ': not supported' );
2077 }
2078
2079 public function splitExt() {
2080 throw new MWException( __METHOD__ . ': not supported' );
2081 }
2082
2083 public function splitHeading() {
2084 throw new MWException( __METHOD__ . ': not supported' );
2085 }
2086}
2087
2091// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2092class PPNode_Hash_Attr implements PPNode {
2093 // @codingStandardsIgnoreEnd
2094
2095 public $name, $value;
2096 private $store, $index;
2097
2105 public function __construct( array $store, $index ) {
2106 $descriptor = $store[$index];
2107 if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
2108 throw new MWException( __METHOD__.': invalid name in attribute descriptor' );
2109 }
2110 $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
2111 $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
2112 $this->store = $store;
2113 $this->index = $index;
2114 }
2115
2116 public function __toString() {
2117 return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
2118 }
2119
2120 public function getName() {
2121 return $this->name;
2122 }
2123
2124 public function getNextSibling() {
2125 return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2126 }
2127
2128 public function getChildren() {
2129 return false;
2130 }
2131
2132 public function getFirstChild() {
2133 return false;
2134 }
2135
2136 public function getChildrenOfType( $name ) {
2137 return false;
2138 }
2139
2140 public function getLength() {
2141 return false;
2142 }
2143
2144 public function item( $i ) {
2145 return false;
2146 }
2147
2148 public function splitArg() {
2149 throw new MWException( __METHOD__ . ': not supported' );
2150 }
2151
2152 public function splitExt() {
2153 throw new MWException( __METHOD__ . ': not supported' );
2154 }
2155
2156 public function splitHeading() {
2157 throw new MWException( __METHOD__ . ': not supported' );
2158 }
2159}
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if( $line===false) $args
Definition cdb.php:64
MediaWiki exception.
Expansion frame with custom arguments.
__construct( $preprocessor, $args)
string $out
Output accumulator string.
breakSyntax( $openingCount=false)
Get the accumulator that would result if the close is not found.
int $count
Number of opening characters found (number of "=" for heading)
Stack class to help Preprocessor::preprocessToObj()
Stack class to help Preprocessor::preprocessToObj()
An expansion frame, used as a context to expand the result of preprocessToObj()
$loopCheckHash
Hashtable listing templates which are disallowed for expansion in this frame, having been encountered...
isEmpty()
Returns true if there are no arguments in this frame.
loopCheck( $title)
Returns true if the infinite loop check is OK, false if a loop is detected.
setVolatile( $flag=true)
Set the volatile flag.
$depth
Recursion depth of this frame, top = 0 Note that this is NOT the same as expansion depth in expand()
expand( $root, $flags=0)
getTitle()
Get a title of frame.
Preprocessor $preprocessor
setTTL( $ttl)
Set the TTL.
getTTL()
Get the TTL.
cachedExpand( $key, $root, $flags=0)
implode( $sep)
Implode with no flags specified This previously called implodeWithFlags but has now been inlined to r...
__construct( $preprocessor)
Construct a new preprocessor frame.
isVolatile()
Get the volatile flag.
implodeWithFlags( $sep, $flags)
newChild( $args=false, $title=false, $indexOffset=0)
Create a new child frame $args is optionally a multi-root PPNode or array containing the template arg...
virtualBracketedImplode( $start, $sep, $end)
Virtual implode with brackets.
isTemplate()
Return true if the frame is a template frame.
getPDBK( $level=false)
virtualImplode( $sep)
Makes an object that, when expand()ed, will be the same as one obtained with implode()
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
getName()
Get the name of this node.
getNextSibling()
Get the next sibling of any node.
getLength()
Returns the length of the array, or false if this is not an array-type node.
splitHeading()
Split an "<h>" node.
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
getFirstChild()
Get the first child of a tree node.
getChildren()
Get an array-type node containing the children of this node.
item( $i)
Returns an item of an array-type node.
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
getChildren()
Get an array-type node containing the children of this node.
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
__construct(array $store, $index)
Construct an object using the data from $store[$index].
item( $i)
Returns an item of an array-type node.
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
splitHeading()
Split an "<h>" node.
getName()
Get the name of this node.
getLength()
Returns the length of the array, or false if this is not an array-type node.
getFirstChild()
Get the first child of a tree node.
getNextSibling()
Get the next sibling of any node.
getChildren()
Get an array-type node containing the children of this node.
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
__construct(array $store, $index)
Construct an object using the data from $store[$index].
getLength()
Returns the length of the array, or false if this is not an array-type node.
splitHeading()
Split an "<h>" node.
getFirstChild()
Get the first child of a tree node.
getNextSibling()
Get the next sibling of any node.
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
getName()
Get the name of this node.
item( $i)
Returns an item of an array-type node.
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
static factory(array $store, $index)
Construct an appropriate PPNode_Hash_* object with a class that depends on what is at the relevant st...
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
static splitRawArg(array $children)
Like splitArg() but for a raw child array.
splitArg()
Split a "<part>" node into an associative array containing:
$index
The index into $this->store which contains the descriptor of this node.
getNextSibling()
Get the next sibling, or false if there is none.
static splitRawTemplate(array $children)
Like splitTemplate() but for a raw child array.
splitHeading()
Split an "<h>" node.
splitTemplate()
Split a "<template>" or "<tplarg>" node.
static splitRawHeading(array $children)
Like splitHeading() but for a raw child array.
$store
The store array for the siblings of this node, including this node itself.
static splitRawExt(array $children)
Like splitExt() but for a raw child array.
__construct(array $store, $index)
Construct an object using the data from $store[$index].
getChildrenOfType( $name)
Get an array of the children with a given node name.
__toString()
Convert a node to XML, for debugging.
const CHILDREN
The offset of the child list within descriptors, used in some places for readability.
$rawChildren
The store array for children of this node.
getFirstChild()
Get the first child, or false if there is none.
getRawChildren()
Get the raw child array.
const NAME
The offset of the name within descriptors, used in some places for readability.
Expansion frame with template arguments.
setTTL( $ttl)
Set the TTL.
cachedExpand( $key, $root, $flags=0)
isEmpty()
Returns true if there are no arguments in this frame.
isTemplate()
Return true if the frame is a template frame.
setVolatile( $flag=true)
Set the volatile flag.
__construct( $preprocessor, $parent=false, $numberedArgs=[], $namedArgs=[], $title=false)
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:70
Differences from DOM schema:
static addLiteral(array &$accum, $text)
preprocessToObj( $text, $flags=0)
Preprocess some wikitext and return the document tree.
cacheGetTree( $text, $flags)
Attempt to load a precomputed document tree for some given wikitext from the cache.
cacheSetTree( $text, $flags, $tree)
Store a document tree in the cache.
Represents a title within MediaWiki.
Definition Title.php:36
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1443
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
Definition design.txt:12
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition hooks.txt:268
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2710
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition hooks.txt:1949
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:886
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:1958
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
const NO_TEMPLATES
const NO_TAGS
const RECOVER_COMMENTS
const NO_ARGS
const NO_IGNORE
const STRIP_COMMENTS
There are three types of nodes:
title
MediaWiki s SiteStore can be cached and stored in a flat in a json format If the SiteStore is frequently the file cache may provide a performance benefit over a database store
Definition sitescache.txt:4