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 
56  public function __construct( $parser ) {
57  $this->parser = $parser;
58  }
59 
63  public function newFrame() {
64  return new PPFrame_Hash( $this );
65  }
66 
71  public function newCustomFrame( $args ) {
72  return new PPCustomFrame_Hash( $this, $args );
73  }
74 
79  public function newPartNodeArray( $values ) {
80  $list = [];
81 
82  foreach ( $values as $k => $val ) {
83  if ( is_int( $k ) ) {
84  $store = [ [ 'part', [
85  [ 'name', [ [ '@index', [ $k ] ] ] ],
86  [ 'value', [ strval( $val ) ] ],
87  ] ] ];
88  } else {
89  $store = [ [ 'part', [
90  [ 'name', [ strval( $k ) ] ],
91  '=',
92  [ 'value', [ strval( $val ) ] ],
93  ] ] ];
94  }
95 
96  $list[] = new PPNode_Hash_Tree( $store, 0 );
97  }
98 
99  $node = new PPNode_Hash_Array( $list );
100  return $node;
101  }
102 
121  public function preprocessToObj( $text, $flags = 0 ) {
123 
124  $tree = $this->cacheGetTree( $text, $flags );
125  if ( $tree !== false ) {
126  $store = json_decode( $tree );
127  if ( is_array( $store ) ) {
128  return new PPNode_Hash_Tree( $store, 0 );
129  }
130  }
131 
132  $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
133 
134  $xmlishElements = $this->parser->getStripList();
135  $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ];
136  $enableOnlyinclude = false;
137  if ( $forInclusion ) {
138  $ignoredTags = [ 'includeonly', '/includeonly' ];
139  $ignoredElements = [ 'noinclude' ];
140  $xmlishElements[] = 'noinclude';
141  if ( strpos( $text, '<onlyinclude>' ) !== false
142  && strpos( $text, '</onlyinclude>' ) !== false
143  ) {
144  $enableOnlyinclude = true;
145  }
146  } else {
147  $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ];
148  $ignoredElements = [ 'includeonly' ];
149  $xmlishElements[] = 'includeonly';
150  }
151  $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
152 
153  // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
154  $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
155 
156  $stack = new PPDStack_Hash;
157 
158  $searchBase = "[{<\n";
159  if ( !$wgDisableLangConversion ) {
160  $searchBase .= '-';
161  }
162 
163  // For fast reverse searches
164  $revText = strrev( $text );
165  $lengthText = strlen( $text );
166 
167  // Input pointer, starts out pointing to a pseudo-newline before the start
168  $i = 0;
169  // Current accumulator. See the doc comment for Preprocessor_Hash for the format.
170  $accum =& $stack->getAccum();
171  // True to find equals signs in arguments
172  $findEquals = false;
173  // True to take notice of pipe characters
174  $findPipe = false;
175  $headingIndex = 1;
176  // True if $i is inside a possible heading
177  $inHeading = false;
178  // True if there are no more greater-than (>) signs right of $i
179  $noMoreGT = false;
180  // Map of tag name => true if there are no more closing tags of given type right of $i
181  $noMoreClosingTag = [];
182  // True to ignore all input up to the next <onlyinclude>
183  $findOnlyinclude = $enableOnlyinclude;
184  // Do a line-start run without outputting an LF character
185  $fakeLineStart = true;
186 
187  while ( true ) {
188  // $this->memCheck();
189 
190  if ( $findOnlyinclude ) {
191  // Ignore all input up to the next <onlyinclude>
192  $startPos = strpos( $text, '<onlyinclude>', $i );
193  if ( $startPos === false ) {
194  // Ignored section runs to the end
195  $accum[] = [ 'ignore', [ substr( $text, $i ) ] ];
196  break;
197  }
198  $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
199  $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
200  $i = $tagEndPos;
201  $findOnlyinclude = false;
202  }
203 
204  if ( $fakeLineStart ) {
205  $found = 'line-start';
206  $curChar = '';
207  } else {
208  # Find next opening brace, closing brace or pipe
209  $search = $searchBase;
210  if ( $stack->top === false ) {
211  $currentClosing = '';
212  } else {
213  $currentClosing = $stack->top->close;
214  $search .= $currentClosing;
215  }
216  if ( $findPipe ) {
217  $search .= '|';
218  }
219  if ( $findEquals ) {
220  // First equals will be for the template
221  $search .= '=';
222  }
223  $rule = null;
224  # Output literal section, advance input counter
225  $literalLength = strcspn( $text, $search, $i );
226  if ( $literalLength > 0 ) {
227  self::addLiteral( $accum, substr( $text, $i, $literalLength ) );
228  $i += $literalLength;
229  }
230  if ( $i >= $lengthText ) {
231  if ( $currentClosing == "\n" ) {
232  // Do a past-the-end run to finish off the heading
233  $curChar = '';
234  $found = 'line-end';
235  } else {
236  # All done
237  break;
238  }
239  } else {
240  $curChar = $curTwoChar = $text[$i];
241  if ( ( $i + 1 ) < $lengthText ) {
242  $curTwoChar .= $text[$i + 1];
243  }
244  if ( $curChar == '|' ) {
245  $found = 'pipe';
246  } elseif ( $curChar == '=' ) {
247  $found = 'equals';
248  } elseif ( $curChar == '<' ) {
249  $found = 'angle';
250  } elseif ( $curChar == "\n" ) {
251  if ( $inHeading ) {
252  $found = 'line-end';
253  } else {
254  $found = 'line-start';
255  }
256  } elseif ( $curTwoChar == $currentClosing ) {
257  $found = 'close';
258  $curChar = $curTwoChar;
259  } elseif ( $curChar == $currentClosing ) {
260  $found = 'close';
261  } elseif ( isset( $this->rules[$curTwoChar] ) ) {
262  $curChar = $curTwoChar;
263  $found = 'open';
264  $rule = $this->rules[$curChar];
265  } elseif ( isset( $this->rules[$curChar] ) ) {
266  $found = 'open';
267  $rule = $this->rules[$curChar];
268  } else {
269  # Some versions of PHP have a strcspn which stops on
270  # null characters; ignore these and continue.
271  # We also may get '-' and '}' characters here which
272  # don't match -{ or $currentClosing. Add these to
273  # output and continue.
274  if ( $curChar == '-' || $curChar == '}' ) {
275  self::addLiteral( $accum, $curChar );
276  }
277  ++$i;
278  continue;
279  }
280  }
281  }
282 
283  if ( $found == 'angle' ) {
284  $matches = false;
285  // Handle </onlyinclude>
286  if ( $enableOnlyinclude
287  && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>'
288  ) {
289  $findOnlyinclude = true;
290  continue;
291  }
292 
293  // Determine element name
294  if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
295  // Element name missing or not listed
296  self::addLiteral( $accum, '<' );
297  ++$i;
298  continue;
299  }
300  // Handle comments
301  if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
302  // To avoid leaving blank lines, when a sequence of
303  // space-separated comments is both preceded and followed by
304  // a newline (ignoring spaces), then
305  // trim leading and trailing spaces and the trailing newline.
306 
307  // Find the end
308  $endPos = strpos( $text, '-->', $i + 4 );
309  if ( $endPos === false ) {
310  // Unclosed comment in input, runs to end
311  $inner = substr( $text, $i );
312  $accum[] = [ 'comment', [ $inner ] ];
313  $i = $lengthText;
314  } else {
315  // Search backwards for leading whitespace
316  $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0;
317 
318  // Search forwards for trailing whitespace
319  // $wsEnd will be the position of the last space (or the '>' if there's none)
320  $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 );
321 
322  // Keep looking forward as long as we're finding more
323  // comments.
324  $comments = [ [ $wsStart, $wsEnd ] ];
325  while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) {
326  $c = strpos( $text, '-->', $wsEnd + 4 );
327  if ( $c === false ) {
328  break;
329  }
330  $c = $c + 2 + strspn( $text, " \t", $c + 3 );
331  $comments[] = [ $wsEnd + 1, $c ];
332  $wsEnd = $c;
333  }
334 
335  // Eat the line if possible
336  // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
337  // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
338  // it's a possible beneficial b/c break.
339  if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
340  && substr( $text, $wsEnd + 1, 1 ) == "\n"
341  ) {
342  // Remove leading whitespace from the end of the accumulator
343  $wsLength = $i - $wsStart;
344  $endIndex = count( $accum ) - 1;
345 
346  // Sanity check
347  if ( $wsLength > 0
348  && $endIndex >= 0
349  && is_string( $accum[$endIndex] )
350  && strspn( $accum[$endIndex], " \t", -$wsLength ) === $wsLength
351  ) {
352  $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
353  }
354 
355  // Dump all but the last comment to the accumulator
356  foreach ( $comments as $j => $com ) {
357  $startPos = $com[0];
358  $endPos = $com[1] + 1;
359  if ( $j == ( count( $comments ) - 1 ) ) {
360  break;
361  }
362  $inner = substr( $text, $startPos, $endPos - $startPos );
363  $accum[] = [ 'comment', [ $inner ] ];
364  }
365 
366  // Do a line-start run next time to look for headings after the comment
367  $fakeLineStart = true;
368  } else {
369  // No line to eat, just take the comment itself
370  $startPos = $i;
371  $endPos += 2;
372  }
373 
374  if ( $stack->top ) {
375  $part = $stack->top->getCurrentPart();
376  if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
377  $part->visualEnd = $wsStart;
378  }
379  // Else comments abutting, no change in visual end
380  $part->commentEnd = $endPos;
381  }
382  $i = $endPos + 1;
383  $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
384  $accum[] = [ 'comment', [ $inner ] ];
385  }
386  continue;
387  }
388  $name = $matches[1];
389  $lowerName = strtolower( $name );
390  $attrStart = $i + strlen( $name ) + 1;
391 
392  // Find end of tag
393  $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
394  if ( $tagEndPos === false ) {
395  // Infinite backtrack
396  // Disable tag search to prevent worst-case O(N^2) performance
397  $noMoreGT = true;
398  self::addLiteral( $accum, '<' );
399  ++$i;
400  continue;
401  }
402 
403  // Handle ignored tags
404  if ( in_array( $lowerName, $ignoredTags ) ) {
405  $accum[] = [ 'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
406  $i = $tagEndPos + 1;
407  continue;
408  }
409 
410  $tagStartPos = $i;
411  if ( $text[$tagEndPos - 1] == '/' ) {
412  // Short end tag
413  $attrEnd = $tagEndPos - 1;
414  $inner = null;
415  $i = $tagEndPos + 1;
416  $close = null;
417  } else {
418  $attrEnd = $tagEndPos;
419  // Find closing tag
420  if (
421  !isset( $noMoreClosingTag[$name] ) &&
422  preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
423  $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
424  ) {
425  $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
426  $i = $matches[0][1] + strlen( $matches[0][0] );
427  $close = $matches[0][0];
428  } else {
429  // No end tag
430  if ( in_array( $name, $xmlishAllowMissingEndTag ) ) {
431  // Let it run out to the end of the text.
432  $inner = substr( $text, $tagEndPos + 1 );
433  $i = $lengthText;
434  $close = null;
435  } else {
436  // Don't match the tag, treat opening tag as literal and resume parsing.
437  $i = $tagEndPos + 1;
438  self::addLiteral( $accum,
439  substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
440  // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>...
441  $noMoreClosingTag[$name] = true;
442  continue;
443  }
444  }
445  }
446  // <includeonly> and <noinclude> just become <ignore> tags
447  if ( in_array( $lowerName, $ignoredElements ) ) {
448  $accum[] = [ 'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
449  continue;
450  }
451 
452  if ( $attrEnd <= $attrStart ) {
453  $attr = '';
454  } else {
455  // Note that the attr element contains the whitespace between name and attribute,
456  // this is necessary for precise reconstruction during pre-save transform.
457  $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
458  }
459 
460  $children = [
461  [ 'name', [ $name ] ],
462  [ 'attr', [ $attr ] ] ];
463  if ( $inner !== null ) {
464  $children[] = [ 'inner', [ $inner ] ];
465  }
466  if ( $close !== null ) {
467  $children[] = [ 'close', [ $close ] ];
468  }
469  $accum[] = [ 'ext', $children ];
470  } elseif ( $found == 'line-start' ) {
471  // Is this the start of a heading?
472  // Line break belongs before the heading element in any case
473  if ( $fakeLineStart ) {
474  $fakeLineStart = false;
475  } else {
476  self::addLiteral( $accum, $curChar );
477  $i++;
478  }
479 
480  $count = strspn( $text, '=', $i, 6 );
481  if ( $count == 1 && $findEquals ) {
482  // DWIM: This looks kind of like a name/value separator.
483  // Let's let the equals handler have it and break the potential
484  // heading. This is heuristic, but AFAICT the methods for
485  // completely correct disambiguation are very complex.
486  } elseif ( $count > 0 ) {
487  $piece = [
488  'open' => "\n",
489  'close' => "\n",
490  'parts' => [ new PPDPart_Hash( str_repeat( '=', $count ) ) ],
491  'startPos' => $i,
492  'count' => $count ];
493  $stack->push( $piece );
494  $accum =& $stack->getAccum();
495  $stackFlags = $stack->getFlags();
496  if ( isset( $stackFlags['findEquals'] ) ) {
497  $findEquals = $stackFlags['findEquals'];
498  }
499  if ( isset( $stackFlags['findPipe'] ) ) {
500  $findPipe = $stackFlags['findPipe'];
501  }
502  if ( isset( $stackFlags['inHeading'] ) ) {
503  $inHeading = $stackFlags['inHeading'];
504  }
505  $i += $count;
506  }
507  } elseif ( $found == 'line-end' ) {
508  $piece = $stack->top;
509  // A heading must be open, otherwise \n wouldn't have been in the search list
510  // FIXME: Don't use assert()
511  // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.assert
512  assert( $piece->open === "\n" );
513  $part = $piece->getCurrentPart();
514  // Search back through the input to see if it has a proper close.
515  // Do this using the reversed string since the other solutions
516  // (end anchor, etc.) are inefficient.
517  $wsLength = strspn( $revText, " \t", $lengthText - $i );
518  $searchStart = $i - $wsLength;
519  if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
520  // Comment found at line end
521  // Search for equals signs before the comment
522  $searchStart = $part->visualEnd;
523  $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
524  }
525  $count = $piece->count;
526  $equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
527  if ( $equalsLength > 0 ) {
528  if ( $searchStart - $equalsLength == $piece->startPos ) {
529  // This is just a single string of equals signs on its own line
530  // Replicate the doHeadings behavior /={count}(.+)={count}/
531  // First find out how many equals signs there really are (don't stop at 6)
532  $count = $equalsLength;
533  if ( $count < 3 ) {
534  $count = 0;
535  } else {
536  $count = min( 6, intval( ( $count - 1 ) / 2 ) );
537  }
538  } else {
539  $count = min( $equalsLength, $count );
540  }
541  if ( $count > 0 ) {
542  // Normal match, output <h>
543  $element = [ [ 'possible-h',
544  array_merge(
545  [
546  [ '@level', [ $count ] ],
547  [ '@i', [ $headingIndex++ ] ]
548  ],
549  $accum
550  )
551  ] ];
552  } else {
553  // Single equals sign on its own line, count=0
554  $element = $accum;
555  }
556  } else {
557  // No match, no <h>, just pass down the inner text
558  $element = $accum;
559  }
560  // Unwind the stack
561  $stack->pop();
562  $accum =& $stack->getAccum();
563  $stackFlags = $stack->getFlags();
564  if ( isset( $stackFlags['findEquals'] ) ) {
565  $findEquals = $stackFlags['findEquals'];
566  }
567  if ( isset( $stackFlags['findPipe'] ) ) {
568  $findPipe = $stackFlags['findPipe'];
569  }
570  if ( isset( $stackFlags['inHeading'] ) ) {
571  $inHeading = $stackFlags['inHeading'];
572  }
573 
574  // Append the result to the enclosing accumulator
575  array_splice( $accum, count( $accum ), 0, $element );
576 
577  // Note that we do NOT increment the input pointer.
578  // This is because the closing linebreak could be the opening linebreak of
579  // another heading. Infinite loops are avoided because the next iteration MUST
580  // hit the heading open case above, which unconditionally increments the
581  // input pointer.
582  } elseif ( $found == 'open' ) {
583  # count opening brace characters
584  $curLen = strlen( $curChar );
585  $count = ( $curLen > 1 ) ?
586  # allow the final character to repeat
587  strspn( $text, $curChar[$curLen - 1], $i + 1 ) + 1 :
588  strspn( $text, $curChar, $i );
589 
590  $savedPrefix = '';
591  $lineStart = ( $i > 0 && $text[$i - 1] == "\n" );
592 
593  if ( $curChar === "-{" && $count > $curLen ) {
594  // -{ => {{ transition because rightmost wins
595  $savedPrefix = '-';
596  $i++;
597  $curChar = '{';
598  $count--;
599  $rule = $this->rules[$curChar];
600  }
601 
602  # we need to add to stack only if opening brace count is enough for one of the rules
603  if ( $count >= $rule['min'] ) {
604  # Add it to the stack
605  $piece = [
606  'open' => $curChar,
607  'close' => $rule['end'],
608  'savedPrefix' => $savedPrefix,
609  'count' => $count,
610  'lineStart' => $lineStart,
611  ];
612 
613  $stack->push( $piece );
614  $accum =& $stack->getAccum();
615  $stackFlags = $stack->getFlags();
616  if ( isset( $stackFlags['findEquals'] ) ) {
617  $findEquals = $stackFlags['findEquals'];
618  }
619  if ( isset( $stackFlags['findPipe'] ) ) {
620  $findPipe = $stackFlags['findPipe'];
621  }
622  if ( isset( $stackFlags['inHeading'] ) ) {
623  $inHeading = $stackFlags['inHeading'];
624  }
625  } else {
626  # Add literal brace(s)
627  self::addLiteral( $accum, $savedPrefix . str_repeat( $curChar, $count ) );
628  }
629  $i += $count;
630  } elseif ( $found == 'close' ) {
631  $piece = $stack->top;
632  # lets check if there are enough characters for closing brace
633  $maxCount = $piece->count;
634  if ( $piece->close === '}-' && $curChar === '}' ) {
635  $maxCount--; # don't try to match closing '-' as a '}'
636  }
637  $curLen = strlen( $curChar );
638  $count = ( $curLen > 1 ) ? $curLen :
639  strspn( $text, $curChar, $i, $maxCount );
640 
641  # check for maximum matching characters (if there are 5 closing
642  # characters, we will probably need only 3 - depending on the rules)
643  $rule = $this->rules[$piece->open];
644  if ( $count > $rule['max'] ) {
645  # The specified maximum exists in the callback array, unless the caller
646  # has made an error
647  $matchingCount = $rule['max'];
648  } else {
649  # Count is less than the maximum
650  # Skip any gaps in the callback array to find the true largest match
651  # Need to use array_key_exists not isset because the callback can be null
652  $matchingCount = $count;
653  while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
654  --$matchingCount;
655  }
656  }
657 
658  if ( $matchingCount <= 0 ) {
659  # No matching element found in callback array
660  # Output a literal closing brace and continue
661  $endText = substr( $text, $i, $count );
662  self::addLiteral( $accum, $endText );
663  $i += $count;
664  continue;
665  }
666  $name = $rule['names'][$matchingCount];
667  if ( $name === null ) {
668  // No element, just literal text
669  $endText = substr( $text, $i, $matchingCount );
670  $element = $piece->breakSyntax( $matchingCount );
671  self::addLiteral( $element, $endText );
672  } else {
673  # Create XML element
674  $parts = $piece->parts;
675  $titleAccum = $parts[0]->out;
676  unset( $parts[0] );
677 
678  $children = [];
679 
680  # The invocation is at the start of the line if lineStart is set in
681  # the stack, and all opening brackets are used up.
682  if ( $maxCount == $matchingCount &&
683  !empty( $piece->lineStart ) &&
684  strlen( $piece->savedPrefix ) == 0 ) {
685  $children[] = [ '@lineStart', [ 1 ] ];
686  }
687  $titleNode = [ 'title', $titleAccum ];
688  $children[] = $titleNode;
689  $argIndex = 1;
690  foreach ( $parts as $part ) {
691  if ( isset( $part->eqpos ) ) {
692  $equalsNode = $part->out[$part->eqpos];
693  $nameNode = [ 'name', array_slice( $part->out, 0, $part->eqpos ) ];
694  $valueNode = [ 'value', array_slice( $part->out, $part->eqpos + 1 ) ];
695  $partNode = [ 'part', [ $nameNode, $equalsNode, $valueNode ] ];
696  $children[] = $partNode;
697  } else {
698  $nameNode = [ 'name', [ [ '@index', [ $argIndex++ ] ] ] ];
699  $valueNode = [ 'value', $part->out ];
700  $partNode = [ 'part', [ $nameNode, $valueNode ] ];
701  $children[] = $partNode;
702  }
703  }
704  $element = [ [ $name, $children ] ];
705  }
706 
707  # Advance input pointer
708  $i += $matchingCount;
709 
710  # Unwind the stack
711  $stack->pop();
712  $accum =& $stack->getAccum();
713 
714  # Re-add the old stack element if it still has unmatched opening characters remaining
715  if ( $matchingCount < $piece->count ) {
716  $piece->parts = [ new PPDPart_Hash ];
717  $piece->count -= $matchingCount;
718  # do we still qualify for any callback with remaining count?
719  $min = $this->rules[$piece->open]['min'];
720  if ( $piece->count >= $min ) {
721  $stack->push( $piece );
722  $accum =& $stack->getAccum();
723  } elseif ( $piece->count == 1 && $piece->open === '{' && $piece->savedPrefix === '-' ) {
724  $piece->savedPrefix = '';
725  $piece->open = '-{';
726  $piece->count = 2;
727  $piece->close = $this->rules[$piece->open]['end'];
728  $stack->push( $piece );
729  $accum =& $stack->getAccum();
730  } else {
731  $s = substr( $piece->open, 0, -1 );
732  $s .= str_repeat(
733  substr( $piece->open, -1 ),
734  $piece->count - strlen( $s )
735  );
736  self::addLiteral( $accum, $piece->savedPrefix . $s );
737  }
738  } elseif ( $piece->savedPrefix !== '' ) {
739  self::addLiteral( $accum, $piece->savedPrefix );
740  }
741 
742  $stackFlags = $stack->getFlags();
743  if ( isset( $stackFlags['findEquals'] ) ) {
744  $findEquals = $stackFlags['findEquals'];
745  }
746  if ( isset( $stackFlags['findPipe'] ) ) {
747  $findPipe = $stackFlags['findPipe'];
748  }
749  if ( isset( $stackFlags['inHeading'] ) ) {
750  $inHeading = $stackFlags['inHeading'];
751  }
752 
753  # Add XML element to the enclosing accumulator
754  array_splice( $accum, count( $accum ), 0, $element );
755  } elseif ( $found == 'pipe' ) {
756  $findEquals = true; // shortcut for getFlags()
757  $stack->addPart();
758  $accum =& $stack->getAccum();
759  ++$i;
760  } elseif ( $found == 'equals' ) {
761  $findEquals = false; // shortcut for getFlags()
762  $accum[] = [ 'equals', [ '=' ] ];
763  $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
764  ++$i;
765  }
766  }
767 
768  # Output any remaining unclosed brackets
769  foreach ( $stack->stack as $piece ) {
770  array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
771  }
772 
773  # Enable top-level headings
774  foreach ( $stack->rootAccum as &$node ) {
775  if ( is_array( $node ) && $node[PPNode_Hash_Tree::NAME] === 'possible-h' ) {
776  $node[PPNode_Hash_Tree::NAME] = 'h';
777  }
778  }
779 
780  $rootStore = [ [ 'root', $stack->rootAccum ] ];
781  $rootNode = new PPNode_Hash_Tree( $rootStore, 0 );
782 
783  // Cache
784  $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
785  if ( $tree !== false ) {
786  $this->cacheSetTree( $text, $flags, $tree );
787  }
788 
789  return $rootNode;
790  }
791 
792  private static function addLiteral( array &$accum, $text ) {
793  $n = count( $accum );
794  if ( $n && is_string( $accum[$n - 1] ) ) {
795  $accum[$n - 1] .= $text;
796  } else {
797  $accum[] = $text;
798  }
799  }
800 }
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:193
in this case you re responsible for computing and outputting the entire conflict part
Definition: hooks.txt:1407
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:773
const PTD_FOR_INCLUSION
Definition: Parser.php:111
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:277
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