MediaWiki REL1_37
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;
43
48
55
61 public $depth;
62
64 private $volatile = false;
66 private $ttl = null;
67
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}
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()
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.
int null $ttl
setVolatile( $flag=true)
Set the volatile flag.
string false[] $titleCache
expand( $root, $flags=0)
implodeWithFlags( $sep, $flags,... $args)
string[] $loopCheckHash
Hashtable listing templates which are disallowed for expansion in this frame, having been encountered...
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...
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:91
const MARKER_PREFIX
Definition Parser.php:150
Represents a title within MediaWiki.
Definition Title.php:48
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1909
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
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s