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 
108  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
109  $namedArgs = [];
110  $numberedArgs = [];
111  if ( $title === false ) {
113  }
114  if ( $args !== false ) {
115  if ( $args instanceof PPNode_Hash_Array ) {
116  $args = $args->value;
117  } elseif ( !is_array( $args ) ) {
118  throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
119  }
120  foreach ( $args as $arg ) {
121  $bits = $arg->splitArg();
122  if ( $bits['index'] !== '' ) {
123  // Numbered parameter
124  $index = $bits['index'] - $indexOffset;
125  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
126  $this->parser->getOutput()->addWarningMsg(
127  'duplicate-args-warning',
128  Message::plaintextParam( (string)$this->title ),
129  Message::plaintextParam( (string)$title ),
130  Message::numParam( $index )
131  );
132  $this->parser->addTrackingCategory( 'duplicate-args-category' );
133  }
134  $numberedArgs[$index] = $bits['value'];
135  unset( $namedArgs[$index] );
136  } else {
137  // Named parameter
138  $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
139  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
140  $this->parser->getOutput()->addWarningMsg(
141  'duplicate-args-warning',
142  Message::plaintextParam( (string)$this->title ),
143  Message::plaintextParam( (string)$title ),
144  Message::plaintextParam( $name )
145  );
146  $this->parser->addTrackingCategory( 'duplicate-args-category' );
147  }
148  $namedArgs[$name] = $bits['value'];
149  unset( $numberedArgs[$name] );
150  }
151  }
152  }
153  return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
154  }
155 
163  public function cachedExpand( $key, $root, $flags = 0 ) {
164  // we don't have a parent, so we don't have a cache
165  return $this->expand( $root, $flags );
166  }
167 
174  public function expand( $root, $flags = 0 ) {
175  static $expansionDepth = 0;
176  if ( is_string( $root ) ) {
177  return $root;
178  }
179 
180  if ( ++$this->parser->mPPNodeCount > $this->maxPPNodeCount ) {
181  $this->parser->limitationWarn( 'node-count-exceeded',
182  $this->parser->mPPNodeCount,
183  $this->maxPPNodeCount
184  );
185  return '<span class="error">Node-count limit exceeded</span>';
186  }
187  if ( $expansionDepth > $this->maxPPExpandDepth ) {
188  $this->parser->limitationWarn( 'expansion-depth-exceeded',
189  $expansionDepth,
190  $this->maxPPExpandDepth
191  );
192  return '<span class="error">Expansion depth limit exceeded</span>';
193  }
194  ++$expansionDepth;
195  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
196  $this->parser->mHighestExpansionDepth = $expansionDepth;
197  }
198 
199  $outStack = [ '', '' ];
200  $iteratorStack = [ false, $root ];
201  $indexStack = [ 0, 0 ];
202 
203  while ( count( $iteratorStack ) > 1 ) {
204  $level = count( $outStack ) - 1;
205  $iteratorNode =& $iteratorStack[$level];
206  $out =& $outStack[$level];
207  $index =& $indexStack[$level];
208 
209  if ( is_array( $iteratorNode ) ) {
210  if ( $index >= count( $iteratorNode ) ) {
211  // All done with this iterator
212  $iteratorStack[$level] = false;
213  $contextNode = false;
214  } else {
215  $contextNode = $iteratorNode[$index];
216  $index++;
217  }
218  } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
219  if ( $index >= $iteratorNode->getLength() ) {
220  // All done with this iterator
221  $iteratorStack[$level] = false;
222  $contextNode = false;
223  } else {
224  $contextNode = $iteratorNode->item( $index );
225  $index++;
226  }
227  } else {
228  // Copy to $contextNode and then delete from iterator stack,
229  // because this is not an iterator but we do have to execute it once
230  $contextNode = $iteratorStack[$level];
231  $iteratorStack[$level] = false;
232  }
233 
234  $newIterator = false;
235  $contextName = false;
236  $contextChildren = false;
237 
238  if ( $contextNode === false ) {
239  // nothing to do
240  } elseif ( is_string( $contextNode ) ) {
241  $out .= $contextNode;
242  } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
243  $newIterator = $contextNode;
244  } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
245  // No output
246  } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
247  $out .= $contextNode->value;
248  } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
249  $contextName = $contextNode->name;
250  $contextChildren = $contextNode->getRawChildren();
251  } elseif ( is_array( $contextNode ) ) {
252  // Node descriptor array
253  if ( count( $contextNode ) !== 2 ) {
254  throw new MWException( __METHOD__ .
255  ': found an array where a node descriptor should be' );
256  }
257  [ $contextName, $contextChildren ] = $contextNode;
258  } else {
259  throw new MWException( __METHOD__ . ': Invalid parameter type' );
260  }
261 
262  // Handle node descriptor array or tree object
263  if ( $contextName === false ) {
264  // Not a node, already handled above
265  } elseif ( $contextName[0] === '@' ) {
266  // Attribute: no output
267  } elseif ( $contextName === 'template' ) {
268  # Double-brace expansion
269  $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
270  if ( $flags & PPFrame::NO_TEMPLATES ) {
271  $newIterator = $this->virtualBracketedImplode(
272  '{{', '|', '}}',
273  $bits['title'],
274  $bits['parts']
275  );
276  } else {
277  $ret = $this->parser->braceSubstitution( $bits, $this );
278  if ( isset( $ret['object'] ) ) {
279  $newIterator = $ret['object'];
280  } else {
281  $out .= $ret['text'];
282  }
283  }
284  } elseif ( $contextName === 'tplarg' ) {
285  # Triple-brace expansion
286  $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
287  if ( $flags & PPFrame::NO_ARGS ) {
288  $newIterator = $this->virtualBracketedImplode(
289  '{{{', '|', '}}}',
290  $bits['title'],
291  $bits['parts']
292  );
293  } else {
294  $ret = $this->parser->argSubstitution( $bits, $this );
295  if ( isset( $ret['object'] ) ) {
296  $newIterator = $ret['object'];
297  } else {
298  $out .= $ret['text'];
299  }
300  }
301  } elseif ( $contextName === 'comment' ) {
302  # HTML-style comment
303  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
304  # Not in RECOVER_COMMENTS mode (msgnw) though.
305  if ( ( $this->parser->ot['html']
306  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
307  || ( $flags & PPFrame::STRIP_COMMENTS )
308  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
309  ) {
310  $out .= '';
311  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
312  # Add a strip marker in PST mode so that pstPass2() can
313  # run some old-fashioned regexes on the result.
314  # Not in RECOVER_COMMENTS mode (extractSections) though.
315  $out .= $this->parser->insertStripItem( $contextChildren[0] );
316  } else {
317  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
318  $out .= $contextChildren[0];
319  }
320  } elseif ( $contextName === 'ignore' ) {
321  # Output suppression used by <includeonly> etc.
322  # OT_WIKI will only respect <ignore> in substed templates.
323  # The other output types respect it unless NO_IGNORE is set.
324  # extractSections() sets NO_IGNORE and so never respects it.
325  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
326  || ( $flags & PPFrame::NO_IGNORE )
327  ) {
328  $out .= $contextChildren[0];
329  } else {
330  // $out .= '';
331  }
332  } elseif ( $contextName === 'ext' ) {
333  # Extension tag
334  $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
335  [ 'attr' => null, 'inner' => null, 'close' => null ];
336  if ( $flags & PPFrame::NO_TAGS ) {
337  $s = '<' . $bits['name']->getFirstChild()->value;
338  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
339  if ( $bits['attr'] ) {
340  $s .= $bits['attr']->getFirstChild()->value;
341  }
342  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
343  if ( $bits['inner'] ) {
344  $s .= '>' . $bits['inner']->getFirstChild()->value;
345  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
346  if ( $bits['close'] ) {
347  $s .= $bits['close']->getFirstChild()->value;
348  }
349  } else {
350  $s .= '/>';
351  }
352  $out .= $s;
353  } else {
354  $out .= $this->parser->extensionSubstitution( $bits, $this,
355  (bool)( $flags & PPFrame::PROCESS_NOWIKI ) );
356  }
357  } elseif ( $contextName === 'h' ) {
358  # Heading
359  if ( $this->parser->ot['html'] ) {
360  # Expand immediately and insert heading index marker
361  $s = $this->expand( $contextChildren, $flags );
362  $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
363  $titleText = $this->title->getPrefixedDBkey();
364  $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
365  $serial = count( $this->parser->mHeadings ) - 1;
366  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
367  $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
368  $this->parser->getStripState()->addGeneral( $marker, '' );
369  $out .= $s;
370  } else {
371  # Expand in virtual stack
372  $newIterator = $contextChildren;
373  }
374  } else {
375  # Generic recursive expansion
376  $newIterator = $contextChildren;
377  }
378 
379  if ( $newIterator !== false ) {
380  $outStack[] = '';
381  $iteratorStack[] = $newIterator;
382  $indexStack[] = 0;
383  } elseif ( $iteratorStack[$level] === false ) {
384  // Return accumulated value to parent
385  // With tail recursion
386  while ( $iteratorStack[$level] === false && $level > 0 ) {
387  $outStack[$level - 1] .= $out;
388  array_pop( $outStack );
389  array_pop( $iteratorStack );
390  array_pop( $indexStack );
391  $level--;
392  }
393  }
394  }
395  --$expansionDepth;
396  return $outStack[0];
397  }
398 
405  public function implodeWithFlags( $sep, $flags, ...$args ) {
406  $first = true;
407  $s = '';
408  foreach ( $args as $root ) {
409  if ( $root instanceof PPNode_Hash_Array ) {
410  $root = $root->value;
411  }
412  if ( !is_array( $root ) ) {
413  $root = [ $root ];
414  }
415  foreach ( $root as $node ) {
416  if ( $first ) {
417  $first = false;
418  } else {
419  $s .= $sep;
420  }
421  $s .= $this->expand( $node, $flags );
422  }
423  }
424  return $s;
425  }
426 
434  public function implode( $sep, ...$args ) {
435  $first = true;
436  $s = '';
437  foreach ( $args as $root ) {
438  if ( $root instanceof PPNode_Hash_Array ) {
439  $root = $root->value;
440  }
441  if ( !is_array( $root ) ) {
442  $root = [ $root ];
443  }
444  foreach ( $root as $node ) {
445  if ( $first ) {
446  $first = false;
447  } else {
448  $s .= $sep;
449  }
450  $s .= $this->expand( $node );
451  }
452  }
453  return $s;
454  }
455 
464  public function virtualImplode( $sep, ...$args ) {
465  $out = [];
466  $first = true;
467 
468  foreach ( $args as $root ) {
469  if ( $root instanceof PPNode_Hash_Array ) {
470  $root = $root->value;
471  }
472  if ( !is_array( $root ) ) {
473  $root = [ $root ];
474  }
475  foreach ( $root as $node ) {
476  if ( $first ) {
477  $first = false;
478  } else {
479  $out[] = $sep;
480  }
481  $out[] = $node;
482  }
483  }
484  return new PPNode_Hash_Array( $out );
485  }
486 
496  public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
497  $out = [ $start ];
498  $first = true;
499 
500  foreach ( $args as $root ) {
501  if ( $root instanceof PPNode_Hash_Array ) {
502  $root = $root->value;
503  }
504  if ( !is_array( $root ) ) {
505  $root = [ $root ];
506  }
507  foreach ( $root as $node ) {
508  if ( $first ) {
509  $first = false;
510  } else {
511  $out[] = $sep;
512  }
513  $out[] = $node;
514  }
515  }
516  $out[] = $end;
517  return new PPNode_Hash_Array( $out );
518  }
519 
520  public function __toString() {
521  return 'frame{}';
522  }
523 
528  public function getPDBK( $level = false ) {
529  if ( $level === false ) {
530  return $this->title->getPrefixedDBkey();
531  } else {
532  return $this->titleCache[$level] ?? false;
533  }
534  }
535 
539  public function getArguments() {
540  return [];
541  }
542 
546  public function getNumberedArguments() {
547  return [];
548  }
549 
553  public function getNamedArguments() {
554  return [];
555  }
556 
562  public function isEmpty() {
563  return true;
564  }
565 
570  public function getArgument( $name ) {
571  return false;
572  }
573 
581  public function loopCheck( $title ) {
582  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
583  }
584 
590  public function isTemplate() {
591  return false;
592  }
593 
599  public function getTitle() {
600  return $this->title;
601  }
602 
608  public function setVolatile( $flag = true ) {
609  $this->volatile = $flag;
610  }
611 
617  public function isVolatile() {
618  return $this->volatile;
619  }
620 
624  public function setTTL( $ttl ) {
625  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
626  $this->ttl = $ttl;
627  }
628  }
629 
633  public function getTTL() {
634  return $this->ttl;
635  }
636 }
MediaWiki exception.
Definition: MWException.php:32
Represents a title within MediaWiki.
Definition: Title.php:82
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1915
static plaintextParam( $plaintext)
Definition: Message.php:1267
static numParam( $num)
Definition: Message.php:1146
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:165
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
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s