MediaWiki  master
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 }
cacheGetTree( $text, $flags)
Attempt to load a precomputed document tree for some given wikitext from the cache.
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
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
in this case you re responsible for computing and outputting the entire conflict part
Definition: hooks.txt:1420
preprocessToObj( $text, $flags=0)
Preprocess some wikitext and return the document tree.
title
Differences from DOM schema:
Stack class to help Preprocessor::preprocessToObj()
if( $line===false) $args
Definition: cdb.php:64
and how to run hooks for an and one after Each event has a name
Definition: hooks.txt:6
An expansion frame, used as a context to expand the result of preprocessToObj()
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
const PTD_FOR_INCLUSION
Definition: Parser.php:108
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
$wgDisableLangConversion
Whether to enable language variant conversion.
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
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
Expansion frame with custom arguments.
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
$matches