MediaWiki  1.33.0
Preprocessor_Hash.php
Go to the documentation of this file.
1 <?php
42 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
44 
48  public $parser;
49 
50  const CACHE_PREFIX = 'preprocess-hash';
51  const CACHE_VERSION = 2;
52 
53  public function __construct( $parser ) {
54  $this->parser = $parser;
55  }
56 
60  public function newFrame() {
61  return new PPFrame_Hash( $this );
62  }
63 
68  public function newCustomFrame( $args ) {
69  return new PPCustomFrame_Hash( $this, $args );
70  }
71 
76  public function newPartNodeArray( $values ) {
77  $list = [];
78 
79  foreach ( $values as $k => $val ) {
80  if ( is_int( $k ) ) {
81  $store = [ [ 'part', [
82  [ 'name', [ [ '@index', [ $k ] ] ] ],
83  [ 'value', [ strval( $val ) ] ],
84  ] ] ];
85  } else {
86  $store = [ [ 'part', [
87  [ 'name', [ strval( $k ) ] ],
88  '=',
89  [ 'value', [ strval( $val ) ] ],
90  ] ] ];
91  }
92 
93  $list[] = new PPNode_Hash_Tree( $store, 0 );
94  }
95 
96  $node = new PPNode_Hash_Array( $list );
97  return $node;
98  }
99 
118  public function preprocessToObj( $text, $flags = 0 ) {
120 
121  $tree = $this->cacheGetTree( $text, $flags );
122  if ( $tree !== false ) {
123  $store = json_decode( $tree );
124  if ( is_array( $store ) ) {
125  return new PPNode_Hash_Tree( $store, 0 );
126  }
127  }
128 
129  $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
130 
131  $xmlishElements = $this->parser->getStripList();
132  $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
133  $enableOnlyinclude = false;
134  if ( $forInclusion ) {
135  $ignoredTags = [ 'includeonly', '/includeonly' ];
136  $ignoredElements = [ 'noinclude' ];
137  $xmlishElements[] = 'noinclude';
138  if ( strpos( $text, '<onlyinclude>' ) !== false
139  && strpos( $text, '</onlyinclude>' ) !== false
140  ) {
141  $enableOnlyinclude = true;
142  }
143  } else {
144  $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
145  $ignoredElements = [ 'includeonly' ];
146  $xmlishElements[] = 'includeonly';
147  }
148  $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
149 
150  // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
151  $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
152 
153  $stack = new PPDStack_Hash;
154 
155  $searchBase = "[{<\n";
156  if ( !$wgDisableLangConversion ) {
157  $searchBase .= '-';
158  }
159 
160  // For fast reverse searches
161  $revText = strrev( $text );
162  $lengthText = strlen( $text );
163 
164  // Input pointer, starts out pointing to a pseudo-newline before the start
165  $i = 0;
166  // Current accumulator. See the doc comment for Preprocessor_Hash for the format.
167  $accum =& $stack->getAccum();
168  // True to find equals signs in arguments
169  $findEquals = false;
170  // True to take notice of pipe characters
171  $findPipe = false;
172  $headingIndex = 1;
173  // True if $i is inside a possible heading
174  $inHeading = false;
175  // True if there are no more greater-than (>) signs right of $i
176  $noMoreGT = false;
177  // Map of tag name => true if there are no more closing tags of given type right of $i
178  $noMoreClosingTag = [];
179  // True to ignore all input up to the next <onlyinclude>
180  $findOnlyinclude = $enableOnlyinclude;
181  // Do a line-start run without outputting an LF character
182  $fakeLineStart = true;
183 
184  while ( true ) {
185  // $this->memCheck();
186 
187  if ( $findOnlyinclude ) {
188  // Ignore all input up to the next <onlyinclude>
189  $startPos = strpos( $text, '<onlyinclude>', $i );
190  if ( $startPos === false ) {
191  // Ignored section runs to the end
192  $accum[] = [ 'ignore', [ substr( $text, $i ) ] ];
193  break;
194  }
195  $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
196  $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
197  $i = $tagEndPos;
198  $findOnlyinclude = false;
199  }
200 
201  if ( $fakeLineStart ) {
202  $found = 'line-start';
203  $curChar = '';
204  } else {
205  # Find next opening brace, closing brace or pipe
206  $search = $searchBase;
207  if ( $stack->top === false ) {
208  $currentClosing = '';
209  } else {
210  $currentClosing = $stack->top->close;
211  $search .= $currentClosing;
212  }
213  if ( $findPipe ) {
214  $search .= '|';
215  }
216  if ( $findEquals ) {
217  // First equals will be for the template
218  $search .= '=';
219  }
220  $rule = null;
221  # Output literal section, advance input counter
222  $literalLength = strcspn( $text, $search, $i );
223  if ( $literalLength > 0 ) {
224  self::addLiteral( $accum, substr( $text, $i, $literalLength ) );
225  $i += $literalLength;
226  }
227  if ( $i >= $lengthText ) {
228  if ( $currentClosing == "\n" ) {
229  // Do a past-the-end run to finish off the heading
230  $curChar = '';
231  $found = 'line-end';
232  } else {
233  # All done
234  break;
235  }
236  } else {
237  $curChar = $curTwoChar = $text[$i];
238  if ( ( $i + 1 ) < $lengthText ) {
239  $curTwoChar .= $text[$i + 1];
240  }
241  if ( $curChar == '|' ) {
242  $found = 'pipe';
243  } elseif ( $curChar == '=' ) {
244  $found = 'equals';
245  } elseif ( $curChar == '<' ) {
246  $found = 'angle';
247  } elseif ( $curChar == "\n" ) {
248  if ( $inHeading ) {
249  $found = 'line-end';
250  } else {
251  $found = 'line-start';
252  }
253  } elseif ( $curTwoChar == $currentClosing ) {
254  $found = 'close';
255  $curChar = $curTwoChar;
256  } elseif ( $curChar == $currentClosing ) {
257  $found = 'close';
258  } elseif ( isset( $this->rules[$curTwoChar] ) ) {
259  $curChar = $curTwoChar;
260  $found = 'open';
261  $rule = $this->rules[$curChar];
262  } elseif ( isset( $this->rules[$curChar] ) ) {
263  $found = 'open';
264  $rule = $this->rules[$curChar];
265  } else {
266  # Some versions of PHP have a strcspn which stops on
267  # null characters; ignore these and continue.
268  # We also may get '-' and '}' characters here which
269  # don't match -{ or $currentClosing. Add these to
270  # output and continue.
271  if ( $curChar == '-' || $curChar == '}' ) {
272  self::addLiteral( $accum, $curChar );
273  }
274  ++$i;
275  continue;
276  }
277  }
278  }
279 
280  if ( $found == 'angle' ) {
281  $matches = false;
282  // Handle </onlyinclude>
283  if ( $enableOnlyinclude
284  && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
285  ) {
286  $findOnlyinclude = true;
287  continue;
288  }
289 
290  // Determine element name
291  if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
292  // Element name missing or not listed
293  self::addLiteral( $accum, '<' );
294  ++$i;
295  continue;
296  }
297  // Handle comments
298  if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
299  // To avoid leaving blank lines, when a sequence of
300  // space-separated comments is both preceded and followed by
301  // a newline (ignoring spaces), then
302  // trim leading and trailing spaces and the trailing newline.
303 
304  // Find the end
305  $endPos = strpos( $text, '-->', $i + 4 );
306  if ( $endPos === false ) {
307  // Unclosed comment in input, runs to end
308  $inner = substr( $text, $i );
309  $accum[] = [ 'comment', [ $inner ] ];
310  $i = $lengthText;
311  } else {
312  // Search backwards for leading whitespace
313  $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
314 
315  // Search forwards for trailing whitespace
316  // $wsEnd will be the position of the last space (or the '>' if there's none)
317  $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
318 
319  // Keep looking forward as long as we're finding more
320  // comments.
321  $comments = [ [ $wsStart, $wsEnd ] ];
322  while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
323  $c = strpos( $text, '-->', $wsEnd + 4 );
324  if ( $c === false ) {
325  break;
326  }
327  $c = $c + 2 + strspn( $text, " \t", $c + 3 );
328  $comments[] = [ $wsEnd + 1, $c ];
329  $wsEnd = $c;
330  }
331 
332  // Eat the line if possible
333  // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
334  // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
335  // it's a possible beneficial b/c break.
336  if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
337  && substr( $text, $wsEnd + 1, 1 ) == "\n"
338  ) {
339  // Remove leading whitespace from the end of the accumulator
340  $wsLength = $i - $wsStart;
341  $endIndex = count( $accum ) - 1;
342 
343  // Sanity check
344  if ( $wsLength > 0
345  && $endIndex >= 0
346  && is_string( $accum[$endIndex] )
347  && strspn( $accum[$endIndex], " \t", -$wsLength ) === $wsLength
348  ) {
349  $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
350  }
351 
352  // Dump all but the last comment to the accumulator
353  foreach ( $comments as $j => $com ) {
354  $startPos = $com[0];
355  $endPos = $com[1] + 1;
356  if ( $j == ( count( $comments ) - 1 ) ) {
357  break;
358  }
359  $inner = substr( $text, $startPos, $endPos - $startPos );
360  $accum[] = [ 'comment', [ $inner ] ];
361  }
362 
363  // Do a line-start run next time to look for headings after the comment
364  $fakeLineStart = true;
365  } else {
366  // No line to eat, just take the comment itself
367  $startPos = $i;
368  $endPos += 2;
369  }
370 
371  if ( $stack->top ) {
372  $part = $stack->top->getCurrentPart();
373  if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
374  $part->visualEnd = $wsStart;
375  }
376  // Else comments abutting, no change in visual end
377  $part->commentEnd = $endPos;
378  }
379  $i = $endPos + 1;
380  $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
381  $accum[] = [ 'comment', [ $inner ] ];
382  }
383  continue;
384  }
385  $name = $matches[1];
386  $lowerName = strtolower( $name );
387  $attrStart = $i + strlen( $name ) + 1;
388 
389  // Find end of tag
390  $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
391  if ( $tagEndPos === false ) {
392  // Infinite backtrack
393  // Disable tag search to prevent worst-case O(N^2) performance
394  $noMoreGT = true;
395  self::addLiteral( $accum, '<' );
396  ++$i;
397  continue;
398  }
399 
400  // Handle ignored tags
401  if ( in_array( $lowerName, $ignoredTags ) ) {
402  $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
403  $i = $tagEndPos + 1;
404  continue;
405  }
406 
407  $tagStartPos = $i;
408  if ( $text[$tagEndPos - 1] == '/' ) {
409  // Short end tag
410  $attrEnd = $tagEndPos - 1;
411  $inner = null;
412  $i = $tagEndPos + 1;
413  $close = null;
414  } else {
415  $attrEnd = $tagEndPos;
416  // Find closing tag
417  if (
418  !isset( $noMoreClosingTag[$name] ) &&
419  preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
420  $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
421  ) {
422  $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
423  $i = $matches[0][1] + strlen( $matches[0][0] );
424  $close = $matches[0][0];
425  } else {
426  // No end tag
427  if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
428  // Let it run out to the end of the text.
429  $inner = substr( $text, $tagEndPos + 1 );
430  $i = $lengthText;
431  $close = null;
432  } else {
433  // Don't match the tag, treat opening tag as literal and resume parsing.
434  $i = $tagEndPos + 1;
435  self::addLiteral( $accum,
436  substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
437  // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
438  $noMoreClosingTag[$name] = true;
439  continue;
440  }
441  }
442  }
443  // <includeonly> and <noinclude> just become <ignore> tags
444  if ( in_array( $lowerName, $ignoredElements ) ) {
445  $accum[] = [ 'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
446  continue;
447  }
448 
449  if ( $attrEnd <= $attrStart ) {
450  $attr = '';
451  } else {
452  // Note that the attr element contains the whitespace between name and attribute,
453  // this is necessary for precise reconstruction during pre-save transform.
454  $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
455  }
456 
457  $children = [
458  [ 'name', [ $name ] ],
459  [ 'attr', [ $attr ] ] ];
460  if ( $inner !== null ) {
461  $children[] = [ 'inner', [ $inner ] ];
462  }
463  if ( $close !== null ) {
464  $children[] = [ 'close', [ $close ] ];
465  }
466  $accum[] = [ 'ext', $children ];
467  } elseif ( $found == 'line-start' ) {
468  // Is this the start of a heading?
469  // Line break belongs before the heading element in any case
470  if ( $fakeLineStart ) {
471  $fakeLineStart = false;
472  } else {
473  self::addLiteral( $accum, $curChar );
474  $i++;
475  }
476 
477  $count = strspn( $text, '=', $i, 6 );
478  if ( $count == 1 && $findEquals ) {
479  // DWIM: This looks kind of like a name/value separator.
480  // Let's let the equals handler have it and break the potential
481  // heading. This is heuristic, but AFAICT the methods for
482  // completely correct disambiguation are very complex.
483  } elseif ( $count > 0 ) {
484  $piece = [
485  'open' => "\n",
486  'close' => "\n",
487  'parts' => [ new PPDPart_Hash( str_repeat( '=', $count ) ) ],
488  'startPos' => $i,
489  'count' => $count ];
490  $stack->push( $piece );
491  $accum =& $stack->getAccum();
492  $stackFlags = $stack->getFlags();
493  if ( isset( $stackFlags['findEquals'] ) ) {
494  $findEquals = $stackFlags['findEquals'];
495  }
496  if ( isset( $stackFlags['findPipe'] ) ) {
497  $findPipe = $stackFlags['findPipe'];
498  }
499  if ( isset( $stackFlags['inHeading'] ) ) {
500  $inHeading = $stackFlags['inHeading'];
501  }
502  $i += $count;
503  }
504  } elseif ( $found == 'line-end' ) {
505  $piece = $stack->top;
506  // A heading must be open, otherwise \n wouldn't have been in the search list
507  // FIXME: Don't use assert()
508  // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.assert
509  assert( $piece->open === "\n" );
510  $part = $piece->getCurrentPart();
511  // Search back through the input to see if it has a proper close.
512  // Do this using the reversed string since the other solutions
513  // (end anchor, etc.) are inefficient.
514  $wsLength = strspn( $revText, " \t", $lengthText - $i );
515  $searchStart = $i - $wsLength;
516  if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
517  // Comment found at line end
518  // Search for equals signs before the comment
519  $searchStart = $part->visualEnd;
520  $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
521  }
522  $count = $piece->count;
523  $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
524  if ( $equalsLength > 0 ) {
525  if ( $searchStart - $equalsLength == $piece->startPos ) {
526  // This is just a single string of equals signs on its own line
527  // Replicate the doHeadings behavior /={count}(.+)={count}/
528  // First find out how many equals signs there really are (don't stop at 6)
529  $count = $equalsLength;
530  if ( $count < 3 ) {
531  $count = 0;
532  } else {
533  $count = min( 6, intval( ( $count - 1 ) / 2 ) );
534  }
535  } else {
536  $count = min( $equalsLength, $count );
537  }
538  if ( $count > 0 ) {
539  // Normal match, output <h>
540  $element = [ [ 'possible-h',
541  array_merge(
542  [
543  [ '@level', [ $count ] ],
544  [ '@i', [ $headingIndex++ ] ]
545  ],
546  $accum
547  )
548  ] ];
549  } else {
550  // Single equals sign on its own line, count=0
551  $element = $accum;
552  }
553  } else {
554  // No match, no <h>, just pass down the inner text
555  $element = $accum;
556  }
557  // Unwind the stack
558  $stack->pop();
559  $accum =& $stack->getAccum();
560  $stackFlags = $stack->getFlags();
561  if ( isset( $stackFlags['findEquals'] ) ) {
562  $findEquals = $stackFlags['findEquals'];
563  }
564  if ( isset( $stackFlags['findPipe'] ) ) {
565  $findPipe = $stackFlags['findPipe'];
566  }
567  if ( isset( $stackFlags['inHeading'] ) ) {
568  $inHeading = $stackFlags['inHeading'];
569  }
570 
571  // Append the result to the enclosing accumulator
572  array_splice( $accum, count( $accum ), 0, $element );
573 
574  // Note that we do NOT increment the input pointer.
575  // This is because the closing linebreak could be the opening linebreak of
576  // another heading. Infinite loops are avoided because the next iteration MUST
577  // hit the heading open case above, which unconditionally increments the
578  // input pointer.
579  } elseif ( $found == 'open' ) {
580  # count opening brace characters
581  $curLen = strlen( $curChar );
582  $count = ( $curLen > 1 ) ?
583  # allow the final character to repeat
584  strspn( $text, $curChar[$curLen - 1], $i + 1 ) + 1 :
585  strspn( $text, $curChar, $i );
586 
587  $savedPrefix = '';
588  $lineStart = ( $i > 0 && $text[$i - 1] == "\n" );
589 
590  if ( $curChar === "-{" && $count > $curLen ) {
591  // -{ => {{ transition because rightmost wins
592  $savedPrefix = '-';
593  $i++;
594  $curChar = '{';
595  $count--;
596  $rule = $this->rules[$curChar];
597  }
598 
599  # we need to add to stack only if opening brace count is enough for one of the rules
600  if ( $count >= $rule['min'] ) {
601  # Add it to the stack
602  $piece = [
603  'open' => $curChar,
604  'close' => $rule['end'],
605  'savedPrefix' => $savedPrefix,
606  'count' => $count,
607  'lineStart' => $lineStart,
608  ];
609 
610  $stack->push( $piece );
611  $accum =& $stack->getAccum();
612  $stackFlags = $stack->getFlags();
613  if ( isset( $stackFlags['findEquals'] ) ) {
614  $findEquals = $stackFlags['findEquals'];
615  }
616  if ( isset( $stackFlags['findPipe'] ) ) {
617  $findPipe = $stackFlags['findPipe'];
618  }
619  if ( isset( $stackFlags['inHeading'] ) ) {
620  $inHeading = $stackFlags['inHeading'];
621  }
622  } else {
623  # Add literal brace(s)
624  self::addLiteral( $accum, $savedPrefix . str_repeat( $curChar, $count ) );
625  }
626  $i += $count;
627  } elseif ( $found == 'close' ) {
628  $piece = $stack->top;
629  # lets check if there are enough characters for closing brace
630  $maxCount = $piece->count;
631  if ( $piece->close === '}-' && $curChar === '}' ) {
632  $maxCount--; # don't try to match closing '-' as a '}'
633  }
634  $curLen = strlen( $curChar );
635  $count = ( $curLen > 1 ) ? $curLen :
636  strspn( $text, $curChar, $i, $maxCount );
637 
638  # check for maximum matching characters (if there are 5 closing
639  # characters, we will probably need only 3 - depending on the rules)
640  $rule = $this->rules[$piece->open];
641  if ( $count > $rule['max'] ) {
642  # The specified maximum exists in the callback array, unless the caller
643  # has made an error
644  $matchingCount = $rule['max'];
645  } else {
646  # Count is less than the maximum
647  # Skip any gaps in the callback array to find the true largest match
648  # Need to use array_key_exists not isset because the callback can be null
649  $matchingCount = $count;
650  while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
651  --$matchingCount;
652  }
653  }
654 
655  if ( $matchingCount <= 0 ) {
656  # No matching element found in callback array
657  # Output a literal closing brace and continue
658  $endText = substr( $text, $i, $count );
659  self::addLiteral( $accum, $endText );
660  $i += $count;
661  continue;
662  }
663  $name = $rule['names'][$matchingCount];
664  if ( $name === null ) {
665  // No element, just literal text
666  $endText = substr( $text, $i, $matchingCount );
667  $element = $piece->breakSyntax( $matchingCount );
668  self::addLiteral( $element, $endText );
669  } else {
670  # Create XML element
671  $parts = $piece->parts;
672  $titleAccum = $parts[0]->out;
673  unset( $parts[0] );
674 
675  $children = [];
676 
677  # The invocation is at the start of the line if lineStart is set in
678  # the stack, and all opening brackets are used up.
679  if ( $maxCount == $matchingCount &&
680  !empty( $piece->lineStart ) &&
681  strlen( $piece->savedPrefix ) == 0 ) {
682  $children[] = [ '@lineStart', [ 1 ] ];
683  }
684  $titleNode = [ 'title', $titleAccum ];
685  $children[] = $titleNode;
686  $argIndex = 1;
687  foreach ( $parts as $part ) {
688  if ( isset( $part->eqpos ) ) {
689  $equalsNode = $part->out[$part->eqpos];
690  $nameNode = [ 'name', array_slice( $part->out, 0, $part->eqpos ) ];
691  $valueNode = [ 'value', array_slice( $part->out, $part->eqpos + 1 ) ];
692  $partNode = [ 'part', [ $nameNode, $equalsNode, $valueNode ] ];
693  $children[] = $partNode;
694  } else {
695  $nameNode = [ 'name', [ [ '@index', [ $argIndex++ ] ] ] ];
696  $valueNode = [ 'value', $part->out ];
697  $partNode = [ 'part', [ $nameNode, $valueNode ] ];
698  $children[] = $partNode;
699  }
700  }
701  $element = [ [ $name, $children ] ];
702  }
703 
704  # Advance input pointer
705  $i += $matchingCount;
706 
707  # Unwind the stack
708  $stack->pop();
709  $accum =& $stack->getAccum();
710 
711  # Re-add the old stack element if it still has unmatched opening characters remaining
712  if ( $matchingCount < $piece->count ) {
713  $piece->parts = [ new PPDPart_Hash ];
714  $piece->count -= $matchingCount;
715  # do we still qualify for any callback with remaining count?
716  $min = $this->rules[$piece->open]['min'];
717  if ( $piece->count >= $min ) {
718  $stack->push( $piece );
719  $accum =& $stack->getAccum();
720  } elseif ( $piece->count == 1 && $piece->open === '{' && $piece->savedPrefix === '-' ) {
721  $piece->savedPrefix = '';
722  $piece->open = '-{';
723  $piece->count = 2;
724  $piece->close = $this->rules[$piece->open]['end'];
725  $stack->push( $piece );
726  $accum =& $stack->getAccum();
727  } else {
728  $s = substr( $piece->open, 0, -1 );
729  $s .= str_repeat(
730  substr( $piece->open, -1 ),
731  $piece->count - strlen( $s )
732  );
733  self::addLiteral( $accum, $piece->savedPrefix . $s );
734  }
735  } elseif ( $piece->savedPrefix !== '' ) {
736  self::addLiteral( $accum, $piece->savedPrefix );
737  }
738 
739  $stackFlags = $stack->getFlags();
740  if ( isset( $stackFlags['findEquals'] ) ) {
741  $findEquals = $stackFlags['findEquals'];
742  }
743  if ( isset( $stackFlags['findPipe'] ) ) {
744  $findPipe = $stackFlags['findPipe'];
745  }
746  if ( isset( $stackFlags['inHeading'] ) ) {
747  $inHeading = $stackFlags['inHeading'];
748  }
749 
750  # Add XML element to the enclosing accumulator
751  array_splice( $accum, count( $accum ), 0, $element );
752  } elseif ( $found == 'pipe' ) {
753  $findEquals = true; // shortcut for getFlags()
754  $stack->addPart();
755  $accum =& $stack->getAccum();
756  ++$i;
757  } elseif ( $found == 'equals' ) {
758  $findEquals = false; // shortcut for getFlags()
759  $accum[] = [ 'equals', [ '=' ] ];
760  $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
761  ++$i;
762  }
763  }
764 
765  # Output any remaining unclosed brackets
766  foreach ( $stack->stack as $piece ) {
767  array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
768  }
769 
770  # Enable top-level headings
771  foreach ( $stack->rootAccum as &$node ) {
772  if ( is_array( $node ) && $node[PPNode_Hash_Tree::NAME] === 'possible-h' ) {
773  $node[PPNode_Hash_Tree::NAME] = 'h';
774  }
775  }
776 
777  $rootStore = [ [ 'root', $stack->rootAccum ] ];
778  $rootNode = new PPNode_Hash_Tree( $rootStore, 0 );
779 
780  // Cache
781  $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
782  if ( $tree !== false ) {
783  $this->cacheSetTree( $text, $flags, $tree );
784  }
785 
786  return $rootNode;
787  }
788 
789  private static function addLiteral( array &$accum, $text ) {
790  $n = count( $accum );
791  if ( $n && is_string( $accum[$n - 1] ) ) {
792  $accum[$n - 1] .= $text;
793  } else {
794  $accum[] = $text;
795  }
796  }
797 }
798 
803 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
804 class PPDStack_Hash extends PPDStack {
805 
806  public function __construct() {
807  $this->elementClass = PPDStackElement_Hash::class;
808  parent::__construct();
809  $this->rootAccum = [];
810  }
811 }
812 
816 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
817 class PPDStackElement_Hash extends PPDStackElement {
818 
819  public function __construct( $data = [] ) {
820  $this->partClass = PPDPart_Hash::class;
821  parent::__construct( $data );
822  }
823 
830  public function breakSyntax( $openingCount = false ) {
831  if ( $this->open == "\n" ) {
832  $accum = array_merge( [ $this->savedPrefix ], $this->parts[0]->out );
833  } else {
834  if ( $openingCount === false ) {
835  $openingCount = $this->count;
836  }
837  $s = substr( $this->open, 0, -1 );
838  $s .= str_repeat(
839  substr( $this->open, -1 ),
840  $openingCount - strlen( $s )
841  );
842  $accum = [ $this->savedPrefix . $s ];
843  $lastIndex = 0;
844  $first = true;
845  foreach ( $this->parts as $part ) {
846  if ( $first ) {
847  $first = false;
848  } elseif ( is_string( $accum[$lastIndex] ) ) {
849  $accum[$lastIndex] .= '|';
850  } else {
851  $accum[++$lastIndex] = '|';
852  }
853  foreach ( $part->out as $node ) {
854  if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
855  $accum[$lastIndex] .= $node;
856  } else {
857  $accum[++$lastIndex] = $node;
858  }
859  }
860  }
861  }
862  return $accum;
863  }
864 }
865 
869 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
870 class PPDPart_Hash extends PPDPart {
871 
872  public function __construct( $out = '' ) {
873  if ( $out !== '' ) {
874  $accum = [ $out ];
875  } else {
876  $accum = [];
877  }
878  parent::__construct( $accum );
879  }
880 }
881 
886 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
887 class PPFrame_Hash implements PPFrame {
888 
892  public $parser;
893 
897  public $preprocessor;
898 
902  public $title;
903  public $titleCache;
904 
909  public $loopCheckHash;
910 
915  public $depth;
916 
917  private $volatile = false;
918  private $ttl = null;
919 
923  protected $childExpansionCache;
924 
929  public function __construct( $preprocessor ) {
930  $this->preprocessor = $preprocessor;
931  $this->parser = $preprocessor->parser;
932  $this->title = $this->parser->mTitle;
933  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
934  $this->loopCheckHash = [];
935  $this->depth = 0;
936  $this->childExpansionCache = [];
937  }
938 
949  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
950  $namedArgs = [];
951  $numberedArgs = [];
952  if ( $title === false ) {
953  $title = $this->title;
954  }
955  if ( $args !== false ) {
956  if ( $args instanceof PPNode_Hash_Array ) {
957  $args = $args->value;
958  } elseif ( !is_array( $args ) ) {
959  throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
960  }
961  foreach ( $args as $arg ) {
962  $bits = $arg->splitArg();
963  if ( $bits['index'] !== '' ) {
964  // Numbered parameter
965  $index = $bits['index'] - $indexOffset;
966  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
967  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
968  wfEscapeWikiText( $this->title ),
969  wfEscapeWikiText( $title ),
970  wfEscapeWikiText( $index ) )->text() );
971  $this->parser->addTrackingCategory( 'duplicate-args-category' );
972  }
973  $numberedArgs[$index] = $bits['value'];
974  unset( $namedArgs[$index] );
975  } else {
976  // Named parameter
977  $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
978  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
979  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
980  wfEscapeWikiText( $this->title ),
981  wfEscapeWikiText( $title ),
982  wfEscapeWikiText( $name ) )->text() );
983  $this->parser->addTrackingCategory( 'duplicate-args-category' );
984  }
985  $namedArgs[$name] = $bits['value'];
986  unset( $numberedArgs[$name] );
987  }
988  }
989  }
990  return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
991  }
992 
1000  public function cachedExpand( $key, $root, $flags = 0 ) {
1001  // we don't have a parent, so we don't have a cache
1002  return $this->expand( $root, $flags );
1003  }
1004 
1011  public function expand( $root, $flags = 0 ) {
1012  static $expansionDepth = 0;
1013  if ( is_string( $root ) ) {
1014  return $root;
1015  }
1016 
1017  if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
1018  $this->parser->limitationWarn( 'node-count-exceeded',
1019  $this->parser->mPPNodeCount,
1020  $this->parser->mOptions->getMaxPPNodeCount()
1021  );
1022  return '<span class="error">Node-count limit exceeded</span>';
1023  }
1024  if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
1025  $this->parser->limitationWarn( 'expansion-depth-exceeded',
1026  $expansionDepth,
1027  $this->parser->mOptions->getMaxPPExpandDepth()
1028  );
1029  return '<span class="error">Expansion depth limit exceeded</span>';
1030  }
1031  ++$expansionDepth;
1032  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
1033  $this->parser->mHighestExpansionDepth = $expansionDepth;
1034  }
1035 
1036  $outStack = [ '', '' ];
1037  $iteratorStack = [ false, $root ];
1038  $indexStack = [ 0, 0 ];
1039 
1040  while ( count( $iteratorStack ) > 1 ) {
1041  $level = count( $outStack ) - 1;
1042  $iteratorNode =& $iteratorStack[$level];
1043  $out =& $outStack[$level];
1044  $index =& $indexStack[$level];
1045 
1046  if ( is_array( $iteratorNode ) ) {
1047  if ( $index >= count( $iteratorNode ) ) {
1048  // All done with this iterator
1049  $iteratorStack[$level] = false;
1050  $contextNode = false;
1051  } else {
1052  $contextNode = $iteratorNode[$index];
1053  $index++;
1054  }
1055  } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
1056  if ( $index >= $iteratorNode->getLength() ) {
1057  // All done with this iterator
1058  $iteratorStack[$level] = false;
1059  $contextNode = false;
1060  } else {
1061  $contextNode = $iteratorNode->item( $index );
1062  $index++;
1063  }
1064  } else {
1065  // Copy to $contextNode and then delete from iterator stack,
1066  // because this is not an iterator but we do have to execute it once
1067  $contextNode = $iteratorStack[$level];
1068  $iteratorStack[$level] = false;
1069  }
1070 
1071  $newIterator = false;
1072  $contextName = false;
1073  $contextChildren = false;
1074 
1075  if ( $contextNode === false ) {
1076  // nothing to do
1077  } elseif ( is_string( $contextNode ) ) {
1078  $out .= $contextNode;
1079  } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
1080  $newIterator = $contextNode;
1081  } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
1082  // No output
1083  } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
1084  $out .= $contextNode->value;
1085  } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
1086  $contextName = $contextNode->name;
1087  $contextChildren = $contextNode->getRawChildren();
1088  } elseif ( is_array( $contextNode ) ) {
1089  // Node descriptor array
1090  if ( count( $contextNode ) !== 2 ) {
1091  throw new MWException( __METHOD__ .
1092  ': found an array where a node descriptor should be' );
1093  }
1094  list( $contextName, $contextChildren ) = $contextNode;
1095  } else {
1096  throw new MWException( __METHOD__ . ': Invalid parameter type' );
1097  }
1098 
1099  // Handle node descriptor array or tree object
1100  if ( $contextName === false ) {
1101  // Not a node, already handled above
1102  } elseif ( $contextName[0] === '@' ) {
1103  // Attribute: no output
1104  } elseif ( $contextName === 'template' ) {
1105  # Double-brace expansion
1106  $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1107  if ( $flags & PPFrame::NO_TEMPLATES ) {
1108  $newIterator = $this->virtualBracketedImplode(
1109  '{{', '|', '}}',
1110  $bits['title'],
1111  $bits['parts']
1112  );
1113  } else {
1114  $ret = $this->parser->braceSubstitution( $bits, $this );
1115  if ( isset( $ret['object'] ) ) {
1116  $newIterator = $ret['object'];
1117  } else {
1118  $out .= $ret['text'];
1119  }
1120  }
1121  } elseif ( $contextName === 'tplarg' ) {
1122  # Triple-brace expansion
1123  $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
1124  if ( $flags & PPFrame::NO_ARGS ) {
1125  $newIterator = $this->virtualBracketedImplode(
1126  '{{{', '|', '}}}',
1127  $bits['title'],
1128  $bits['parts']
1129  );
1130  } else {
1131  $ret = $this->parser->argSubstitution( $bits, $this );
1132  if ( isset( $ret['object'] ) ) {
1133  $newIterator = $ret['object'];
1134  } else {
1135  $out .= $ret['text'];
1136  }
1137  }
1138  } elseif ( $contextName === 'comment' ) {
1139  # HTML-style comment
1140  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1141  # Not in RECOVER_COMMENTS mode (msgnw) though.
1142  if ( ( $this->parser->ot['html']
1143  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
1144  || ( $flags & PPFrame::STRIP_COMMENTS )
1145  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
1146  ) {
1147  $out .= '';
1148  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
1149  # Add a strip marker in PST mode so that pstPass2() can
1150  # run some old-fashioned regexes on the result.
1151  # Not in RECOVER_COMMENTS mode (extractSections) though.
1152  $out .= $this->parser->insertStripItem( $contextChildren[0] );
1153  } else {
1154  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1155  $out .= $contextChildren[0];
1156  }
1157  } elseif ( $contextName === 'ignore' ) {
1158  # Output suppression used by <includeonly> etc.
1159  # OT_WIKI will only respect <ignore> in substed templates.
1160  # The other output types respect it unless NO_IGNORE is set.
1161  # extractSections() sets NO_IGNORE and so never respects it.
1162  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
1163  || ( $flags & PPFrame::NO_IGNORE )
1164  ) {
1165  $out .= $contextChildren[0];
1166  } else {
1167  // $out .= '';
1168  }
1169  } elseif ( $contextName === 'ext' ) {
1170  # Extension tag
1171  $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
1172  [ 'attr' => null, 'inner' => null, 'close' => null ];
1173  if ( $flags & PPFrame::NO_TAGS ) {
1174  $s = '<' . $bits['name']->getFirstChild()->value;
1175  if ( $bits['attr'] ) {
1176  $s .= $bits['attr']->getFirstChild()->value;
1177  }
1178  if ( $bits['inner'] ) {
1179  $s .= '>' . $bits['inner']->getFirstChild()->value;
1180  if ( $bits['close'] ) {
1181  $s .= $bits['close']->getFirstChild()->value;
1182  }
1183  } else {
1184  $s .= '/>';
1185  }
1186  $out .= $s;
1187  } else {
1188  $out .= $this->parser->extensionSubstitution( $bits, $this );
1189  }
1190  } elseif ( $contextName === 'h' ) {
1191  # Heading
1192  if ( $this->parser->ot['html'] ) {
1193  # Expand immediately and insert heading index marker
1194  $s = $this->expand( $contextChildren, $flags );
1195  $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
1196  $titleText = $this->title->getPrefixedDBkey();
1197  $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
1198  $serial = count( $this->parser->mHeadings ) - 1;
1199  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
1200  $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
1201  $this->parser->mStripState->addGeneral( $marker, '' );
1202  $out .= $s;
1203  } else {
1204  # Expand in virtual stack
1205  $newIterator = $contextChildren;
1206  }
1207  } else {
1208  # Generic recursive expansion
1209  $newIterator = $contextChildren;
1210  }
1211 
1212  if ( $newIterator !== false ) {
1213  $outStack[] = '';
1214  $iteratorStack[] = $newIterator;
1215  $indexStack[] = 0;
1216  } elseif ( $iteratorStack[$level] === false ) {
1217  // Return accumulated value to parent
1218  // With tail recursion
1219  while ( $iteratorStack[$level] === false && $level > 0 ) {
1220  $outStack[$level - 1] .= $out;
1221  array_pop( $outStack );
1222  array_pop( $iteratorStack );
1223  array_pop( $indexStack );
1224  $level--;
1225  }
1226  }
1227  }
1228  --$expansionDepth;
1229  return $outStack[0];
1230  }
1231 
1238  public function implodeWithFlags( $sep, $flags /*, ... */ ) {
1239  $args = array_slice( func_get_args(), 2 );
1240 
1241  $first = true;
1242  $s = '';
1243  foreach ( $args as $root ) {
1244  if ( $root instanceof PPNode_Hash_Array ) {
1245  $root = $root->value;
1246  }
1247  if ( !is_array( $root ) ) {
1248  $root = [ $root ];
1249  }
1250  foreach ( $root as $node ) {
1251  if ( $first ) {
1252  $first = false;
1253  } else {
1254  $s .= $sep;
1255  }
1256  $s .= $this->expand( $node, $flags );
1257  }
1258  }
1259  return $s;
1260  }
1261 
1269  public function implode( $sep /*, ... */ ) {
1270  $args = array_slice( func_get_args(), 1 );
1271 
1272  $first = true;
1273  $s = '';
1274  foreach ( $args as $root ) {
1275  if ( $root instanceof PPNode_Hash_Array ) {
1276  $root = $root->value;
1277  }
1278  if ( !is_array( $root ) ) {
1279  $root = [ $root ];
1280  }
1281  foreach ( $root as $node ) {
1282  if ( $first ) {
1283  $first = false;
1284  } else {
1285  $s .= $sep;
1286  }
1287  $s .= $this->expand( $node );
1288  }
1289  }
1290  return $s;
1291  }
1292 
1301  public function virtualImplode( $sep /*, ... */ ) {
1302  $args = array_slice( func_get_args(), 1 );
1303  $out = [];
1304  $first = true;
1305 
1306  foreach ( $args as $root ) {
1307  if ( $root instanceof PPNode_Hash_Array ) {
1308  $root = $root->value;
1309  }
1310  if ( !is_array( $root ) ) {
1311  $root = [ $root ];
1312  }
1313  foreach ( $root as $node ) {
1314  if ( $first ) {
1315  $first = false;
1316  } else {
1317  $out[] = $sep;
1318  }
1319  $out[] = $node;
1320  }
1321  }
1322  return new PPNode_Hash_Array( $out );
1323  }
1324 
1334  public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
1335  $args = array_slice( func_get_args(), 3 );
1336  $out = [ $start ];
1337  $first = true;
1338 
1339  foreach ( $args as $root ) {
1340  if ( $root instanceof PPNode_Hash_Array ) {
1341  $root = $root->value;
1342  }
1343  if ( !is_array( $root ) ) {
1344  $root = [ $root ];
1345  }
1346  foreach ( $root as $node ) {
1347  if ( $first ) {
1348  $first = false;
1349  } else {
1350  $out[] = $sep;
1351  }
1352  $out[] = $node;
1353  }
1354  }
1355  $out[] = $end;
1356  return new PPNode_Hash_Array( $out );
1357  }
1358 
1359  public function __toString() {
1360  return 'frame{}';
1361  }
1362 
1367  public function getPDBK( $level = false ) {
1368  if ( $level === false ) {
1369  return $this->title->getPrefixedDBkey();
1370  } else {
1371  return $this->titleCache[$level] ?? false;
1372  }
1373  }
1374 
1378  public function getArguments() {
1379  return [];
1380  }
1381 
1385  public function getNumberedArguments() {
1386  return [];
1387  }
1388 
1392  public function getNamedArguments() {
1393  return [];
1394  }
1395 
1401  public function isEmpty() {
1402  return true;
1403  }
1404 
1409  public function getArgument( $name ) {
1410  return false;
1411  }
1412 
1420  public function loopCheck( $title ) {
1421  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
1422  }
1423 
1429  public function isTemplate() {
1430  return false;
1431  }
1432 
1438  public function getTitle() {
1439  return $this->title;
1440  }
1441 
1447  public function setVolatile( $flag = true ) {
1448  $this->volatile = $flag;
1449  }
1450 
1456  public function isVolatile() {
1457  return $this->volatile;
1458  }
1459 
1465  public function setTTL( $ttl ) {
1466  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
1467  $this->ttl = $ttl;
1468  }
1469  }
1470 
1476  public function getTTL() {
1477  return $this->ttl;
1478  }
1479 }
1480 
1485 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1486 class PPTemplateFrame_Hash extends PPFrame_Hash {
1487 
1488  public $numberedArgs, $namedArgs, $parent;
1489  public $numberedExpansionCache, $namedExpansionCache;
1490 
1498  public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
1499  $namedArgs = [], $title = false
1500  ) {
1501  parent::__construct( $preprocessor );
1502 
1503  $this->parent = $parent;
1504  $this->numberedArgs = $numberedArgs;
1505  $this->namedArgs = $namedArgs;
1506  $this->title = $title;
1507  $pdbk = $title ? $title->getPrefixedDBkey() : false;
1508  $this->titleCache = $parent->titleCache;
1509  $this->titleCache[] = $pdbk;
1510  $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
1511  if ( $pdbk !== false ) {
1512  $this->loopCheckHash[$pdbk] = true;
1513  }
1514  $this->depth = $parent->depth + 1;
1515  $this->numberedExpansionCache = $this->namedExpansionCache = [];
1516  }
1517 
1518  public function __toString() {
1519  $s = 'tplframe{';
1520  $first = true;
1521  $args = $this->numberedArgs + $this->namedArgs;
1522  foreach ( $args as $name => $value ) {
1523  if ( $first ) {
1524  $first = false;
1525  } else {
1526  $s .= ', ';
1527  }
1528  $s .= "\"$name\":\"" .
1529  str_replace( '"', '\\"', $value->__toString() ) . '"';
1530  }
1531  $s .= '}';
1532  return $s;
1533  }
1534 
1542  public function cachedExpand( $key, $root, $flags = 0 ) {
1543  if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1544  return $this->parent->childExpansionCache[$key];
1545  }
1546  $retval = $this->expand( $root, $flags );
1547  if ( !$this->isVolatile() ) {
1548  $this->parent->childExpansionCache[$key] = $retval;
1549  }
1550  return $retval;
1551  }
1552 
1558  public function isEmpty() {
1559  return !count( $this->numberedArgs ) && !count( $this->namedArgs );
1560  }
1561 
1565  public function getArguments() {
1566  $arguments = [];
1567  foreach ( array_merge(
1568  array_keys( $this->numberedArgs ),
1569  array_keys( $this->namedArgs ) ) as $key ) {
1570  $arguments[$key] = $this->getArgument( $key );
1571  }
1572  return $arguments;
1573  }
1574 
1578  public function getNumberedArguments() {
1579  $arguments = [];
1580  foreach ( array_keys( $this->numberedArgs ) as $key ) {
1581  $arguments[$key] = $this->getArgument( $key );
1582  }
1583  return $arguments;
1584  }
1585 
1589  public function getNamedArguments() {
1590  $arguments = [];
1591  foreach ( array_keys( $this->namedArgs ) as $key ) {
1592  $arguments[$key] = $this->getArgument( $key );
1593  }
1594  return $arguments;
1595  }
1596 
1601  public function getNumberedArgument( $index ) {
1602  if ( !isset( $this->numberedArgs[$index] ) ) {
1603  return false;
1604  }
1605  if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1606  # No trimming for unnamed arguments
1607  $this->numberedExpansionCache[$index] = $this->parent->expand(
1608  $this->numberedArgs[$index],
1609  PPFrame::STRIP_COMMENTS
1610  );
1611  }
1612  return $this->numberedExpansionCache[$index];
1613  }
1614 
1619  public function getNamedArgument( $name ) {
1620  if ( !isset( $this->namedArgs[$name] ) ) {
1621  return false;
1622  }
1623  if ( !isset( $this->namedExpansionCache[$name] ) ) {
1624  # Trim named arguments post-expand, for backwards compatibility
1625  $this->namedExpansionCache[$name] = trim(
1626  $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
1627  }
1628  return $this->namedExpansionCache[$name];
1629  }
1630 
1635  public function getArgument( $name ) {
1636  $text = $this->getNumberedArgument( $name );
1637  if ( $text === false ) {
1638  $text = $this->getNamedArgument( $name );
1639  }
1640  return $text;
1641  }
1642 
1648  public function isTemplate() {
1649  return true;
1650  }
1651 
1652  public function setVolatile( $flag = true ) {
1653  parent::setVolatile( $flag );
1654  $this->parent->setVolatile( $flag );
1655  }
1656 
1657  public function setTTL( $ttl ) {
1658  parent::setTTL( $ttl );
1659  $this->parent->setTTL( $ttl );
1660  }
1661 }
1662 
1667 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1668 class PPCustomFrame_Hash extends PPFrame_Hash {
1669 
1670  public $args;
1671 
1672  public function __construct( $preprocessor, $args ) {
1673  parent::__construct( $preprocessor );
1674  $this->args = $args;
1675  }
1676 
1677  public function __toString() {
1678  $s = 'cstmframe{';
1679  $first = true;
1680  foreach ( $this->args as $name => $value ) {
1681  if ( $first ) {
1682  $first = false;
1683  } else {
1684  $s .= ', ';
1685  }
1686  $s .= "\"$name\":\"" .
1687  str_replace( '"', '\\"', $value->__toString() ) . '"';
1688  }
1689  $s .= '}';
1690  return $s;
1691  }
1692 
1696  public function isEmpty() {
1697  return !count( $this->args );
1698  }
1699 
1704  public function getArgument( $index ) {
1705  return $this->args[$index] ?? false;
1706  }
1707 
1708  public function getArguments() {
1709  return $this->args;
1710  }
1711 }
1712 
1716 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
1717 class PPNode_Hash_Tree implements PPNode {
1718 
1719  public $name;
1720 
1726  private $rawChildren;
1727 
1731  private $store;
1732 
1736  private $index;
1737 
1742  const NAME = 0;
1743 
1748  const CHILDREN = 1;
1749 
1757  public function __construct( array $store, $index ) {
1758  $this->store = $store;
1759  $this->index = $index;
1760  list( $this->name, $this->rawChildren ) = $this->store[$index];
1761  }
1762 
1771  public static function factory( array $store, $index ) {
1772  if ( !isset( $store[$index] ) ) {
1773  return false;
1774  }
1775 
1776  $descriptor = $store[$index];
1777  if ( is_string( $descriptor ) ) {
1778  $class = PPNode_Hash_Text::class;
1779  } elseif ( is_array( $descriptor ) ) {
1780  if ( $descriptor[self::NAME][0] === '@' ) {
1781  $class = PPNode_Hash_Attr::class;
1782  } else {
1783  $class = self::class;
1784  }
1785  } else {
1786  throw new MWException( __METHOD__ . ': invalid node descriptor' );
1787  }
1788  return new $class( $store, $index );
1789  }
1790 
1794  public function __toString() {
1795  $inner = '';
1796  $attribs = '';
1797  for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
1798  if ( $node instanceof PPNode_Hash_Attr ) {
1799  $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
1800  } else {
1801  $inner .= $node->__toString();
1802  }
1803  }
1804  if ( $inner === '' ) {
1805  return "<{$this->name}$attribs/>";
1806  } else {
1807  return "<{$this->name}$attribs>$inner</{$this->name}>";
1808  }
1809  }
1810 
1814  public function getChildren() {
1815  $children = [];
1816  foreach ( $this->rawChildren as $i => $child ) {
1817  $children[] = self::factory( $this->rawChildren, $i );
1818  }
1819  return new PPNode_Hash_Array( $children );
1820  }
1821 
1829  public function getFirstChild() {
1830  if ( !isset( $this->rawChildren[0] ) ) {
1831  return false;
1832  } else {
1833  return self::factory( $this->rawChildren, 0 );
1834  }
1835  }
1836 
1844  public function getNextSibling() {
1845  return self::factory( $this->store, $this->index + 1 );
1846  }
1847 
1854  public function getChildrenOfType( $name ) {
1855  $children = [];
1856  foreach ( $this->rawChildren as $i => $child ) {
1857  if ( is_array( $child ) && $child[self::NAME] === $name ) {
1858  $children[] = self::factory( $this->rawChildren, $i );
1859  }
1860  }
1861  return new PPNode_Hash_Array( $children );
1862  }
1863 
1868  public function getRawChildren() {
1869  return $this->rawChildren;
1870  }
1871 
1875  public function getLength() {
1876  return false;
1877  }
1878 
1883  public function item( $i ) {
1884  return false;
1885  }
1886 
1890  public function getName() {
1891  return $this->name;
1892  }
1893 
1903  public function splitArg() {
1904  return self::splitRawArg( $this->rawChildren );
1905  }
1906 
1912  public static function splitRawArg( array $children ) {
1913  $bits = [];
1914  foreach ( $children as $i => $child ) {
1915  if ( !is_array( $child ) ) {
1916  continue;
1917  }
1918  if ( $child[self::NAME] === 'name' ) {
1919  $bits['name'] = new self( $children, $i );
1920  if ( isset( $child[self::CHILDREN][0][self::NAME] )
1921  && $child[self::CHILDREN][0][self::NAME] === '@index'
1922  ) {
1923  $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
1924  }
1925  } elseif ( $child[self::NAME] === 'value' ) {
1926  $bits['value'] = new self( $children, $i );
1927  }
1928  }
1929 
1930  if ( !isset( $bits['name'] ) ) {
1931  throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
1932  }
1933  if ( !isset( $bits['index'] ) ) {
1934  $bits['index'] = '';
1935  }
1936  return $bits;
1937  }
1938 
1946  public function splitExt() {
1947  return self::splitRawExt( $this->rawChildren );
1948  }
1949 
1955  public static function splitRawExt( array $children ) {
1956  $bits = [];
1957  foreach ( $children as $i => $child ) {
1958  if ( !is_array( $child ) ) {
1959  continue;
1960  }
1961  switch ( $child[self::NAME] ) {
1962  case 'name':
1963  $bits['name'] = new self( $children, $i );
1964  break;
1965  case 'attr':
1966  $bits['attr'] = new self( $children, $i );
1967  break;
1968  case 'inner':
1969  $bits['inner'] = new self( $children, $i );
1970  break;
1971  case 'close':
1972  $bits['close'] = new self( $children, $i );
1973  break;
1974  }
1975  }
1976  if ( !isset( $bits['name'] ) ) {
1977  throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
1978  }
1979  return $bits;
1980  }
1981 
1988  public function splitHeading() {
1989  if ( $this->name !== 'h' ) {
1990  throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
1991  }
1992  return self::splitRawHeading( $this->rawChildren );
1993  }
1994 
2000  public static function splitRawHeading( array $children ) {
2001  $bits = [];
2002  foreach ( $children as $i => $child ) {
2003  if ( !is_array( $child ) ) {
2004  continue;
2005  }
2006  if ( $child[self::NAME] === '@i' ) {
2007  $bits['i'] = $child[self::CHILDREN][0];
2008  } elseif ( $child[self::NAME] === '@level' ) {
2009  $bits['level'] = $child[self::CHILDREN][0];
2010  }
2011  }
2012  if ( !isset( $bits['i'] ) ) {
2013  throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
2014  }
2015  return $bits;
2016  }
2017 
2024  public function splitTemplate() {
2025  return self::splitRawTemplate( $this->rawChildren );
2026  }
2027 
2033  public static function splitRawTemplate( array $children ) {
2034  $parts = [];
2035  $bits = [ 'lineStart' => '' ];
2036  foreach ( $children as $i => $child ) {
2037  if ( !is_array( $child ) ) {
2038  continue;
2039  }
2040  switch ( $child[self::NAME] ) {
2041  case 'title':
2042  $bits['title'] = new self( $children, $i );
2043  break;
2044  case 'part':
2045  $parts[] = new self( $children, $i );
2046  break;
2047  case '@lineStart':
2048  $bits['lineStart'] = '1';
2049  break;
2050  }
2051  }
2052  if ( !isset( $bits['title'] ) ) {
2053  throw new MWException( 'Invalid node passed to ' . __METHOD__ );
2054  }
2055  $bits['parts'] = new PPNode_Hash_Array( $parts );
2056  return $bits;
2057  }
2058 }
2059 
2063 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
2064 class PPNode_Hash_Text implements PPNode {
2065 
2066  public $value;
2067  private $store, $index;
2068 
2076  public function __construct( array $store, $index ) {
2077  $this->value = $store[$index];
2078  if ( !is_scalar( $this->value ) ) {
2079  throw new MWException( __CLASS__ . ' given object instead of string' );
2080  }
2081  $this->store = $store;
2082  $this->index = $index;
2083  }
2084 
2085  public function __toString() {
2086  return htmlspecialchars( $this->value );
2087  }
2088 
2089  public function getNextSibling() {
2090  return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2091  }
2092 
2093  public function getChildren() {
2094  return false;
2095  }
2096 
2097  public function getFirstChild() {
2098  return false;
2099  }
2100 
2101  public function getChildrenOfType( $name ) {
2102  return false;
2103  }
2104 
2105  public function getLength() {
2106  return false;
2107  }
2108 
2109  public function item( $i ) {
2110  return false;
2111  }
2112 
2113  public function getName() {
2114  return '#text';
2115  }
2116 
2117  public function splitArg() {
2118  throw new MWException( __METHOD__ . ': not supported' );
2119  }
2120 
2121  public function splitExt() {
2122  throw new MWException( __METHOD__ . ': not supported' );
2123  }
2124 
2125  public function splitHeading() {
2126  throw new MWException( __METHOD__ . ': not supported' );
2127  }
2128 }
2129 
2133 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
2134 class PPNode_Hash_Array implements PPNode {
2135 
2136  public $value;
2137 
2138  public function __construct( $value ) {
2139  $this->value = $value;
2140  }
2141 
2142  public function __toString() {
2143  return var_export( $this, true );
2144  }
2145 
2146  public function getLength() {
2147  return count( $this->value );
2148  }
2149 
2150  public function item( $i ) {
2151  return $this->value[$i];
2152  }
2153 
2154  public function getName() {
2155  return '#nodelist';
2156  }
2157 
2158  public function getNextSibling() {
2159  return false;
2160  }
2161 
2162  public function getChildren() {
2163  return false;
2164  }
2165 
2166  public function getFirstChild() {
2167  return false;
2168  }
2169 
2170  public function getChildrenOfType( $name ) {
2171  return false;
2172  }
2173 
2174  public function splitArg() {
2175  throw new MWException( __METHOD__ . ': not supported' );
2176  }
2177 
2178  public function splitExt() {
2179  throw new MWException( __METHOD__ . ': not supported' );
2180  }
2181 
2182  public function splitHeading() {
2183  throw new MWException( __METHOD__ . ': not supported' );
2184  }
2185 }
2186 
2190 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
2191 class PPNode_Hash_Attr implements PPNode {
2192 
2193  public $name, $value;
2194  private $store, $index;
2195 
2203  public function __construct( array $store, $index ) {
2204  $descriptor = $store[$index];
2205  if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
2206  throw new MWException( __METHOD__ . ': invalid name in attribute descriptor' );
2207  }
2208  $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
2209  $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
2210  $this->store = $store;
2211  $this->index = $index;
2212  }
2213 
2214  public function __toString() {
2215  return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
2216  }
2217 
2218  public function getName() {
2219  return $this->name;
2220  }
2221 
2222  public function getNextSibling() {
2223  return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
2224  }
2225 
2226  public function getChildren() {
2227  return false;
2228  }
2229 
2230  public function getFirstChild() {
2231  return false;
2232  }
2233 
2234  public function getChildrenOfType( $name ) {
2235  return false;
2236  }
2237 
2238  public function getLength() {
2239  return false;
2240  }
2241 
2242  public function item( $i ) {
2243  return false;
2244  }
2245 
2246  public function splitArg() {
2247  throw new MWException( __METHOD__ . ': not supported' );
2248  }
2249 
2250  public function splitExt() {
2251  throw new MWException( __METHOD__ . ': not supported' );
2252  }
2253 
2254  public function splitHeading() {
2255  throw new MWException( __METHOD__ . ': not supported' );
2256  }
2257 }
part
in this case you re responsible for computing and outputting the entire conflict part
Definition: hooks.txt:1423
PPNode_Hash_Tree\getLength
getLength()
Definition: Preprocessor_Hash.php:1875
PPNode_Hash_Tree\getChildrenOfType
getChildrenOfType( $name)
Get an array of the children with a given node name.
Definition: Preprocessor_Hash.php:1854
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
PPNode_Hash_Tree\item
item( $i)
Definition: Preprocessor_Hash.php:1883
PPDPart_Hash
Definition: Preprocessor_Hash.php:870
PPNode_Hash_Tree\$store
$store
The store array for the siblings of this node, including this node itself.
Definition: Preprocessor_Hash.php:1731
PPNode_Hash_Array\splitHeading
splitHeading()
Split an "<h>" node.
Definition: Preprocessor_Hash.php:2182
PPNode_Hash_Tree\getFirstChild
getFirstChild()
Get the first child, or false if there is none.
Definition: Preprocessor_Hash.php:1829
captcha-old.count
count
Definition: captcha-old.py:249
PPNode_Hash_Tree\splitRawExt
static splitRawExt(array $children)
Like splitExt() but for a raw child array.
Definition: Preprocessor_Hash.php:1955
Preprocessor_Hash\__construct
__construct( $parser)
Definition: Preprocessor_Hash.php:53
PPFrame_Hash
An expansion frame, used as a context to expand the result of preprocessToObj()
Definition: Preprocessor_Hash.php:887
wiki
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning maintenance scripts have been cleaned up to use a unified class Directory structure How to run a script How to write your own DIRECTORY STRUCTURE The maintenance directory of a MediaWiki installation contains several all of which have unique purposes HOW TO RUN A SCRIPT Ridiculously just call php someScript php that s in the top level maintenance directory if not default wiki
Definition: maintenance.txt:1
PPNode_Hash_Text\splitHeading
splitHeading()
Split an "<h>" node.
Definition: Preprocessor_Hash.php:2125
PPNode_Hash_Tree\splitHeading
splitHeading()
Split an "<h>" node.
Definition: Preprocessor_Hash.php:1988
PPNode_Hash_Attr\splitExt
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
Definition: Preprocessor_Hash.php:2250
PPCustomFrame_Hash\isEmpty
isEmpty()
Definition: Preprocessor_Hash.php:1696
PPNode_Hash_Text\getChildrenOfType
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
Definition: Preprocessor_Hash.php:2101
PPNode_Hash_Tree\splitExt
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
Definition: Preprocessor_Hash.php:1946
PPNode_Hash_Text\getLength
getLength()
Returns the length of the array, or false if this is not an array-type node.
Definition: Preprocessor_Hash.php:2105
PPNode_Hash_Tree\splitRawArg
static splitRawArg(array $children)
Like splitArg() but for a raw child array.
Definition: Preprocessor_Hash.php:1912
Preprocessor_Hash\newFrame
newFrame()
Definition: Preprocessor_Hash.php:60
$s
$s
Definition: mergeMessageFileList.php:186
a
</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre >< span ></span >< span class="kd"> var</span >< span class="nx"> a</span >< span class="p"></span ></pre ></div > ! end ! test Multiline< source/> in lists !input *< source > a b</source > *foo< source > a b</source > ! html< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! html tidy< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! end ! test Custom attributes !input< source lang="javascript" id="foo" class="bar" dir="rtl" style="font-size: larger;"> var a
Definition: parserTests.txt:85
value
if( $inline) $status value
Definition: SyntaxHighlight.php:345
PPNode_Hash_Tree\splitRawHeading
static splitRawHeading(array $children)
Like splitHeading() but for a raw child array.
Definition: Preprocessor_Hash.php:2000
so
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is and will automatically terminate your rights under this License parties who have received or from you under this License will not have their licenses terminated so long as such parties remain in full compliance You are not required to accept this since you have not signed it nothing else grants you permission to modify or distribute the Program or its derivative works These actions are prohibited by law if you do not accept this License by modifying or distributing the you indicate your acceptance of this License to do so
Definition: COPYING.txt:185
Preprocessor_Hash
Differences from DOM schema:
Definition: Preprocessor_Hash.php:43
PPNode_Hash_Text\__construct
__construct(array $store, $index)
Construct an object using the data from $store[$index].
Definition: Preprocessor_Hash.php:2076
PPNode_Hash_Array\splitExt
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
Definition: Preprocessor_Hash.php:2178
PPNode_Hash_Attr\$store
$store
Definition: Preprocessor_Hash.php:2194
php
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:35
Preprocessor
Definition: Preprocessor.php:29
PPNode_Hash_Array\splitArg
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
Definition: Preprocessor_Hash.php:2174
Preprocessor_Hash\newCustomFrame
newCustomFrame( $args)
Definition: Preprocessor_Hash.php:68
PPNode_Hash_Array\getFirstChild
getFirstChild()
Get the first child of a tree node.
Definition: Preprocessor_Hash.php:2166
PPNode_Hash_Attr\item
item( $i)
Returns an item of an array-type node.
Definition: Preprocessor_Hash.php:2242
PPNode_Hash_Attr\getFirstChild
getFirstChild()
Get the first child of a tree node.
Definition: Preprocessor_Hash.php:2230
name
and how to run hooks for an and one after Each event has a name
Definition: hooks.txt:6
PPNode_Hash_Attr\splitHeading
splitHeading()
Split an "<h>" node.
Definition: Preprocessor_Hash.php:2254
PPNode_Hash_Tree\$name
$name
Definition: Preprocessor_Hash.php:1719
PPNode_Hash_Attr\getChildrenOfType
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
Definition: Preprocessor_Hash.php:2234
MWException
MediaWiki exception.
Definition: MWException.php:26
PPNode_Hash_Tree\CHILDREN
const CHILDREN
The offset of the child list within descriptors, used in some places for readability.
Definition: Preprocessor_Hash.php:1748
PPNode_Hash_Array\getChildrenOfType
getChildrenOfType( $name)
Get all children of this tree node which have a given name.
Definition: Preprocessor_Hash.php:2170
PPNode_Hash_Tree\getNextSibling
getNextSibling()
Get the next sibling, or false if there is none.
Definition: Preprocessor_Hash.php:1844
PPNode_Hash_Text\getNextSibling
getNextSibling()
Get the next sibling of any node.
Definition: Preprocessor_Hash.php:2089
PPNode_Hash_Text\__toString
__toString()
Definition: Preprocessor_Hash.php:2085
PPNode_Hash_Array\getLength
getLength()
Returns the length of the array, or false if this is not an array-type node.
Definition: Preprocessor_Hash.php:2146
Preprocessor_Hash\newPartNodeArray
newPartNodeArray( $values)
Definition: Preprocessor_Hash.php:76
PPNode_Hash_Tree\splitTemplate
splitTemplate()
Split a "<template>" or "<tplarg>" node.
Definition: Preprocessor_Hash.php:2024
PPNode_Hash_Tree\__toString
__toString()
Convert a node to XML, for debugging.
Definition: Preprocessor_Hash.php:1794
$matches
$matches
Definition: NoLocalSettings.php:24
PPNode
There are three types of nodes:
Definition: Preprocessor.php:360
$attribs
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:1985
Preprocessor_Hash\CACHE_VERSION
const CACHE_VERSION
Definition: Preprocessor_Hash.php:51
Preprocessor_Hash\$parser
Parser $parser
Definition: Preprocessor_Hash.php:48
PPNode_Hash_Attr\$value
$value
Definition: Preprocessor_Hash.php:2193
PPNode_Hash_Tree\splitRawTemplate
static splitRawTemplate(array $children)
Like splitTemplate() but for a raw child array.
Definition: Preprocessor_Hash.php:2033
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
PPNode_Hash_Attr\getLength
getLength()
Returns the length of the array, or false if this is not an array-type node.
Definition: Preprocessor_Hash.php:2238
PPNode_Hash_Tree\__construct
__construct(array $store, $index)
Construct an object using the data from $store[$index].
Definition: Preprocessor_Hash.php:1757
$wgDisableLangConversion
$wgDisableLangConversion
Whether to enable language variant conversion.
Definition: DefaultSettings.php:3085
Preprocessor_Hash\CACHE_PREFIX
const CACHE_PREFIX
Definition: Preprocessor_Hash.php:50
list
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
PPNode_Hash_Text\splitExt
splitExt()
Split an "<ext>" node into an associative array containing name, attr, inner and close All values in ...
Definition: Preprocessor_Hash.php:2121
or
or
Definition: COPYING.txt:140
PPNode_Hash_Text\$value
$value
Definition: Preprocessor_Hash.php:2066
PPNode_Hash_Array
Definition: Preprocessor_Hash.php:2134
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
root
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as root
Definition: distributors.txt:39
PPNode_Hash_Text\getFirstChild
getFirstChild()
Get the first child of a tree node.
Definition: Preprocessor_Hash.php:2097
PPNode_Hash_Tree\getRawChildren
getRawChildren()
Get the raw child array.
Definition: Preprocessor_Hash.php:1868
PPNode_Hash_Text\$store
$store
Definition: Preprocessor_Hash.php:2067
PPNode_Hash_Tree\getName
getName()
Definition: Preprocessor_Hash.php:1890
$value
$value
Definition: styleTest.css.php:49
Preprocessor_Hash\preprocessToObj
preprocessToObj( $text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Preprocessor_Hash.php:118
title
title
Definition: parserTests.txt:245
PPNode_Hash_Tree\getChildren
getChildren()
Definition: Preprocessor_Hash.php:1814
PPNode_Hash_Array\__construct
__construct( $value)
Definition: Preprocessor_Hash.php:2138
PPNode_Hash_Text\getName
getName()
Get the name of this node.
Definition: Preprocessor_Hash.php:2113
PPNode_Hash_Text\getChildren
getChildren()
Get an array-type node containing the children of this node.
Definition: Preprocessor_Hash.php:2093
PPNode_Hash_Tree\$index
$index
The index into $this->store which contains the descriptor of this node.
Definition: Preprocessor_Hash.php:1736
PPNode_Hash_Array\item
item( $i)
Returns an item of an array-type node.
Definition: Preprocessor_Hash.php:2150
PPNode_Hash_Array\getChildren
getChildren()
Get an array-type node containing the children of this node.
Definition: Preprocessor_Hash.php:2162
PPNode_Hash_Attr\getChildren
getChildren()
Get an array-type node containing the children of this node.
Definition: Preprocessor_Hash.php:2226
PPNode_Hash_Array\getName
getName()
Get the name of this node.
Definition: Preprocessor_Hash.php:2154
PPNode_Hash_Attr\getNextSibling
getNextSibling()
Get the next sibling of any node.
Definition: Preprocessor_Hash.php:2222
PPNode_Hash_Attr
Definition: Preprocessor_Hash.php:2191
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
$args
if( $line===false) $args
Definition: cdb.php:64
PPNode_Hash_Text
Definition: Preprocessor_Hash.php:2064
PPCustomFrame_Hash\getArgument
getArgument( $index)
Definition: Preprocessor_Hash.php:1704
PPNode_Hash_Tree\$rawChildren
$rawChildren
The store array for children of this node.
Definition: Preprocessor_Hash.php:1726
PPNode_Hash_Text\item
item( $i)
Returns an item of an array-type node.
Definition: Preprocessor_Hash.php:2109
PPNode_Hash_Attr\getName
getName()
Get the name of this node.
Definition: Preprocessor_Hash.php:2218
PPDStack_Hash
Stack class to help Preprocessor::preprocessToObj()
Definition: Preprocessor_Hash.php:804
PPNode_Hash_Array\$value
$value
Definition: Preprocessor_Hash.php:2136
Preprocessor_Hash\addLiteral
static addLiteral(array &$accum, $text)
Definition: Preprocessor_Hash.php:789
PPNode_Hash_Attr\splitArg
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
Definition: Preprocessor_Hash.php:2246
PPNode_Hash_Array\getNextSibling
getNextSibling()
Get the next sibling of any node.
Definition: Preprocessor_Hash.php:2158
PPNode_Hash_Array\__toString
__toString()
Definition: Preprocessor_Hash.php:2142
as
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
Definition: distributors.txt:9
PPCustomFrame_Hash\getArguments
getArguments()
Definition: Preprocessor_Hash.php:1708
PPCustomFrame_Hash
Expansion frame with custom arguments.
Definition: Preprocessor_Hash.php:1668
captcha-old.parser
parser
Definition: captcha-old.py:210
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
PPNode_Hash_Attr\__construct
__construct(array $store, $index)
Construct an object using the data from $store[$index].
Definition: Preprocessor_Hash.php:2203
Preprocessor\cacheGetTree
cacheGetTree( $text, $flags)
Attempt to load a precomputed document tree for some given wikitext from the cache.
Definition: Preprocessor.php:96
PPNode_Hash_Text\splitArg
splitArg()
Split a "<part>" node into an associative array containing: name PPNode name index String index value...
Definition: Preprocessor_Hash.php:2117
PPNode_Hash_Tree
Definition: Preprocessor_Hash.php:1717
type
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
PPNode_Hash_Attr\__toString
__toString()
Definition: Preprocessor_Hash.php:2214
captcha-old.args
args
Definition: captcha-old.py:227
PPNode_Hash_Tree\factory
static factory(array $store, $index)
Construct an appropriate PPNode_Hash_* object with a class that depends on what is at the relevant st...
Definition: Preprocessor_Hash.php:1771
PPNode_Hash_Tree\NAME
const NAME
The offset of the name within descriptors, used in some places for readability.
Definition: Preprocessor_Hash.php:1742
PPNode_Hash_Tree\splitArg
splitArg()
Split a "<part>" node into an associative array containing:
Definition: Preprocessor_Hash.php:1903
names
alter the names
Definition: COPYING.txt:329