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