MediaWiki  master
PPFrame_Hash.php
Go to the documentation of this file.
1 <?php
23 
28 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
29 class PPFrame_Hash implements PPFrame {
30 
34  public $parser;
35 
39  public $preprocessor;
40 
44  public $title;
45 
49  public $titleCache;
50 
57 
63  public $depth;
64 
66  private $volatile = false;
68  private $ttl = null;
69 
77  private $maxPPNodeCount;
81  private $maxPPExpandDepth;
82 
86  public function __construct( $preprocessor ) {
87  $this->preprocessor = $preprocessor;
88  $this->parser = $preprocessor->parser;
89  $this->title = $this->parser->getTitle();
90  $this->maxPPNodeCount = $this->parser->getOptions()->getMaxPPNodeCount();
91  $this->maxPPExpandDepth = $this->parser->getOptions()->getMaxPPExpandDepth();
92  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
93  $this->loopCheckHash = [];
94  $this->depth = 0;
95  $this->childExpansionCache = [];
96  }
97 
107  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
108  $namedArgs = [];
109  $numberedArgs = [];
110  if ( $title === false ) {
112  }
113  if ( $args !== false ) {
114  if ( $args instanceof PPNode_Hash_Array ) {
115  $args = $args->value;
116  } elseif ( !is_array( $args ) ) {
117  throw new InvalidArgumentException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
118  }
119  foreach ( $args as $arg ) {
120  $bits = $arg->splitArg();
121  if ( $bits['index'] !== '' ) {
122  // Numbered parameter
123  $index = $bits['index'] - $indexOffset;
124  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
125  $this->parser->getOutput()->addWarningMsg(
126  'duplicate-args-warning',
127  Message::plaintextParam( (string)$this->title ),
128  Message::plaintextParam( (string)$title ),
129  Message::numParam( $index )
130  );
131  $this->parser->addTrackingCategory( 'duplicate-args-category' );
132  }
133  $numberedArgs[$index] = $bits['value'];
134  unset( $namedArgs[$index] );
135  } else {
136  // Named parameter
137  $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
138  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
139  $this->parser->getOutput()->addWarningMsg(
140  'duplicate-args-warning',
141  Message::plaintextParam( (string)$this->title ),
142  Message::plaintextParam( (string)$title ),
143  Message::plaintextParam( $name )
144  );
145  $this->parser->addTrackingCategory( 'duplicate-args-category' );
146  }
147  $namedArgs[$name] = $bits['value'];
148  unset( $numberedArgs[$name] );
149  }
150  }
151  }
152  return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
153  }
154 
161  public function cachedExpand( $key, $root, $flags = 0 ) {
162  // we don't have a parent, so we don't have a cache
163  return $this->expand( $root, $flags );
164  }
165 
171  public function expand( $root, $flags = 0 ) {
172  static $expansionDepth = 0;
173  if ( is_string( $root ) ) {
174  return $root;
175  }
176 
177  if ( ++$this->parser->mPPNodeCount > $this->maxPPNodeCount ) {
178  $this->parser->limitationWarn( 'node-count-exceeded',
179  $this->parser->mPPNodeCount,
180  $this->maxPPNodeCount
181  );
182  return '<span class="error">Node-count limit exceeded</span>';
183  }
184  if ( $expansionDepth > $this->maxPPExpandDepth ) {
185  $this->parser->limitationWarn( 'expansion-depth-exceeded',
186  $expansionDepth,
187  $this->maxPPExpandDepth
188  );
189  return '<span class="error">Expansion depth limit exceeded</span>';
190  }
191  ++$expansionDepth;
192  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
193  $this->parser->mHighestExpansionDepth = $expansionDepth;
194  }
195 
196  $outStack = [ '', '' ];
197  $iteratorStack = [ false, $root ];
198  $indexStack = [ 0, 0 ];
199 
200  while ( count( $iteratorStack ) > 1 ) {
201  $level = count( $outStack ) - 1;
202  $iteratorNode =& $iteratorStack[$level];
203  $out =& $outStack[$level];
204  $index =& $indexStack[$level];
205 
206  if ( is_array( $iteratorNode ) ) {
207  if ( $index >= count( $iteratorNode ) ) {
208  // All done with this iterator
209  $iteratorStack[$level] = false;
210  $contextNode = false;
211  } else {
212  $contextNode = $iteratorNode[$index];
213  $index++;
214  }
215  } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
216  if ( $index >= $iteratorNode->getLength() ) {
217  // All done with this iterator
218  $iteratorStack[$level] = false;
219  $contextNode = false;
220  } else {
221  $contextNode = $iteratorNode->item( $index );
222  $index++;
223  }
224  } else {
225  // Copy to $contextNode and then delete from iterator stack,
226  // because this is not an iterator but we do have to execute it once
227  $contextNode = $iteratorStack[$level];
228  $iteratorStack[$level] = false;
229  }
230 
231  $newIterator = false;
232  $contextName = false;
233  $contextChildren = false;
234 
235  if ( $contextNode === false ) {
236  // nothing to do
237  } elseif ( is_string( $contextNode ) ) {
238  $out .= $contextNode;
239  } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
240  $newIterator = $contextNode;
241  } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
242  // No output
243  } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
244  $out .= $contextNode->value;
245  } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
246  $contextName = $contextNode->name;
247  $contextChildren = $contextNode->getRawChildren();
248  } elseif ( is_array( $contextNode ) ) {
249  // Node descriptor array
250  if ( count( $contextNode ) !== 2 ) {
251  throw new RuntimeException( __METHOD__ .
252  ': found an array where a node descriptor should be' );
253  }
254  [ $contextName, $contextChildren ] = $contextNode;
255  } else {
256  throw new RuntimeException( __METHOD__ . ': Invalid parameter type' );
257  }
258 
259  // Handle node descriptor array or tree object
260  if ( $contextName === false ) {
261  // Not a node, already handled above
262  } elseif ( $contextName[0] === '@' ) {
263  // Attribute: no output
264  } elseif ( $contextName === 'template' ) {
265  # Double-brace expansion
266  $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
267  if ( $flags & PPFrame::NO_TEMPLATES ) {
268  $newIterator = $this->virtualBracketedImplode(
269  '{{', '|', '}}',
270  $bits['title'],
271  $bits['parts']
272  );
273  } else {
274  $ret = $this->parser->braceSubstitution( $bits, $this );
275  if ( isset( $ret['object'] ) ) {
276  $newIterator = $ret['object'];
277  } else {
278  $out .= $ret['text'];
279  }
280  }
281  } elseif ( $contextName === 'tplarg' ) {
282  # Triple-brace expansion
283  $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
284  if ( $flags & PPFrame::NO_ARGS ) {
285  $newIterator = $this->virtualBracketedImplode(
286  '{{{', '|', '}}}',
287  $bits['title'],
288  $bits['parts']
289  );
290  } else {
291  $ret = $this->parser->argSubstitution( $bits, $this );
292  if ( isset( $ret['object'] ) ) {
293  $newIterator = $ret['object'];
294  } else {
295  $out .= $ret['text'];
296  }
297  }
298  } elseif ( $contextName === 'comment' ) {
299  # HTML-style comment
300  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
301  # Not in RECOVER_COMMENTS mode (msgnw) though.
302  if ( ( $this->parser->ot['html']
303  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
304  || ( $flags & PPFrame::STRIP_COMMENTS )
305  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
306  ) {
307  $out .= '';
308  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
309  # Add a strip marker in PST mode so that pstPass2() can
310  # run some old-fashioned regexes on the result.
311  # Not in RECOVER_COMMENTS mode (extractSections) though.
312  $out .= $this->parser->insertStripItem( $contextChildren[0] );
313  } else {
314  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
315  $out .= $contextChildren[0];
316  }
317  } elseif ( $contextName === 'ignore' ) {
318  # Output suppression used by <includeonly> etc.
319  # OT_WIKI will only respect <ignore> in substed templates.
320  # The other output types respect it unless NO_IGNORE is set.
321  # extractSections() sets NO_IGNORE and so never respects it.
322  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
323  || ( $flags & PPFrame::NO_IGNORE )
324  ) {
325  $out .= $contextChildren[0];
326  } else {
327  // $out .= '';
328  }
329  } elseif ( $contextName === 'ext' ) {
330  # Extension tag
331  $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
332  [ 'attr' => null, 'inner' => null, 'close' => null ];
333  if ( $flags & PPFrame::NO_TAGS ) {
334  $s = '<' . $bits['name']->getFirstChild()->value;
335  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
336  if ( $bits['attr'] ) {
337  $s .= $bits['attr']->getFirstChild()->value;
338  }
339  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
340  if ( $bits['inner'] ) {
341  $s .= '>' . $bits['inner']->getFirstChild()->value;
342  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
343  if ( $bits['close'] ) {
344  $s .= $bits['close']->getFirstChild()->value;
345  }
346  } else {
347  $s .= '/>';
348  }
349  $out .= $s;
350  } else {
351  $out .= $this->parser->extensionSubstitution( $bits, $this,
352  (bool)( $flags & PPFrame::PROCESS_NOWIKI ) );
353  }
354  } elseif ( $contextName === 'h' ) {
355  # Heading
356  if ( $this->parser->ot['html'] ) {
357  # Expand immediately and insert heading index marker
358  $s = $this->expand( $contextChildren, $flags );
359  $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
360  $titleText = $this->title->getPrefixedDBkey();
361  $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
362  $serial = count( $this->parser->mHeadings ) - 1;
363  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
364  $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
365  $this->parser->getStripState()->addGeneral( $marker, '' );
366  $out .= $s;
367  } else {
368  # Expand in virtual stack
369  $newIterator = $contextChildren;
370  }
371  } else {
372  # Generic recursive expansion
373  $newIterator = $contextChildren;
374  }
375 
376  if ( $newIterator !== false ) {
377  $outStack[] = '';
378  $iteratorStack[] = $newIterator;
379  $indexStack[] = 0;
380  } elseif ( $iteratorStack[$level] === false ) {
381  // Return accumulated value to parent
382  // With tail recursion
383  while ( $iteratorStack[$level] === false && $level > 0 ) {
384  $outStack[$level - 1] .= $out;
385  array_pop( $outStack );
386  array_pop( $iteratorStack );
387  array_pop( $indexStack );
388  $level--;
389  }
390  }
391  }
392  --$expansionDepth;
393  return $outStack[0];
394  }
395 
402  public function implodeWithFlags( $sep, $flags, ...$args ) {
403  $first = true;
404  $s = '';
405  foreach ( $args as $root ) {
406  if ( $root instanceof PPNode_Hash_Array ) {
407  $root = $root->value;
408  }
409  if ( !is_array( $root ) ) {
410  $root = [ $root ];
411  }
412  foreach ( $root as $node ) {
413  if ( $first ) {
414  $first = false;
415  } else {
416  $s .= $sep;
417  }
418  $s .= $this->expand( $node, $flags );
419  }
420  }
421  return $s;
422  }
423 
431  public function implode( $sep, ...$args ) {
432  $first = true;
433  $s = '';
434  foreach ( $args as $root ) {
435  if ( $root instanceof PPNode_Hash_Array ) {
436  $root = $root->value;
437  }
438  if ( !is_array( $root ) ) {
439  $root = [ $root ];
440  }
441  foreach ( $root as $node ) {
442  if ( $first ) {
443  $first = false;
444  } else {
445  $s .= $sep;
446  }
447  $s .= $this->expand( $node );
448  }
449  }
450  return $s;
451  }
452 
461  public function virtualImplode( $sep, ...$args ) {
462  $out = [];
463  $first = true;
464 
465  foreach ( $args as $root ) {
466  if ( $root instanceof PPNode_Hash_Array ) {
467  $root = $root->value;
468  }
469  if ( !is_array( $root ) ) {
470  $root = [ $root ];
471  }
472  foreach ( $root as $node ) {
473  if ( $first ) {
474  $first = false;
475  } else {
476  $out[] = $sep;
477  }
478  $out[] = $node;
479  }
480  }
481  return new PPNode_Hash_Array( $out );
482  }
483 
493  public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
494  $out = [ $start ];
495  $first = true;
496 
497  foreach ( $args as $root ) {
498  if ( $root instanceof PPNode_Hash_Array ) {
499  $root = $root->value;
500  }
501  if ( !is_array( $root ) ) {
502  $root = [ $root ];
503  }
504  foreach ( $root as $node ) {
505  if ( $first ) {
506  $first = false;
507  } else {
508  $out[] = $sep;
509  }
510  $out[] = $node;
511  }
512  }
513  $out[] = $end;
514  return new PPNode_Hash_Array( $out );
515  }
516 
517  public function __toString() {
518  return 'frame{}';
519  }
520 
525  public function getPDBK( $level = false ) {
526  if ( $level === false ) {
527  return $this->title->getPrefixedDBkey();
528  } else {
529  return $this->titleCache[$level] ?? false;
530  }
531  }
532 
536  public function getArguments() {
537  return [];
538  }
539 
543  public function getNumberedArguments() {
544  return [];
545  }
546 
550  public function getNamedArguments() {
551  return [];
552  }
553 
559  public function isEmpty() {
560  return true;
561  }
562 
567  public function getArgument( $name ) {
568  return false;
569  }
570 
578  public function loopCheck( $title ) {
579  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
580  }
581 
587  public function isTemplate() {
588  return false;
589  }
590 
596  public function getTitle() {
597  return $this->title;
598  }
599 
605  public function setVolatile( $flag = true ) {
606  $this->volatile = $flag;
607  }
608 
614  public function isVolatile() {
615  return $this->volatile;
616  }
617 
621  public function setTTL( $ttl ) {
622  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
623  $this->ttl = $ttl;
624  }
625  }
626 
630  public function getTTL() {
631  return $this->ttl;
632  }
633 }
Represents a title within MediaWiki.
Definition: Title.php:76
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1873
static plaintextParam( $plaintext)
Definition: Message.php:1268
static numParam( $num)
Definition: Message.php:1147
An expansion frame, used as a context to expand the result of preprocessToObj()
getArgument( $name)
isEmpty()
Returns true if there are no arguments in this frame.
int $depth
Recursion depth of this frame, top = 0 Note that this is NOT the same as expansion depth in expand()
loopCheck( $title)
Returns true if the infinite loop check is OK, false if a loop is detected.
setVolatile( $flag=true)
Set the volatile flag.
string false[] $titleCache
expand( $root, $flags=0)
implodeWithFlags( $sep, $flags,... $args)
getTitle()
Get a title of frame.
Preprocessor $preprocessor
implode( $sep,... $args)
Implode with no flags specified This previously called implodeWithFlags but has now been inlined to r...
true[] $loopCheckHash
Hashtable listing templates which are disallowed for expansion in this frame, having been encountered...
cachedExpand( $key, $root, $flags=0)
__construct( $preprocessor)
array $childExpansionCache
virtualImplode( $sep,... $args)
Makes an object that, when expand()ed, will be the same as one obtained with implode()
virtualBracketedImplode( $start, $sep, $end,... $args)
Virtual implode with brackets.
isVolatile()
Get the volatile flag.
newChild( $args=false, $title=false, $indexOffset=0)
Create a new child frame $args is optionally a multi-root PPNode or array containing the template arg...
Parser $parser
isTemplate()
Return true if the frame is a template frame.
getPDBK( $level=false)
static splitRawTemplate(array $children)
Like splitTemplate() but for a raw child array.
static splitRawHeading(array $children)
Like splitHeading() but for a raw child array.
static splitRawExt(array $children)
Like splitExt() but for a raw child array.
Expansion frame with template arguments.
const MARKER_PREFIX
Definition: Parser.php:181
const NO_TEMPLATES
Definition: PPFrame.php:32
const NO_TAGS
Definition: PPFrame.php:36
const PROCESS_NOWIKI
Definition: PPFrame.php:37
const RECOVER_COMMENTS
Definition: PPFrame.php:35
const NO_ARGS
Definition: PPFrame.php:31
const NO_IGNORE
Definition: PPFrame.php:34
const STRIP_COMMENTS
Definition: PPFrame.php:33