MediaWiki REL1_40
PPFrame_Hash.php
Go to the documentation of this file.
1<?php
23
28// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
29class PPFrame_Hash implements PPFrame {
30
34 public $parser;
35
40
44 public $title;
45
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 ),
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 ),
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.
Represents a title within MediaWiki.
Definition Title.php:82
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1911
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...
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.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:107
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