MediaWiki REL1_35
PPFrame_Hash.php
Go to the documentation of this file.
1<?php
26// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
27class PPFrame_Hash implements PPFrame {
28
32 public $parser;
33
38
42 public $title;
44
50
55 public $depth;
56
57 private $volatile = false;
58 private $ttl = null;
59
72
76 public function __construct( $preprocessor ) {
77 $this->preprocessor = $preprocessor;
78 $this->parser = $preprocessor->parser;
79 $this->title = $this->parser->getTitle();
80 $this->maxPPNodeCount = $this->parser->getOptions()->getMaxPPNodeCount();
81 $this->maxPPExpandDepth = $this->parser->getOptions()->getMaxPPExpandDepth();
82 $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
83 $this->loopCheckHash = [];
84 $this->depth = 0;
85 $this->childExpansionCache = [];
86 }
87
98 public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
99 $namedArgs = [];
100 $numberedArgs = [];
101 if ( $title === false ) {
103 }
104 if ( $args !== false ) {
105 if ( $args instanceof PPNode_Hash_Array ) {
106 $args = $args->value;
107 } elseif ( !is_array( $args ) ) {
108 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
109 }
110 foreach ( $args as $arg ) {
111 $bits = $arg->splitArg();
112 if ( $bits['index'] !== '' ) {
113 // Numbered parameter
114 $index = $bits['index'] - $indexOffset;
115 if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
116 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
117 wfEscapeWikiText( $this->title ),
119 wfEscapeWikiText( $index ) )->text() );
120 $this->parser->addTrackingCategory( 'duplicate-args-category' );
121 }
122 $numberedArgs[$index] = $bits['value'];
123 unset( $namedArgs[$index] );
124 } else {
125 // Named parameter
126 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
127 if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
128 $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
129 wfEscapeWikiText( $this->title ),
131 wfEscapeWikiText( $name ) )->text() );
132 $this->parser->addTrackingCategory( 'duplicate-args-category' );
133 }
134 $namedArgs[$name] = $bits['value'];
135 unset( $numberedArgs[$name] );
136 }
137 }
138 }
139 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
140 }
141
149 public function cachedExpand( $key, $root, $flags = 0 ) {
150 // we don't have a parent, so we don't have a cache
151 return $this->expand( $root, $flags );
152 }
153
160 public function expand( $root, $flags = 0 ) {
161 static $expansionDepth = 0;
162 if ( is_string( $root ) ) {
163 return $root;
164 }
165
166 if ( ++$this->parser->mPPNodeCount > $this->maxPPNodeCount ) {
167 $this->parser->limitationWarn( 'node-count-exceeded',
168 $this->parser->mPPNodeCount,
169 $this->maxPPNodeCount
170 );
171 return '<span class="error">Node-count limit exceeded</span>';
172 }
173 if ( $expansionDepth > $this->maxPPExpandDepth ) {
174 $this->parser->limitationWarn( 'expansion-depth-exceeded',
175 $expansionDepth,
176 $this->maxPPExpandDepth
177 );
178 return '<span class="error">Expansion depth limit exceeded</span>';
179 }
180 ++$expansionDepth;
181 if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
182 $this->parser->mHighestExpansionDepth = $expansionDepth;
183 }
184
185 $outStack = [ '', '' ];
186 $iteratorStack = [ false, $root ];
187 $indexStack = [ 0, 0 ];
188
189 while ( count( $iteratorStack ) > 1 ) {
190 $level = count( $outStack ) - 1;
191 $iteratorNode =& $iteratorStack[$level];
192 $out =& $outStack[$level];
193 $index =& $indexStack[$level];
194
195 if ( is_array( $iteratorNode ) ) {
196 if ( $index >= count( $iteratorNode ) ) {
197 // All done with this iterator
198 $iteratorStack[$level] = false;
199 $contextNode = false;
200 } else {
201 $contextNode = $iteratorNode[$index];
202 $index++;
203 }
204 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
205 if ( $index >= $iteratorNode->getLength() ) {
206 // All done with this iterator
207 $iteratorStack[$level] = false;
208 $contextNode = false;
209 } else {
210 $contextNode = $iteratorNode->item( $index );
211 $index++;
212 }
213 } else {
214 // Copy to $contextNode and then delete from iterator stack,
215 // because this is not an iterator but we do have to execute it once
216 $contextNode = $iteratorStack[$level];
217 $iteratorStack[$level] = false;
218 }
219
220 $newIterator = false;
221 $contextName = false;
222 $contextChildren = false;
223
224 if ( $contextNode === false ) {
225 // nothing to do
226 } elseif ( is_string( $contextNode ) ) {
227 $out .= $contextNode;
228 } elseif ( $contextNode instanceof PPNode_Hash_Array ) {
229 $newIterator = $contextNode;
230 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
231 // No output
232 } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
233 $out .= $contextNode->value;
234 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
235 $contextName = $contextNode->name;
236 $contextChildren = $contextNode->getRawChildren();
237 } elseif ( is_array( $contextNode ) ) {
238 // Node descriptor array
239 if ( count( $contextNode ) !== 2 ) {
240 throw new MWException( __METHOD__ .
241 ': found an array where a node descriptor should be' );
242 }
243 list( $contextName, $contextChildren ) = $contextNode;
244 } else {
245 throw new MWException( __METHOD__ . ': Invalid parameter type' );
246 }
247
248 // Handle node descriptor array or tree object
249 if ( $contextName === false ) {
250 // Not a node, already handled above
251 } elseif ( $contextName[0] === '@' ) {
252 // Attribute: no output
253 } elseif ( $contextName === 'template' ) {
254 # Double-brace expansion
255 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
256 if ( $flags & PPFrame::NO_TEMPLATES ) {
257 $newIterator = $this->virtualBracketedImplode(
258 '{{', '|', '}}',
259 $bits['title'],
260 $bits['parts']
261 );
262 } else {
263 $ret = $this->parser->braceSubstitution( $bits, $this );
264 if ( isset( $ret['object'] ) ) {
265 $newIterator = $ret['object'];
266 } else {
267 $out .= $ret['text'];
268 }
269 }
270 } elseif ( $contextName === 'tplarg' ) {
271 # Triple-brace expansion
272 $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
273 if ( $flags & PPFrame::NO_ARGS ) {
274 $newIterator = $this->virtualBracketedImplode(
275 '{{{', '|', '}}}',
276 $bits['title'],
277 $bits['parts']
278 );
279 } else {
280 $ret = $this->parser->argSubstitution( $bits, $this );
281 if ( isset( $ret['object'] ) ) {
282 $newIterator = $ret['object'];
283 } else {
284 $out .= $ret['text'];
285 }
286 }
287 } elseif ( $contextName === 'comment' ) {
288 # HTML-style comment
289 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
290 # Not in RECOVER_COMMENTS mode (msgnw) though.
291 if ( ( $this->parser->ot['html']
292 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
293 || ( $flags & PPFrame::STRIP_COMMENTS )
294 ) && !( $flags & PPFrame::RECOVER_COMMENTS )
295 ) {
296 $out .= '';
297 } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
298 # Add a strip marker in PST mode so that pstPass2() can
299 # run some old-fashioned regexes on the result.
300 # Not in RECOVER_COMMENTS mode (extractSections) though.
301 $out .= $this->parser->insertStripItem( $contextChildren[0] );
302 } else {
303 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
304 $out .= $contextChildren[0];
305 }
306 } elseif ( $contextName === 'ignore' ) {
307 # Output suppression used by <includeonly> etc.
308 # OT_WIKI will only respect <ignore> in substed templates.
309 # The other output types respect it unless NO_IGNORE is set.
310 # extractSections() sets NO_IGNORE and so never respects it.
311 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
312 || ( $flags & PPFrame::NO_IGNORE )
313 ) {
314 $out .= $contextChildren[0];
315 } else {
316 // $out .= '';
317 }
318 } elseif ( $contextName === 'ext' ) {
319 # Extension tag
320 $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
321 [ 'attr' => null, 'inner' => null, 'close' => null ];
322 if ( $flags & PPFrame::NO_TAGS ) {
323 $s = '<' . $bits['name']->getFirstChild()->value;
324 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
325 if ( $bits['attr'] ) {
326 $s .= $bits['attr']->getFirstChild()->value;
327 }
328 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
329 if ( $bits['inner'] ) {
330 $s .= '>' . $bits['inner']->getFirstChild()->value;
331 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
332 if ( $bits['close'] ) {
333 $s .= $bits['close']->getFirstChild()->value;
334 }
335 } else {
336 $s .= '/>';
337 }
338 $out .= $s;
339 } else {
340 $out .= $this->parser->extensionSubstitution( $bits, $this );
341 }
342 } elseif ( $contextName === 'h' ) {
343 # Heading
344 if ( $this->parser->ot['html'] ) {
345 # Expand immediately and insert heading index marker
346 $s = $this->expand( $contextChildren, $flags );
347 $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
348 $titleText = $this->title->getPrefixedDBkey();
349 $this->parser->mHeadings[] = [ $titleText, $bits['i'] ];
350 $serial = count( $this->parser->mHeadings ) - 1;
351 $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
352 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
353 $this->parser->mStripState->addGeneral( $marker, '' );
354 $out .= $s;
355 } else {
356 # Expand in virtual stack
357 $newIterator = $contextChildren;
358 }
359 } else {
360 # Generic recursive expansion
361 $newIterator = $contextChildren;
362 }
363
364 if ( $newIterator !== false ) {
365 $outStack[] = '';
366 $iteratorStack[] = $newIterator;
367 $indexStack[] = 0;
368 } elseif ( $iteratorStack[$level] === false ) {
369 // Return accumulated value to parent
370 // With tail recursion
371 while ( $iteratorStack[$level] === false && $level > 0 ) {
372 $outStack[$level - 1] .= $out;
373 array_pop( $outStack );
374 array_pop( $iteratorStack );
375 array_pop( $indexStack );
376 $level--;
377 }
378 }
379 }
380 --$expansionDepth;
381 return $outStack[0];
382 }
383
390 public function implodeWithFlags( $sep, $flags, ...$args ) {
391 $first = true;
392 $s = '';
393 foreach ( $args as $root ) {
394 if ( $root instanceof PPNode_Hash_Array ) {
395 $root = $root->value;
396 }
397 if ( !is_array( $root ) ) {
398 $root = [ $root ];
399 }
400 foreach ( $root as $node ) {
401 if ( $first ) {
402 $first = false;
403 } else {
404 $s .= $sep;
405 }
406 $s .= $this->expand( $node, $flags );
407 }
408 }
409 return $s;
410 }
411
419 public function implode( $sep, ...$args ) {
420 $first = true;
421 $s = '';
422 foreach ( $args as $root ) {
423 if ( $root instanceof PPNode_Hash_Array ) {
424 $root = $root->value;
425 }
426 if ( !is_array( $root ) ) {
427 $root = [ $root ];
428 }
429 foreach ( $root as $node ) {
430 if ( $first ) {
431 $first = false;
432 } else {
433 $s .= $sep;
434 }
435 $s .= $this->expand( $node );
436 }
437 }
438 return $s;
439 }
440
449 public function virtualImplode( $sep, ...$args ) {
450 $out = [];
451 $first = true;
452
453 foreach ( $args as $root ) {
454 if ( $root instanceof PPNode_Hash_Array ) {
455 $root = $root->value;
456 }
457 if ( !is_array( $root ) ) {
458 $root = [ $root ];
459 }
460 foreach ( $root as $node ) {
461 if ( $first ) {
462 $first = false;
463 } else {
464 $out[] = $sep;
465 }
466 $out[] = $node;
467 }
468 }
469 return new PPNode_Hash_Array( $out );
470 }
471
481 public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
482 $out = [ $start ];
483 $first = true;
484
485 foreach ( $args as $root ) {
486 if ( $root instanceof PPNode_Hash_Array ) {
487 $root = $root->value;
488 }
489 if ( !is_array( $root ) ) {
490 $root = [ $root ];
491 }
492 foreach ( $root as $node ) {
493 if ( $first ) {
494 $first = false;
495 } else {
496 $out[] = $sep;
497 }
498 $out[] = $node;
499 }
500 }
501 $out[] = $end;
502 return new PPNode_Hash_Array( $out );
503 }
504
505 public function __toString() {
506 return 'frame{}';
507 }
508
513 public function getPDBK( $level = false ) {
514 if ( $level === false ) {
515 return $this->title->getPrefixedDBkey();
516 } else {
517 return $this->titleCache[$level] ?? false;
518 }
519 }
520
524 public function getArguments() {
525 return [];
526 }
527
531 public function getNumberedArguments() {
532 return [];
533 }
534
538 public function getNamedArguments() {
539 return [];
540 }
541
547 public function isEmpty() {
548 return true;
549 }
550
555 public function getArgument( $name ) {
556 return false;
557 }
558
566 public function loopCheck( $title ) {
567 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
568 }
569
575 public function isTemplate() {
576 return false;
577 }
578
584 public function getTitle() {
585 return $this->title;
586 }
587
593 public function setVolatile( $flag = true ) {
594 $this->volatile = $flag;
595 }
596
602 public function isVolatile() {
603 return $this->volatile;
604 }
605
611 public function setTTL( $ttl ) {
612 if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
613 $this->ttl = $ttl;
614 }
615 }
616
622 public function getTTL() {
623 return $this->ttl;
624 }
625}
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
MediaWiki exception.
An expansion frame, used as a context to expand the result of preprocessToObj()
$loopCheckHash
Hashtable listing templates which are disallowed for expansion in this frame, having been encountered...
getArgument( $name)
isEmpty()
Returns true if there are no arguments in this frame.
loopCheck( $title)
Returns true if the infinite loop check is OK, false if a loop is detected.
setVolatile( $flag=true)
Set the volatile flag.
$depth
Recursion depth of this frame, top = 0 Note that this is NOT the same as expansion depth in expand()
expand( $root, $flags=0)
implodeWithFlags( $sep, $flags,... $args)
getTitle()
Get a title of frame.
Preprocessor $preprocessor
setTTL( $ttl)
Set the TTL.
implode( $sep,... $args)
Implode with no flags specified This previously called implodeWithFlags but has now been inlined to r...
getTTL()
Get the TTL.
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:85
Represents a title within MediaWiki.
Definition Title.php:42
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1847
const NO_TEMPLATES
Definition PPFrame.php:30
const NO_TAGS
Definition PPFrame.php:34
const RECOVER_COMMENTS
Definition PPFrame.php:33
const NO_ARGS
Definition PPFrame.php:29
const NO_IGNORE
Definition PPFrame.php:32
const STRIP_COMMENTS
Definition PPFrame.php:31
if( $line===false) $args
Definition mcc.php:124