MediaWiki master
StripState.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Parser;
25
26use Closure;
27use InvalidArgumentException;
28use Wikimedia\Parsoid\Fragments\PFragment;
29
37 protected $data;
39 protected $extra;
41 protected $regex;
42
43 protected ?Parser $parser;
44
48 protected $depth = 0;
50 protected $highestDepth = 0;
52 protected $expandSize = 0;
53
55 protected $depthLimit = 20;
57 protected $sizeLimit = 5_000_000;
58
65 public function __construct( ?Parser $parser = null, $options = [] ) {
66 $this->data = [
67 'nowiki' => [],
68 'general' => []
69 ];
70 $this->extra = [];
71 $this->regex = '/' . Parser::MARKER_PREFIX . "([^\x7f<>&'\"]+)" . Parser::MARKER_SUFFIX . '/';
72 $this->circularRefGuard = [];
73 $this->parser = $parser;
74
75 if ( isset( $options['depthLimit'] ) ) {
76 $this->depthLimit = $options['depthLimit'];
77 }
78 if ( isset( $options['sizeLimit'] ) ) {
79 $this->sizeLimit = $options['sizeLimit'];
80 }
81 }
82
90 public function addNoWiki( $marker, $value, ?string $extra = null ) {
91 $this->addItem( 'nowiki', $marker, $value, $extra );
92 }
93
98 public function addGeneral( $marker, $value ) {
99 $this->addItem( 'general', $marker, $value );
100 }
101
108 public function addExtTag( $marker, $value ) {
109 $this->addItem( 'exttag', $marker, $value );
110 }
111
118 public function addParsoidOpaque( $marker, PFragment $extra ) {
119 $this->addItem( 'parsoid', $marker, '<parsoid opaque>', $extra );
120 }
121
131 protected function addItem( $type, $marker, $value, $extra = null ) {
132 if ( !preg_match( $this->regex, $marker, $m ) ) {
133 throw new InvalidArgumentException( "Invalid marker: $marker" );
134 }
135
136 $this->data[$type][$m[1]] = $value;
137 if ( $extra !== null ) {
138 $this->extra[$type][$m[1]] = $extra;
139 }
140 }
141
146 public function unstripGeneral( $text ) {
147 return $this->unstripType( 'general', $text );
148 }
149
154 public function unstripNoWiki( $text ) {
155 return $this->unstripType( 'nowiki', $text );
156 }
157
164 public function replaceNoWikis( string $text, callable $callback ): string {
165 / Shortcut
166 if ( !count( $this->data['nowiki'] ) ) {
167 return $text;
168 }
169
170 $callback = function ( $m ) use ( $callback ) {
171 $marker = $m[1];
172 if ( isset( $this->data['nowiki'][$marker] ) ) {
173 $value = $this->data['nowiki'][$marker];
174 if ( $value instanceof Closure ) {
175 $value = $value();
176 }
177
178 $this->expandSize += strlen( $value );
179 if ( $this->expandSize > $this->sizeLimit ) {
180 return $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit );
181 }
182
183 return $callback( $value );
184 } else {
185 return $m[0];
186 }
187 };
188
189 return preg_replace_callback( $this->regex, $callback, $text );
190 }
191
200 public function split( string $text ): array {
201 $result = [];
202 $pieces = preg_split( $this->regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
203 for ( $i = 0; $i < count( $pieces ); $i++ ) {
204 if ( $i % 2 === 0 ) {
205 $result[] = [
206 'type' => 'string',
207 'content' => $pieces[$i],
208 ];
209 continue;
210 }
211 $marker = $pieces[$i];
212 foreach ( $this->data as $type => $items ) {
213 if ( isset( $items[$marker] ) ) {
214 $value = $items[$marker];
215 $extra = $this->extra[$type][$marker] ?? null;
216 if ( $value instanceof Closure ) {
217 $value = $value();
218 }
219
220 if ( $type === 'exttag' ) {
221 / Catch circular refs / enforce depth limits
222 / similar to code in unstripType().
223 if ( isset( $this->circularRefGuard[$marker] ) ) {
224 $result[] = [
225 'type' => 'string',
226 'content' => $this->getWarning( 'parser-unstrip-loop-warning' )
227 ];
228 continue;
229 }
230
231 if ( $this->depth > $this->highestDepth ) {
232 $this->highestDepth = $this->depth;
233 }
234 if ( $this->depth >= $this->depthLimit ) {
235 $result[] = [
236 'type' => 'string',
237 'content' => $this->getLimitationWarning( 'unstrip-depth', $this->depthLimit )
238 ];
239 continue;
240 }
241
242 / For exttag types, the output size should include the output of
243 / the extension, but don't think unstripType is doing that, and so
244 / we aren't doing that either here. But this is kinda broken.
245 / See T380758#10355050.
246 $this->expandSize += strlen( $value );
247 if ( $this->expandSize > $this->sizeLimit ) {
248 $result[] = [
249 'type' => 'string',
250 'content' => $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit )
251 ];
252 continue;
253 }
254
255 $this->circularRefGuard[$marker] = true;
256 $this->depth++;
257 $result = array_merge( $result, $this->split( $value ) );
258 $this->depth--;
259 unset( $this->circularRefGuard[$marker] );
260 } else {
261 $result[] = [
262 'type' => $type,
263 'content' => $value,
264 'extra' => $extra,
265 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
266 ];
267 }
268 continue 2;
269 }
270 }
271 $result[] = [
272 'type' => 'unknown',
273 'content' => null,
274 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
275 ];
276 }
277 return $result;
278 }
279
284 public function unstripBoth( $text ) {
285 $text = $this->unstripType( 'general', $text );
286 $text = $this->unstripType( 'nowiki', $text );
287 return $text;
288 }
289
295 protected function unstripType( $type, $text ) {
296 / Shortcut
297 if ( !count( $this->data[$type] ) ) {
298 return $text;
299 }
300
301 $callback = function ( $m ) use ( $type ) {
302 $marker = $m[1];
303 if ( isset( $this->data[$type][$marker] ) ) {
304 if ( isset( $this->circularRefGuard[$marker] ) ) {
305 return $this->getWarning( 'parser-unstrip-loop-warning' );
306 }
307
308 if ( $this->depth > $this->highestDepth ) {
309 $this->highestDepth = $this->depth;
310 }
311 if ( $this->depth >= $this->depthLimit ) {
312 return $this->getLimitationWarning( 'unstrip-depth', $this->depthLimit );
313 }
314
315 $value = $this->data[$type][$marker];
316 if ( $value instanceof Closure ) {
317 $value = $value();
318 }
319
320 $this->expandSize += strlen( $value );
321 if ( $this->expandSize > $this->sizeLimit ) {
322 return $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit );
323 }
324
325 $this->circularRefGuard[$marker] = true;
326 $this->depth++;
327 $ret = $this->unstripType( $type, $value );
328 $this->depth--;
329 unset( $this->circularRefGuard[$marker] );
330
331 return $ret;
332 } else {
333 return $m[0];
334 }
335 };
336
337 $text = preg_replace_callback( $this->regex, $callback, $text );
338 return $text;
339 }
340
348 private function getLimitationWarning( $type, $max = '' ) {
349 if ( $this->parser ) {
350 $this->parser->limitationWarn( $type, $max );
351 }
352 return $this->getWarning( "$type-warning", $max );
353 }
354
362 private function getWarning( $message, $max = '' ) {
363 return '<span class="error">' .
364 wfMessage( $message )
365 ->numParams( $max )->inContentLanguage()->text() .
366 '</span>';
367 }
368
375 public function getLimitReport() {
376 return [
377 [ 'limitreport-unstrip-depth',
378 [
379 $this->highestDepth,
380 $this->depthLimit
381 ],
382 ],
383 [ 'limitreport-unstrip-size',
384 [
385 $this->expandSize,
386 $this->sizeLimit
387 ],
388 ]
389 ];
390 }
391
398 public function killMarkers( $text ) {
399 return preg_replace( $this->regex, '', $text );
400 }
401}
402
404class_alias( StripState::class, 'StripState' );
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:147
__construct(?Parser $parser=null, $options=[])
addItem( $type, $marker, $value, $extra=null)
replaceNoWikis(string $text, callable $callback)
addParsoidOpaque( $marker, PFragment $extra)
addNoWiki( $marker, $value, ?string $extra=null)
Add a nowiki strip item.
killMarkers( $text)
Remove any strip markers found in the given text.
getLimitReport()
Get an array of parameters to pass to ParserOutput::setLimitReportData()
addGeneral( $marker, $value)
addExtTag( $marker, $value)
split(string $text)
Split the given text by strip markers, returning an array of [ 'type' => ..., 'content' => ....

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant