MediaWiki REL1_29
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 ) {
121
122 $tree = $this->cacheGetTree( $text, $flags );
123 if ( $tree !== false ) {
124 $store = json_decode( $tree );
125 if ( is_array( $store ) ) {
126 return new PPNode_Hash_Tree( $store, 0 );
127 }
128 }
129
130 $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
131
132 $xmlishElements = $this->parser->getStripList();
133 $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
134 $enableOnlyinclude = false;
135 if ( $forInclusion ) {
136 $ignoredTags = [ 'includeonly', '/includeonly' ];
137 $ignoredElements = [ 'noinclude' ];
138 $xmlishElements[] = 'noinclude';
139 if ( strpos( $text, '<onlyinclude>' ) !== false
140 && strpos( $text, '</onlyinclude>' ) !== false
141 ) {
142 $enableOnlyinclude = true;
143 }
144 } else {
145 $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
146 $ignoredElements = [ 'includeonly' ];
147 $xmlishElements[] = 'includeonly';
148 }
149 $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
150
151 // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
152 $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
153
154 $stack = new PPDStack_Hash;
155
156 $searchBase = "[{<\n";
158 // FIXME: disabled due to T153761
159 // $searchBase .= '-';
160 }
161
162 // For fast reverse searches
163 $revText = strrev( $text );
164 $lengthText = strlen( $text );
165
166 // Input pointer, starts out pointing to a pseudo-newline before the start
167 $i = 0;
168 // Current accumulator. See the doc comment for Preprocessor_Hash for the format.
169 $accum =& $stack->getAccum();
170 // True to find equals signs in arguments
171 $findEquals = false;
172 // True to take notice of pipe characters
173 $findPipe = false;
174 $headingIndex = 1;
175 // True if $i is inside a possible heading
176 $inHeading = false;
177 // True if there are no more greater-than (>) signs right of $i
178 $noMoreGT = false;
179 // Map of tag name => true if there are no more closing tags of given type right of $i
180 $noMoreClosingTag = [];
181 // True to ignore all input up to the next <onlyinclude>
182 $findOnlyinclude = $enableOnlyinclude;
183 // Do a line-start run without outputting an LF character
184 $fakeLineStart = true;
185
186 while ( true ) {
187 // $this->memCheck();
188
189 if ( $findOnlyinclude ) {
190 // Ignore all input up to the next <onlyinclude>
191 $startPos = strpos( $text, '<onlyinclude>', $i );
192 if ( $startPos === false ) {
193 // Ignored section runs to the end
194 $accum[] = [ 'ignore', [ substr( $text, $i ) ] ];
195 break;
196 }
197 $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
198 $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
199 $i = $tagEndPos;
200 $findOnlyinclude = false;
201 }
202
203 if ( $fakeLineStart ) {
204 $found = 'line-start';
205 $curChar = '';
206 } else {
207 # Find next opening brace, closing brace or pipe
208 $search = $searchBase;
209 if ( $stack->top === false ) {
210 $currentClosing = '';
211 } else {
212 $currentClosing = $stack->top->close;
213 $search .= $currentClosing;
214 }
215 if ( $findPipe ) {
216 $search .= '|';
217 }
218 if ( $findEquals ) {
219 // First equals will be for the template
220 $search .= '=';
221 }
222 $rule = null;
223 # Output literal section, advance input counter
224 $literalLength = strcspn( $text, $search, $i );
225 if ( $literalLength > 0 ) {
226 self::addLiteral( $accum, substr( $text, $i, $literalLength ) );
227 $i += $literalLength;
228 }
229 if ( $i >= $lengthText ) {
230 if ( $currentClosing == "\n" ) {
231 // Do a past-the-end run to finish off the heading
232 $curChar = '';
233 $found = 'line-end';
234 } else {
235 # All done
236 break;
237 }
238 } else {
239 $curChar = $curTwoChar = $text[$i];
240 if ( ( $i + 1 ) < $lengthText ) {
241 $curTwoChar .= $text[$i + 1];
242 }
243 if ( $curChar == '|' ) {
244 $found = 'pipe';
245 } elseif ( $curChar == '=' ) {
246 $found = 'equals';
247 } elseif ( $curChar == '<' ) {
248 $found = 'angle';
249 } elseif ( $curChar == "\n" ) {
250 if ( $inHeading ) {
251 $found = 'line-end';
252 } else {
253 $found = 'line-start';
254 }
255 } elseif ( $curTwoChar == $currentClosing ) {
256 $found = 'close';
257 $curChar = $curTwoChar;
258 } elseif ( $curChar == $currentClosing ) {
259 $found = 'close';
260 } elseif ( isset( $this->rules[$curTwoChar] ) ) {
261 $curChar = $curTwoChar;
262 $found = 'open';
263 $rule = $this->rules[$curChar];
264 } elseif ( isset( $this->rules[$curChar] ) ) {
265 $found = 'open';
266 $rule = $this->rules[$curChar];
267 } elseif ( $curChar == '-' ) {
268 $found = 'dash';
269 } else {
270 # Some versions of PHP have a strcspn which stops on null characters
271 # Ignore and continue
272 ++$i;
273 continue;
274 }
275 }
276 }
277
278 if ( $found == 'angle' ) {
279 $matches = false;
280 // Handle </onlyinclude>
281 if ( $enableOnlyinclude
282 && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
283 ) {
284 $findOnlyinclude = true;
285 continue;
286 }
287
288 // Determine element name
289 if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
290 // Element name missing or not listed
291 self::addLiteral( $accum, '<' );
292 ++$i;
293 continue;
294 }
295 // Handle comments
296 if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
297
298 // To avoid leaving blank lines, when a sequence of
299 // space-separated comments is both preceded and followed by
300 // a newline (ignoring spaces), then
301 // trim leading and trailing spaces and the trailing newline.
302
303 // Find the end
304 $endPos = strpos( $text, '-->', $i + 4 );
305 if ( $endPos === false ) {
306 // Unclosed comment in input, runs to end
307 $inner = substr( $text, $i );
308 $accum[] = [ 'comment', [ $inner ] ];
309 $i = $lengthText;
310 } else {
311 // Search backwards for leading whitespace
312 $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
313
314 // Search forwards for trailing whitespace
315 // $wsEnd will be the position of the last space (or the '>' if there's none)
316 $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
317
318 // Keep looking forward as long as we're finding more
319 // comments.
320 $comments = [ [ $wsStart, $wsEnd ] ];
321 while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
322 $c = strpos( $text, '-->', $wsEnd + 4 );
323 if ( $c === false ) {
324 break;
325 }
326 $c = $c + 2 + strspn( $text, " \t", $c + 3 );
327 $comments[] = [ $wsEnd + 1, $c ];
328 $wsEnd = $c;
329 }
330
331 // Eat the line if possible
332 // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
333 // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
334 // it's a possible beneficial b/c break.
335 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
336 && substr( $text, $wsEnd + 1, 1 ) == "\n"
337 ) {
338 // Remove leading whitespace from the end of the accumulator
339 $wsLength = $i - $wsStart;
340 $endIndex = count( $accum ) - 1;
341
342 // Sanity check
343 if ( $wsLength > 0
344 && $endIndex >= 0
345 && is_string( $accum[$endIndex] )
346 && strspn( $accum[$endIndex], " \t", -$wsLength ) === $wsLength
347 ) {
348 $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
349 }
350
351 // Dump all but the last comment to the accumulator
352 foreach ( $comments as $j => $com ) {
353 $startPos = $com[0];
354 $endPos = $com[1] + 1;
355 if ( $j == ( count( $comments ) - 1 ) ) {
356 break;
357 }
358 $inner = substr( $text, $startPos, $endPos - $startPos );
359 $accum[] = [ 'comment', [ $inner ] ];
360 }
361
362 // Do a line-start run next time to look for headings after the comment
363 $fakeLineStart = true;
364 } else {
365 // No line to eat, just take the comment itself
366 $startPos = $i;
367 $endPos += 2;
368 }
369
370 if ( $stack->top ) {
371 $part = $stack->top->getCurrentPart();
372 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
373 $part->visualEnd = $wsStart;
374 }
375 // Else comments abutting, no change in visual end
376 $part->commentEnd = $endPos;
377 }
378 $i = $endPos + 1;
379 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
380 $accum[] = [ 'comment', [ $inner ] ];
381 }
382 continue;
383 }
384 $name = $matches[1];
385 $lowerName = strtolower( $name );
386 $attrStart = $i + strlen( $name ) + 1;
387
388 // Find end of tag
389 $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
390 if ( $tagEndPos === false ) {
391 // Infinite backtrack
392 // Disable tag search to prevent worst-case O(N^2) performance
393 $noMoreGT = true;
394 self::addLiteral( $accum, '<' );
395 ++$i;
396 continue;
397 }
398
399 // Handle ignored tags
400 if ( in_array( $lowerName, $ignoredTags ) ) {
401 $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
402 $i = $tagEndPos + 1;
403 continue;
404 }
405
406 $tagStartPos = $i;
407 if ( $text[$tagEndPos - 1] == '/' ) {
408 // Short end tag
409 $attrEnd = $tagEndPos - 1;
410 $inner = null;
411 $i = $tagEndPos + 1;
412 $close = null;
413 } else {
414 $attrEnd = $tagEndPos;
415 // Find closing tag
416 if (
417 !isset( $noMoreClosingTag[$name] ) &&
418 preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
419 $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
420 ) {
421 $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
422 $i = $matches[0][1] + strlen( $matches[0][0] );
423 $close = $matches[0][0];
424 } else {
425 // No end tag
426 if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
427 // Let it run out to the end of the text.
428 $inner = substr( $text, $tagEndPos + 1 );
429 $i = $lengthText;
430 $close = null;
431 } else {
432 // Don't match the tag, treat opening tag as literal and resume parsing.
433 $i = $tagEndPos + 1;
434 self::addLiteral( $accum,
435 substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
436 // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
437 $noMoreClosingTag[$name] = true;
438 continue;
439 }
440 }
441 }
442 // <includeonly> and <noinclude> just become <ignore> tags
443 if ( in_array( $lowerName, $ignoredElements ) ) {
444 $accum[] = [ 'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
445 continue;
446 }
447
448 if ( $attrEnd <= $attrStart ) {
449 $attr = '';
450 } else {
451 // Note that the attr element contains the whitespace between name and attribute,
452 // this is necessary for precise reconstruction during pre-save transform.
453 $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
454 }
455
456 $children = [
457 [ 'name', [ $name ] ],
458 [ 'attr', [ $attr ] ] ];
459 if ( $inner !== null ) {
460 $children[] = [ 'inner', [ $inner ] ];
461 }
462 if ( $close !== null ) {
463 $children[] = [ 'close', [ $close ] ];
464 }
465 $accum[] = [ 'ext', $children ];
466 } elseif ( $found == 'line-start' ) {
467 // Is this the start of a heading?
468 // Line break belongs before the heading element in any case
469 if ( $fakeLineStart ) {
470 $fakeLineStart = false;
471 } else {
472 self::addLiteral( $accum, $curChar );
473 $i++;
474 }
475
476 $count = strspn( $text, '=', $i, 6 );
477 if ( $count == 1 && $findEquals ) {
478 // DWIM: This looks kind of like a name/value separator.
479 // Let's let the equals handler have it and break the potential
480 // heading. This is heuristic, but AFAICT the methods for
481 // completely correct disambiguation are very complex.
482 } elseif ( $count > 0 ) {
483 $piece = [
484 'open' => "\n",
485 'close' => "\n",
486 'parts' => [ new PPDPart_Hash( str_repeat( '=', $count ) ) ],
487 'startPos' => $i,
488 'count' => $count ];
489 $stack->push( $piece );
490 $accum =& $stack->getAccum();
491 extract( $stack->getFlags() );
492 $i += $count;
493 }
494 } elseif ( $found == 'line-end' ) {
495 $piece = $stack->top;
496 // A heading must be open, otherwise \n wouldn't have been in the search list
497 assert( $piece->open === "\n" );
498 $part = $piece->getCurrentPart();
499 // Search back through the input to see if it has a proper close.
500 // Do this using the reversed string since the other solutions
501 // (end anchor, etc.) are inefficient.
502 $wsLength = strspn( $revText, " \t", $lengthText - $i );
503 $searchStart = $i - $wsLength;
504 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
505 // Comment found at line end
506 // Search for equals signs before the comment
507 $searchStart = $part->visualEnd;
508 $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
509 }
510 $count = $piece->count;
511 $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
512 if ( $equalsLength > 0 ) {
513 if ( $searchStart - $equalsLength == $piece->startPos ) {
514 // This is just a single string of equals signs on its own line
515 // Replicate the doHeadings behavior /={count}(.+)={count}/
516 // First find out how many equals signs there really are (don't stop at 6)
517 $count = $equalsLength;
518 if ( $count < 3 ) {
519 $count = 0;
520 } else {
521 $count = min( 6, intval( ( $count - 1 ) / 2 ) );
522 }
523 } else {
524 $count = min( $equalsLength, $count );
525 }
526 if ( $count > 0 ) {
527 // Normal match, output <h>
528 $element = [ [ 'possible-h',
529 array_merge(
530 [
531 [ '@level', [ $count ] ],
532 [ '@i', [ $headingIndex++ ] ]
533 ],
534 $accum
535 )
536 ] ];
537 } else {
538 // Single equals sign on its own line, count=0
539 $element = $accum;
540 }
541 } else {
542 // No match, no <h>, just pass down the inner text
543 $element = $accum;
544 }
545 // Unwind the stack
546 $stack->pop();
547 $accum =& $stack->getAccum();
548 extract( $stack->getFlags() );
549
550 // Append the result to the enclosing accumulator
551 array_splice( $accum, count( $accum ), 0, $element );
552
553 // Note that we do NOT increment the input pointer.
554 // This is because the closing linebreak could be the opening linebreak of
555 // another heading. Infinite loops are avoided because the next iteration MUST
556 // hit the heading open case above, which unconditionally increments the
557 // input pointer.
558 } elseif ( $found == 'open' ) {
559 # count opening brace characters
560 $curLen = strlen( $curChar );
561 $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i );
562
563 # we need to add to stack only if opening brace count is enough for one of the rules
564 if ( $count >= $rule['min'] ) {
565 # Add it to the stack
566 $piece = [
567 'open' => $curChar,
568 'close' => $rule['end'],
569 'count' => $count,
570 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ),
571 ];
572
573 $stack->push( $piece );
574 $accum =& $stack->getAccum();
575 extract( $stack->getFlags() );
576 } else {
577 # Add literal brace(s)
578 self::addLiteral( $accum, str_repeat( $curChar, $count ) );
579 }
580 $i += $curLen * $count;
581 } elseif ( $found == 'close' ) {
582 $piece = $stack->top;
583 # lets check if there are enough characters for closing brace
584 $maxCount = $piece->count;
585 $curLen = strlen( $curChar );
586 $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i, $maxCount );
587
588 # check for maximum matching characters (if there are 5 closing
589 # characters, we will probably need only 3 - depending on the rules)
590 $rule = $this->rules[$piece->open];
591 if ( $count > $rule['max'] ) {
592 # The specified maximum exists in the callback array, unless the caller
593 # has made an error
594 $matchingCount = $rule['max'];
595 } else {
596 # Count is less than the maximum
597 # Skip any gaps in the callback array to find the true largest match
598 # Need to use array_key_exists not isset because the callback can be null
599 $matchingCount = $count;
600 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
601 --$matchingCount;
602 }
603 }
604
605 if ( $matchingCount <= 0 ) {
606 # No matching element found in callback array
607 # Output a literal closing brace and continue
608 self::addLiteral( $accum, str_repeat( $curChar, $count ) );
609 $i += $curLen * $count;
610 continue;
611 }
612 $name = $rule['names'][$matchingCount];
613 if ( $name === null ) {
614 // No element, just literal text
615 $element = $piece->breakSyntax( $matchingCount );
616 self::addLiteral( $element, str_repeat( $rule['end'], $matchingCount ) );
617 } else {
618 # Create XML element
619 $parts = $piece->parts;
620 $titleAccum = $parts[0]->out;
621 unset( $parts[0] );
622
623 $children = [];
624
625 # The invocation is at the start of the line if lineStart is set in
626 # the stack, and all opening brackets are used up.
627 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
628 $children[] = [ '@lineStart', [ 1 ] ];
629 }
630 $titleNode = [ 'title', $titleAccum ];
631 $children[] = $titleNode;
632 $argIndex = 1;
633 foreach ( $parts as $part ) {
634 if ( isset( $part->eqpos ) ) {
635 $equalsNode = $part->out[$part->eqpos];
636 $nameNode = [ 'name', array_slice( $part->out, 0, $part->eqpos ) ];
637 $valueNode = [ 'value', array_slice( $part->out, $part->eqpos + 1 ) ];
638 $partNode = [ 'part', [ $nameNode, $equalsNode, $valueNode ] ];
639 $children[] = $partNode;
640 } else {
641 $nameNode = [ 'name', [ [ '@index', [ $argIndex++ ] ] ] ];
642 $valueNode = [ 'value', $part->out ];
643 $partNode = [ 'part', [ $nameNode, $valueNode ] ];
644 $children[] = $partNode;
645 }
646 }
647 $element = [ [ $name, $children ] ];
648 }
649
650 # Advance input pointer
651 $i += $curLen * $matchingCount;
652
653 # Unwind the stack
654 $stack->pop();
655 $accum =& $stack->getAccum();
656
657 # Re-add the old stack element if it still has unmatched opening characters remaining
658 if ( $matchingCount < $piece->count ) {
659 $piece->parts = [ new PPDPart_Hash ];
660 $piece->count -= $matchingCount;
661 # do we still qualify for any callback with remaining count?
662 $min = $this->rules[$piece->open]['min'];
663 if ( $piece->count >= $min ) {
664 $stack->push( $piece );
665 $accum =& $stack->getAccum();
666 } else {
667 self::addLiteral( $accum, str_repeat( $piece->open, $piece->count ) );
668 }
669 }
670
671 extract( $stack->getFlags() );
672
673 # Add XML element to the enclosing accumulator
674 array_splice( $accum, count( $accum ), 0, $element );
675 } elseif ( $found == 'pipe' ) {
676 $findEquals = true; // shortcut for getFlags()
677 $stack->addPart();
678 $accum =& $stack->getAccum();
679 ++$i;
680 } elseif ( $found == 'equals' ) {
681 $findEquals = false; // shortcut for getFlags()
682 $accum[] = [ 'equals', [ '=' ] ];
683 $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
684 ++$i;
685 } elseif ( $found == 'dash' ) {
686 self::addLiteral( $accum, '-' );
687 ++$i;
688 }
689 }
690
691 # Output any remaining unclosed brackets
692 foreach ( $stack->stack as $piece ) {
693 array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
694 }
695
696 # Enable top-level headings
697 foreach ( $stack->rootAccum as &$node ) {
698 if ( is_array( $node ) && $node[PPNode_Hash_Tree::NAME] === 'possible-h' ) {
699 $node[PPNode_Hash_Tree::NAME] = 'h';
700 }
701 }
702
703 $rootStore = [ [ 'root', $stack->rootAccum ] ];
704 $rootNode = new PPNode_Hash_Tree( $rootStore, 0 );
705
706 // Cache
707 $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
708 if ( $tree !== false ) {
709 $this->cacheSetTree( $text, $flags, $tree );
710 }
711
712 return $rootNode;
713 }
714
715 private static function addLiteral( array &$accum, $text ) {
716 $n = count( $accum );
717 if ( $n && is_string( $accum[$n - 1] ) ) {
718 $accum[$n - 1] .= $text;
719 } else {
720 $accum[] = $text;
721 }
722 }
723}
724
729// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
730class PPDStack_Hash extends PPDStack {
731 // @codingStandardsIgnoreEnd
732
733 public function __construct() {
734 $this->elementClass = 'PPDStackElement_Hash';
735 parent::__construct();
736 $this->rootAccum = [];
737 }
738}
739
743// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
745 // @codingStandardsIgnoreEnd
746
747 public function __construct( $data = [] ) {
748 $this->partClass = 'PPDPart_Hash';
749 parent::__construct( $data );
750 }
751
758 public function breakSyntax( $openingCount = false ) {
759 if ( $this->open == "\n" ) {
760 $accum = $this->parts[0]->out;
761 } else {
762 if ( $openingCount === false ) {
763 $openingCount = $this->count;
764 }
765 $accum = [ str_repeat( $this->open, $openingCount ) ];
766 $lastIndex = 0;
767 $first = true;
768 foreach ( $this->parts as $part ) {
769 if ( $first ) {
770 $first = false;
771 } elseif ( is_string( $accum[$lastIndex] ) ) {
772 $accum[$lastIndex] .= '|';
773 } else {
774 $accum[++$lastIndex] = '|';
775 }
776 foreach ( $part->out as $node ) {
777 if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
778 $accum[$lastIndex] .= $node;
779 } else {
780 $accum[++$lastIndex] = $node;
781 }
782 }
783 }
784 }
785 return $accum;
786 }
787}
788
792// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
793class PPDPart_Hash extends PPDPart {
794 // @codingStandardsIgnoreEnd
795
796 public function __construct( $out = '' ) {
797 if ( $out !== '' ) {
798 $accum = [ $out ];
799 } else {
800 $accum = [];
801 }
802 parent::__construct( $accum );
803 }
804}
805
810// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
811class PPFrame_Hash implements PPFrame {
812 // @codingStandardsIgnoreEnd
813
817 public $parser;
818
823
827 public $title;
829
835
840 public $depth;
841
842 private $volatile = false;
843 private $ttl = null;
844
849
854 public function __construct( $preprocessor ) {
855 $this->preprocessor = $preprocessor;
856 $this->parser = $preprocessor->parser;
857 $this->title = $this->parser->mTitle;
858 $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
859 $this->loopCheckHash = [];
860 $this->depth = 0;
861 $this->childExpansionCache = [];
862 }
863
874 public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
875 $namedArgs = [];
876 $numberedArgs = [];
877 if ( $title === false ) {
879 }
880 if ( $args !== false ) {
881 if ( $args instanceof PPNode_Hash_Array ) {
882 $args = $args->value;
883 } elseif ( !is_array( $args ) ) {
884 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
885 }
886 foreach ( $args as $arg ) {
887 $bits = $arg->splitArg();
888 if ( $bits['index'] !== '' ) {
889 // Numbered parameter
890 $index = $bits['index'] - $indexOffset;
891 if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
892 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
893 wfEscapeWikiText( $this->title ),
895 wfEscapeWikiText( $index ) )->text() );
896 $this->parser->addTrackingCategory( 'duplicate-args-category' );
897 }
898 $numberedArgs[$index] = $bits['value'];
899 unset( $namedArgs[$index] );
900 } else {
901 // Named parameter
902 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
903 if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
904 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
905 wfEscapeWikiText( $this->title ),
907 wfEscapeWikiText( $name ) )->text() );
908 $this->parser->addTrackingCategory( 'duplicate-args-category' );
909 }
910 $namedArgs[$name] = $bits['value'];
911 unset( $numberedArgs[$name] );
912 }
913 }
914 }
915 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
916 }
917
925 public function cachedExpand( $key, $root, $flags = 0 ) {
926 // we don't have a parent, so we don't have a cache
927 return $this->expand( $root, $flags );
928 }
929
936 public function expand( $root, $flags = 0 ) {
937 static $expansionDepth = 0;
938 if ( is_string( $root ) ) {
939 return $root;
940 }
941
942 if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
943 $this->parser->limitationWarn( 'node-count-exceeded',
944 $this->parser->mPPNodeCount,
945 $this->parser->mOptions->getMaxPPNodeCount()
946 );
947 return '<span class="error">Node-count limit exceeded</span>';
948 }
949 if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
950 $this->parser->limitationWarn( 'expansion-depth-exceeded',
951 $expansionDepth,
952 $this->parser->mOptions->getMaxPPExpandDepth()
953 );
954 return '<span class="error">Expansion depth limit exceeded</span>';
955 }
956 ++$expansionDepth;
957 if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
958 $this->parser->mHighestExpansionDepth = $expansionDepth;
959 }
960
961 $outStack = [ '', '' ];
962 $iteratorStack = [ false, $root ];
963 $indexStack = [ 0, 0 ];
964
965 while ( count( $iteratorStack ) > 1 ) {
966 $level = count( $outStack ) - 1;
967 $iteratorNode =& $iteratorStack[$level];
968 $out =& $outStack[$level];
969 $index =& $indexStack[$level];
970
971 if ( is_array( $iteratorNode ) ) {
972 if ( $index >= count( $iteratorNode ) ) {
973 // All done with this iterator
974 $iteratorStack[$level] = false;
975 $contextNode = false;
976 } else {
977 $contextNode = $iteratorNode[$index];
978 $index++;
979 }
980 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
981 if ( $index >= $iteratorNode->getLength() ) {
982 // All done with this iterator
983 $iteratorStack[$level] = false;
984 $contextNode = false;
985 } else {
986 $contextNode = $iteratorNode->item( $index );
987 $index++;
988 }
989 } else {
990 // Copy to $contextNode and then delete from iterator stack,
991 // because this is not an iterator but we do have to execute it once
992 $contextNode = $iteratorStack[$level];
993 $iteratorStack[$level] = false;
994 }
995
996 $newIterator = false;
997 $contextName = false;
998 $contextChildren = false;
999
1000 if ( $contextNode === false ) {
1001 // nothing to do
1002 } elseif ( is_string( $contextNode ) ) {
1003 $out .= $contextNode;
1004 } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
1005 $newIterator = $contextNode;
1006 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
1007 // No output
1008 } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
1009 $out .= $contextNode->value;
1010 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
1011 $contextName = $contextNode->name;
1012 $contextChildren = $contextNode->getRawChildren();
1013 } elseif ( is_array( $contextNode ) ) {
1014 // Node descriptor array
1015 if ( count( $contextNode ) !== 2 ) {
1016 throw new MWException( __METHOD__.
1017 ': found an array where a node descriptor should be' );
1018 }
1019 list( $contextName, $contextChildren ) = $contextNode;
1020 } else {
1021 throw new MWException( __METHOD__ . ': Invalid parameter type' );
1022 }
1023
1024 // Handle node descriptor array or tree object
1025 if ( $contextName === false ) {
1026 // Not a node, already handled above
1027 } elseif ( $contextName[0] === '@' ) {
1028 // Attribute: no output
1029 } elseif ( $contextName === 'template' ) {
1030 # Double-brace expansion
1031 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1032 if ( $flags & PPFrame::NO_TEMPLATES ) {
1033 $newIterator = $this->virtualBracketedImplode(
1034 '{{', '|', '}}',
1035 $bits['title'],
1036 $bits['parts']
1037 );
1038 } else {
1039 $ret = $this->parser->braceSubstitution( $bits, $this );
1040 if ( isset( $ret['object'] ) ) {
1041 $newIterator = $ret['object'];
1042 } else {
1043 $out .= $ret['text'];
1044 }
1045 }
1046 } elseif ( $contextName === 'tplarg' ) {
1047 # Triple-brace expansion
1048 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1049 if ( $flags & PPFrame::NO_ARGS ) {
1050 $newIterator = $this->virtualBracketedImplode(
1051 '{{{', '|', '}}}',
1052 $bits['title'],
1053 $bits['parts']
1054 );
1055 } else {
1056 $ret = $this->parser->argSubstitution( $bits, $this );
1057 if ( isset( $ret['object'] ) ) {
1058 $newIterator = $ret['object'];
1059 } else {
1060 $out .= $ret['text'];
1061 }
1062 }
1063 } elseif ( $contextName === 'comment' ) {
1064 # HTML-style comment
1065 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1066 # Not in RECOVER_COMMENTS mode (msgnw) though.
1067 if ( ( $this->parser->ot['html']
1068 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1071 ) {
1072 $out .= '';
1073 } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1074 # Add a strip marker in PST mode so that pstPass2() can
1075 # run some old-fashioned regexes on the result.
1076 # Not in RECOVER_COMMENTS mode (extractSections) though.
1077 $out .= $this->parser->insertStripItem( $contextChildren[0] );
1078 } else {
1079 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1080 $out .= $contextChildren[0];
1081 }
1082 } elseif ( $contextName === 'ignore' ) {
1083 # Output suppression used by <includeonly> etc.
1084 # OT_WIKI will only respect <ignore> in substed templates.
1085 # The other output types respect it unless NO_IGNORE is set.
1086 # extractSections() sets NO_IGNORE and so never respects it.
1087 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1088 || ( $flags & PPFrame::NO_IGNORE )
1089 ) {
1090 $out .= $contextChildren[0];
1091 } else {
1092 // $out .= '';
1093 }
1094 } elseif ( $contextName === 'ext' ) {
1095 # Extension tag
1096 $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
1097 [ 'attr' => null, 'inner' => null, 'close' => null ];
1098 if ( $flags & PPFrame::NO_TAGS ) {
1099 $s = '<' . $bits['name']->getFirstChild()->value;
1100 if ( $bits['attr'] ) {
1101 $s .= $bits['attr']->getFirstChild()->value;
1102 }
1103 if ( $bits['inner'] ) {
1104 $s .= '>' . $bits['inner']->getFirstChild()->value;
1105 if ( $bits['close'] ) {
1106 $s .= $bits['close']->getFirstChild()->value;
1107 }
1108 } else {
1109 $s .= '/>';
1110 }
1111 $out .= $s;
1112 } else {
1113 $out .= $this->parser->extensionSubstitution( $bits, $this );
1114 }
1115 } elseif ( $contextName === 'h' ) {
1116 # Heading
1117 if ( $this->parser->ot['html'] ) {
1118 # Expand immediately and insert heading index marker
1119 $s = $this->expand( $contextChildren, $flags );
1120 $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
1121 $titleText = $this->title->getPrefixedDBkey();
1122 $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
1123 $serial = count( $this->parser->mHeadings ) - 1;
1124 $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1125 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
1126 $this->parser->mStripState->addGeneral( $marker, '' );
1127 $out .= $s;
1128 } else {
1129 # Expand in virtual stack
1130 $newIterator = $contextChildren;
1131 }
1132 } else {
1133 # Generic recursive expansion
1134 $newIterator = $contextChildren;
1135 }
1136
1137 if ( $newIterator !== false ) {
1138 $outStack[] = '';
1139 $iteratorStack[] = $newIterator;
1140 $indexStack[] = 0;
1141 } elseif ( $iteratorStack[$level] === false ) {
1142 // Return accumulated value to parent
1143 // With tail recursion
1144 while ( $iteratorStack[$level] === false && $level > 0 ) {
1145 $outStack[$level - 1] .= $out;
1146 array_pop( $outStack );
1147 array_pop( $iteratorStack );
1148 array_pop( $indexStack );
1149 $level--;
1150 }
1151 }
1152 }
1153 --$expansionDepth;
1154 return $outStack[0];
1155 }
1156
1163 public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1164 $args = array_slice( func_get_args(), 2 );
1165
1166 $first = true;
1167 $s = '';
1168 foreach ( $args as $root ) {
1169 if ( $root instanceof PPNode_Hash_Array ) {
1170 $root = $root->value;
1171 }
1172 if ( !is_array( $root ) ) {
1173 $root = [ $root ];
1174 }
1175 foreach ( $root as $node ) {
1176 if ( $first ) {
1177 $first = false;
1178 } else {
1179 $s .= $sep;
1180 }
1181 $s .= $this->expand( $node, $flags );
1182 }
1183 }
1184 return $s;
1185 }
1186
1194 public function implode( $sep /*, ... */ ) {
1195 $args = array_slice( func_get_args(), 1 );
1196
1197 $first = true;
1198 $s = '';
1199 foreach ( $args as $root ) {
1200 if ( $root instanceof PPNode_Hash_Array ) {
1201 $root = $root->value;
1202 }
1203 if ( !is_array( $root ) ) {
1204 $root = [ $root ];
1205 }
1206 foreach ( $root as $node ) {
1207 if ( $first ) {
1208 $first = false;
1209 } else {
1210 $s .= $sep;
1211 }
1212 $s .= $this->expand( $node );
1213 }
1214 }
1215 return $s;
1216 }
1217
1226 public function virtualImplode( $sep /*, ... */ ) {
1227 $args = array_slice( func_get_args(), 1 );
1228 $out = [];
1229 $first = true;
1230
1231 foreach ( $args as $root ) {
1232 if ( $root instanceof PPNode_Hash_Array ) {
1233 $root = $root->value;
1234 }
1235 if ( !is_array( $root ) ) {
1236 $root = [ $root ];
1237 }
1238 foreach ( $root as $node ) {
1239 if ( $first ) {
1240 $first = false;
1241 } else {
1242 $out[] = $sep;
1243 }
1244 $out[] = $node;
1245 }
1246 }
1247 return new PPNode_Hash_Array( $out );
1248 }
1249
1259 public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1260 $args = array_slice( func_get_args(), 3 );
1261 $out = [ $start ];
1262 $first = true;
1263
1264 foreach ( $args as $root ) {
1265 if ( $root instanceof PPNode_Hash_Array ) {
1266 $root = $root->value;
1267 }
1268 if ( !is_array( $root ) ) {
1269 $root = [ $root ];
1270 }
1271 foreach ( $root as $node ) {
1272 if ( $first ) {
1273 $first = false;
1274 } else {
1275 $out[] = $sep;
1276 }
1277 $out[] = $node;
1278 }
1279 }
1280 $out[] = $end;
1281 return new PPNode_Hash_Array( $out );
1282 }
1283
1284 public function __toString() {
1285 return 'frame{}';
1286 }
1287
1292 public function getPDBK( $level = false ) {
1293 if ( $level === false ) {
1294 return $this->title->getPrefixedDBkey();
1295 } else {
1296 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
1297 }
1298 }
1299
1303 public function getArguments() {
1304 return [];
1305 }
1306
1310 public function getNumberedArguments() {
1311 return [];
1312 }
1313
1317 public function getNamedArguments() {
1318 return [];
1319 }
1320
1326 public function isEmpty() {
1327 return true;
1328 }
1329
1334 public function getArgument( $name ) {
1335 return false;
1336 }
1337
1345 public function loopCheck( $title ) {
1346 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1347 }
1348
1354 public function isTemplate() {
1355 return false;
1356 }
1357
1363 public function getTitle() {
1364 return $this->title;
1365 }
1366
1372 public function setVolatile( $flag = true ) {
1373 $this->volatile = $flag;
1374 }
1375
1381 public function isVolatile() {
1382 return $this->volatile;
1383 }
1384
1390 public function setTTL( $ttl ) {
1391 if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1392 $this->ttl = $ttl;
1393 }
1394 }
1395
1401 public function getTTL() {
1402 return $this->ttl;
1403 }
1404}
1405
1410// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1412 // @codingStandardsIgnoreEnd
1413
1416
1424 public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1425 $namedArgs = [], $title = false
1426 ) {
1427 parent::__construct( $preprocessor );
1428
1429 $this->parent = $parent;
1430 $this->numberedArgs = $numberedArgs;
1431 $this->namedArgs = $namedArgs;
1432 $this->title = $title;
1433 $pdbk = $title ? $title->getPrefixedDBkey() : false;
1434 $this->titleCache = $parent->titleCache;
1435 $this->titleCache[] = $pdbk;
1436 $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1437 if ( $pdbk !== false ) {
1438 $this->loopCheckHash[$pdbk] = true;
1439 }
1440 $this->depth = $parent->depth + 1;
1441 $this->numberedExpansionCache = $this->namedExpansionCache = [];
1442 }
1443
1444 public function __toString() {
1445 $s = 'tplframe{';
1446 $first = true;
1447 $args = $this->numberedArgs + $this->namedArgs;
1448 foreach ( $args as $name => $value ) {
1449 if ( $first ) {
1450 $first = false;
1451 } else {
1452 $s .= ', ';
1453 }
1454 $s .= "\"$name\":\"" .
1455 str_replace( '"', '\\"', $value->__toString() ) . '"';
1456 }
1457 $s .= '}';
1458 return $s;
1459 }
1460
1468 public function cachedExpand( $key, $root, $flags = 0 ) {
1469 if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1470 return $this->parent->childExpansionCache[$key];
1471 }
1472 $retval = $this->expand( $root, $flags );
1473 if ( !$this->isVolatile() ) {
1474 $this->parent->childExpansionCache[$key] = $retval;
1475 }
1476 return $retval;
1477 }
1478
1484 public function isEmpty() {
1485 return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1486 }
1487
1491 public function getArguments() {
1492 $arguments = [];
1493 foreach ( array_merge(
1494 array_keys( $this->numberedArgs ),
1495 array_keys( $this->namedArgs ) ) as $key ) {
1496 $arguments[$key] = $this->getArgument( $key );
1497 }
1498 return $arguments;
1499 }
1500
1504 public function getNumberedArguments() {
1505 $arguments = [];
1506 foreach ( array_keys( $this->numberedArgs ) as $key ) {
1507 $arguments[$key] = $this->getArgument( $key );
1508 }
1509 return $arguments;
1510 }
1511
1515 public function getNamedArguments() {
1516 $arguments = [];
1517 foreach ( array_keys( $this->namedArgs ) as $key ) {
1518 $arguments[$key] = $this->getArgument( $key );
1519 }
1520 return $arguments;
1521 }
1522
1527 public function getNumberedArgument( $index ) {
1528 if ( !isset( $this->numberedArgs[$index] ) ) {
1529 return false;
1530 }
1531 if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1532 # No trimming for unnamed arguments
1533 $this->numberedExpansionCache[$index] = $this->parent->expand(
1534 $this->numberedArgs[$index],
1536 );
1537 }
1538 return $this->numberedExpansionCache[$index];
1539 }
1540
1545 public function getNamedArgument( $name ) {
1546 if ( !isset( $this->namedArgs[$name] ) ) {
1547 return false;
1548 }
1549 if ( !isset( $this->namedExpansionCache[$name] ) ) {
1550 # Trim named arguments post-expand, for backwards compatibility
1551 $this->namedExpansionCache[$name] = trim(
1552 $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1553 }
1554 return $this->namedExpansionCache[$name];
1555 }
1556
1561 public function getArgument( $name ) {
1562 $text = $this->getNumberedArgument( $name );
1563 if ( $text === false ) {
1564 $text = $this->getNamedArgument( $name );
1565 }
1566 return $text;
1567 }
1568
1574 public function isTemplate() {
1575 return true;
1576 }
1577
1578 public function setVolatile( $flag = true ) {
1579 parent::setVolatile( $flag );
1580 $this->parent->setVolatile( $flag );
1581 }
1582
1583 public function setTTL( $ttl ) {
1584 parent::setTTL( $ttl );
1585 $this->parent->setTTL( $ttl );
1586 }
1587}
1588
1593// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1595 // @codingStandardsIgnoreEnd
1596
1597 public $args;
1598
1599 public function __construct( $preprocessor, $args ) {
1600 parent::__construct( $preprocessor );
1601 $this->args = $args;
1602 }
1603
1604 public function __toString() {
1605 $s = 'cstmframe{';
1606 $first = true;
1607 foreach ( $this->args as $name => $value ) {
1608 if ( $first ) {
1609 $first = false;
1610 } else {
1611 $s .= ', ';
1612 }
1613 $s .= "\"$name\":\"" .
1614 str_replace( '"', '\\"', $value->__toString() ) . '"';
1615 }
1616 $s .= '}';
1617 return $s;
1618 }
1619
1623 public function isEmpty() {
1624 return !count( $this->args );
1625 }
1626
1631 public function getArgument( $index ) {
1632 if ( !isset( $this->args[$index] ) ) {
1633 return false;
1634 }
1635 return $this->args[$index];
1636 }
1637
1638 public function getArguments() {
1639 return $this->args;
1640 }
1641}
1642
1646// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1647class PPNode_Hash_Tree implements PPNode {
1648 // @codingStandardsIgnoreEnd
1649
1650 public $name;
1651
1658
1662 private $store;
1663
1667 private $index;
1668
1673 const NAME = 0;
1674
1679 const CHILDREN = 1;
1680
1688 public function __construct( array $store, $index ) {
1689 $this->store = $store;
1690 $this->index = $index;
1691 list( $this->name, $this->rawChildren ) = $this->store[$index];
1692 }
1693
1702 public static function factory( array $store, $index ) {
1703 if ( !isset( $store[$index] ) ) {
1704 return false;
1705 }
1706
1707 $descriptor = $store[$index];
1708 if ( is_string( $descriptor ) ) {
1709 $class = 'PPNode_Hash_Text';
1710 } elseif ( is_array( $descriptor ) ) {
1711 if ( $descriptor[self::NAME][0] === '@' ) {
1712 $class = 'PPNode_Hash_Attr';
1713 } else {
1714 $class = 'PPNode_Hash_Tree';
1715 }
1716 } else {
1717 throw new MWException( __METHOD__.': invalid node descriptor' );
1718 }
1719 return new $class( $store, $index );
1720 }
1721
1725 public function __toString() {
1726 $inner = '';
1727 $attribs = '';
1728 for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
1729 if ( $node instanceof PPNode_Hash_Attr ) {
1730 $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
1731 } else {
1732 $inner .= $node->__toString();
1733 }
1734 }
1735 if ( $inner === '' ) {
1736 return "<{$this->name}$attribs/>";
1737 } else {
1738 return "<{$this->name}$attribs>$inner</{$this->name}>";
1739 }
1740 }
1741
1745 public function getChildren() {
1746 $children = [];
1747 foreach ( $this->rawChildren as $i => $child ) {
1748 $children[] = self::factory( $this->rawChildren, $i );
1749 }
1750 return new PPNode_Hash_Array( $children );
1751 }
1752
1760 public function getFirstChild() {
1761 if ( !isset( $this->rawChildren[0] ) ) {
1762 return false;
1763 } else {
1764 return self::factory( $this->rawChildren, 0 );
1765 }
1766 }
1767
1775 public function getNextSibling() {
1776 return self::factory( $this->store, $this->index + 1 );
1777 }
1778
1785 public function getChildrenOfType( $name ) {
1786 $children = [];
1787 foreach ( $this->rawChildren as $i => $child ) {
1788 if ( is_array( $child ) && $child[self::NAME] === $name ) {
1789 $children[] = self::factory( $this->rawChildren, $i );
1790 }
1791 }
1792 return new PPNode_Hash_Array( $children );
1793 }
1794
1799 public function getRawChildren() {
1800 return $this->rawChildren;
1801 }
1802
1806 public function getLength() {
1807 return false;
1808 }
1809
1814 public function item( $i ) {
1815 return false;
1816 }
1817
1821 public function getName() {
1822 return $this->name;
1823 }
1824
1834 public function splitArg() {
1835 return self::splitRawArg( $this->rawChildren );
1836 }
1837
1841 public static function splitRawArg( array $children ) {
1842 $bits = [];
1843 foreach ( $children as $i => $child ) {
1844 if ( !is_array( $child ) ) {
1845 continue;
1846 }
1847 if ( $child[self::NAME] === 'name' ) {
1848 $bits['name'] = new self( $children, $i );
1849 if ( isset( $child[self::CHILDREN][0][self::NAME] )
1850 && $child[self::CHILDREN][0][self::NAME] === '@index'
1851 ) {
1852 $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
1853 }
1854 } elseif ( $child[self::NAME] === 'value' ) {
1855 $bits['value'] = new self( $children, $i );
1856 }
1857 }
1858
1859 if ( !isset( $bits['name'] ) ) {
1860 throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
1861 }
1862 if ( !isset( $bits['index'] ) ) {
1863 $bits['index'] = '';
1864 }
1865 return $bits;
1866 }
1867
1875 public function splitExt() {
1876 return self::splitRawExt( $this->rawChildren );
1877 }
1878
1882 public static function splitRawExt( array $children ) {
1883 $bits = [];
1884 foreach ( $children as $i => $child ) {
1885 if ( !is_array( $child ) ) {
1886 continue;
1887 }
1888 switch ( $child[self::NAME] ) {
1889 case 'name':
1890 $bits['name'] = new self( $children, $i );
1891 break;
1892 case 'attr':
1893 $bits['attr'] = new self( $children, $i );
1894 break;
1895 case 'inner':
1896 $bits['inner'] = new self( $children, $i );
1897 break;
1898 case 'close':
1899 $bits['close'] = new self( $children, $i );
1900 break;
1901 }
1902 }
1903 if ( !isset( $bits['name'] ) ) {
1904 throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
1905 }
1906 return $bits;
1907 }
1908
1915 public function splitHeading() {
1916 if ( $this->name !== 'h' ) {
1917 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1918 }
1919 return self::splitRawHeading( $this->rawChildren );
1920 }
1921
1925 public static function splitRawHeading( array $children ) {
1926 $bits = [];
1927 foreach ( $children as $i => $child ) {
1928 if ( !is_array( $child ) ) {
1929 continue;
1930 }
1931 if ( $child[self::NAME] === '@i' ) {
1932 $bits['i'] = $child[self::CHILDREN][0];
1933 } elseif ( $child[self::NAME] === '@level' ) {
1934 $bits['level'] = $child[self::CHILDREN][0];
1935 }
1936 }
1937 if ( !isset( $bits['i'] ) ) {
1938 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1939 }
1940 return $bits;
1941 }
1942
1949 public function splitTemplate() {
1950 return self::splitRawTemplate( $this->rawChildren );
1951 }
1952
1956 public static function splitRawTemplate( array $children ) {
1957 $parts = [];
1958 $bits = [ 'lineStart' => '' ];
1959 foreach ( $children as $i => $child ) {
1960 if ( !is_array( $child ) ) {
1961 continue;
1962 }
1963 switch ( $child[self::NAME] ) {
1964 case 'title':
1965 $bits['title'] = new self( $children, $i );
1966 break;
1967 case 'part':
1968 $parts[] = new self( $children, $i );
1969 break;
1970 case '@lineStart':
1971 $bits['lineStart'] = '1';
1972 break;
1973 }
1974 }
1975 if ( !isset( $bits['title'] ) ) {
1976 throw new MWException( 'Invalid node passed to ' . __METHOD__ );
1977 }
1978 $bits['parts'] = new PPNode_Hash_Array( $parts );
1979 return $bits;
1980 }
1981}
1982
1986// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
1987class PPNode_Hash_Text implements PPNode {
1988 // @codingStandardsIgnoreEnd
1989
1990 public $value;
1991 private $store, $index;
1992
2000 public function __construct( array $store, $index ) {
2001 $this->value = $store[$index];
2002 if ( !is_scalar( $this->value ) ) {
2003 throw new MWException( __CLASS__ . ' given object instead of string' );
2004 }
2005 $this->store = $store;
2006 $this->index = $index;
2007 }
2008
2009 public function __toString() {
2010 return htmlspecialchars( $this->value );
2011 }
2012
2013 public function getNextSibling() {
2014 return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2015 }
2016
2017 public function getChildren() {
2018 return false;
2019 }
2020
2021 public function getFirstChild() {
2022 return false;
2023 }
2024
2025 public function getChildrenOfType( $name ) {
2026 return false;
2027 }
2028
2029 public function getLength() {
2030 return false;
2031 }
2032
2033 public function item( $i ) {
2034 return false;
2035 }
2036
2037 public function getName() {
2038 return '#text';
2039 }
2040
2041 public function splitArg() {
2042 throw new MWException( __METHOD__ . ': not supported' );
2043 }
2044
2045 public function splitExt() {
2046 throw new MWException( __METHOD__ . ': not supported' );
2047 }
2048
2049 public function splitHeading() {
2050 throw new MWException( __METHOD__ . ': not supported' );
2051 }
2052}
2053
2057// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2058class PPNode_Hash_Array implements PPNode {
2059 // @codingStandardsIgnoreEnd
2060
2061 public $value;
2062
2063 public function __construct( $value ) {
2064 $this->value = $value;
2065 }
2066
2067 public function __toString() {
2068 return var_export( $this, true );
2069 }
2070
2071 public function getLength() {
2072 return count( $this->value );
2073 }
2074
2075 public function item( $i ) {
2076 return $this->value[$i];
2077 }
2078
2079 public function getName() {
2080 return '#nodelist';
2081 }
2082
2083 public function getNextSibling() {
2084 return false;
2085 }
2086
2087 public function getChildren() {
2088 return false;
2089 }
2090
2091 public function getFirstChild() {
2092 return false;
2093 }
2094
2095 public function getChildrenOfType( $name ) {
2096 return false;
2097 }
2098
2099 public function splitArg() {
2100 throw new MWException( __METHOD__ . ': not supported' );
2101 }
2102
2103 public function splitExt() {
2104 throw new MWException( __METHOD__ . ': not supported' );
2105 }
2106
2107 public function splitHeading() {
2108 throw new MWException( __METHOD__ . ': not supported' );
2109 }
2110}
2111
2115// @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps
2116class PPNode_Hash_Attr implements PPNode {
2117 // @codingStandardsIgnoreEnd
2118
2119 public $name, $value;
2120 private $store, $index;
2121
2129 public function __construct( array $store, $index ) {
2130 $descriptor = $store[$index];
2131 if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
2132 throw new MWException( __METHOD__.': invalid name in attribute descriptor' );
2133 }
2134 $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
2135 $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
2136 $this->store = $store;
2137 $this->index = $index;
2138 }
2139
2140 public function __toString() {
2141 return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
2142 }
2143
2144 public function getName() {
2145 return $this->name;
2146 }
2147
2148 public function getNextSibling() {
2149 return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2150 }
2151
2152 public function getChildren() {
2153 return false;
2154 }
2155
2156 public function getFirstChild() {
2157 return false;
2158 }
2159
2160 public function getChildrenOfType( $name ) {
2161 return false;
2162 }
2163
2164 public function getLength() {
2165 return false;
2166 }
2167
2168 public function item( $i ) {
2169 return false;
2170 }
2171
2172 public function splitArg() {
2173 throw new MWException( __METHOD__ . ': not supported' );
2174 }
2175
2176 public function splitExt() {
2177 throw new MWException( __METHOD__ . ': not supported' );
2178 }
2179
2180 public function splitHeading() {
2181 throw new MWException( __METHOD__ . ': not supported' );
2182 }
2183}
$wgDisableLangConversion
Whether to enable language variant conversion.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if( $line===false) $args
Definition cdb.php:63
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:39
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1439
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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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:2753
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:1966
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:864
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:1975
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