MediaWiki master
StripState.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Parser;
25
26use Closure;
27use InvalidArgumentException;
28
36 protected $data;
38 protected $extra;
40 protected $regex;
41
42 protected ?Parser $parser;
43
47 protected $depth = 0;
49 protected $highestDepth = 0;
51 protected $expandSize = 0;
52
54 protected $depthLimit = 20;
56 protected $sizeLimit = 5_000_000;
57
64 public function __construct( ?Parser $parser = null, $options = [] ) {
65 $this->data = [
66 'nowiki' => [],
67 'general' => []
68 ];
69 $this->extra = [];
70 $this->regex = '/' . Parser::MARKER_PREFIX . "([^\x7f<>&'\"]+)" . Parser::MARKER_SUFFIX . '/';
71 $this->circularRefGuard = [];
72 $this->parser = $parser;
73
74 if ( isset( $options['depthLimit'] ) ) {
75 $this->depthLimit = $options['depthLimit'];
76 }
77 if ( isset( $options['sizeLimit'] ) ) {
78 $this->sizeLimit = $options['sizeLimit'];
79 }
80 }
81
89 public function addNoWiki( $marker, $value, ?string $extra = null ) {
90 $this->addItem( 'nowiki', $marker, $value, $extra );
91 }
92
97 public function addGeneral( $marker, $value ) {
98 $this->addItem( 'general', $marker, $value );
99 }
100
107 public function addExtTag( $marker, $value ) {
108 $this->addItem( 'exttag', $marker, $value );
109 }
110
120 protected function addItem( $type, $marker, $value, ?string $extra = null ) {
121 if ( !preg_match( $this->regex, $marker, $m ) ) {
122 throw new InvalidArgumentException( "Invalid marker: $marker" );
123 }
124
125 $this->data[$type][$m[1]] = $value;
126 if ( $extra !== null ) {
127 $this->extra[$type][$m[1]] = $extra;
128 }
129 }
130
135 public function unstripGeneral( $text ) {
136 return $this->unstripType( 'general', $text );
137 }
138
143 public function unstripNoWiki( $text ) {
144 return $this->unstripType( 'nowiki', $text );
145 }
146
153 public function replaceNoWikis( string $text, callable $callback ): string {
154 // Shortcut
155 if ( !count( $this->data['nowiki'] ) ) {
156 return $text;
157 }
158
159 $callback = function ( $m ) use ( $callback ) {
160 $marker = $m[1];
161 if ( isset( $this->data['nowiki'][$marker] ) ) {
162 $value = $this->data['nowiki'][$marker];
163 if ( $value instanceof Closure ) {
164 $value = $value();
165 }
166
167 $this->expandSize += strlen( $value );
168 if ( $this->expandSize > $this->sizeLimit ) {
169 return $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit );
170 }
171
172 return $callback( $value );
173 } else {
174 return $m[0];
175 }
176 };
177
178 return preg_replace_callback( $this->regex, $callback, $text );
179 }
180
189 public function split( string $text ): array {
190 $result = [];
191 $pieces = preg_split( $this->regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
192 for ( $i = 0; $i < count( $pieces ); $i++ ) {
193 if ( $i % 2 === 0 ) {
194 $result[] = [
195 'type' => 'string',
196 'content' => $pieces[$i],
197 ];
198 continue;
199 }
200 $marker = $pieces[$i];
201 foreach ( $this->data as $type => $items ) {
202 if ( isset( $items[$marker] ) ) {
203 $value = $items[$marker];
204 $extra = $this->extra[$type][$marker] ?? null;
205 if ( $value instanceof Closure ) {
206 $value = $value();
207 }
208
209 if ( $type === 'exttag' ) {
210 // Catch circular refs / enforce depth limits
211 // similar to code in unstripType().
212 if ( isset( $this->circularRefGuard[$marker] ) ) {
213 $result[] = [
214 'type' => 'string',
215 'content' => $this->getWarning( 'parser-unstrip-loop-warning' )
216 ];
217 continue;
218 }
219
220 if ( $this->depth > $this->highestDepth ) {
221 $this->highestDepth = $this->depth;
222 }
223 if ( $this->depth >= $this->depthLimit ) {
224 $result[] = [
225 'type' => 'string',
226 'content' => $this->getLimitationWarning( 'unstrip-depth', $this->depthLimit )
227 ];
228 continue;
229 }
230
231 // For exttag types, the output size should include the output of
232 // the extension, but don't think unstripType is doing that, and so
233 // we aren't doing that either here. But this is kinda broken.
234 // See T380758#10355050.
235 $this->expandSize += strlen( $value );
236 if ( $this->expandSize > $this->sizeLimit ) {
237 $result[] = [
238 'type' => 'string',
239 'content' => $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit )
240 ];
241 continue;
242 }
243
244 $this->circularRefGuard[$marker] = true;
245 $this->depth++;
246 $result = array_merge( $result, $this->split( $value ) );
247 $this->depth--;
248 unset( $this->circularRefGuard[$marker] );
249 } else {
250 $result[] = [
251 'type' => $type,
252 'content' => $value,
253 'extra' => $extra,
254 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
255 ];
256 }
257 continue 2;
258 }
259 }
260 $result[] = [
261 'type' => 'unknown',
262 'content' => null,
263 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
264 ];
265 }
266 return $result;
267 }
268
273 public function unstripBoth( $text ) {
274 $text = $this->unstripType( 'general', $text );
275 $text = $this->unstripType( 'nowiki', $text );
276 return $text;
277 }
278
284 protected function unstripType( $type, $text ) {
285 // Shortcut
286 if ( !count( $this->data[$type] ) ) {
287 return $text;
288 }
289
290 $callback = function ( $m ) use ( $type ) {
291 $marker = $m[1];
292 if ( isset( $this->data[$type][$marker] ) ) {
293 if ( isset( $this->circularRefGuard[$marker] ) ) {
294 return $this->getWarning( 'parser-unstrip-loop-warning' );
295 }
296
297 if ( $this->depth > $this->highestDepth ) {
298 $this->highestDepth = $this->depth;
299 }
300 if ( $this->depth >= $this->depthLimit ) {
301 return $this->getLimitationWarning( 'unstrip-depth', $this->depthLimit );
302 }
303
304 $value = $this->data[$type][$marker];
305 if ( $value instanceof Closure ) {
306 $value = $value();
307 }
308
309 $this->expandSize += strlen( $value );
310 if ( $this->expandSize > $this->sizeLimit ) {
311 return $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit );
312 }
313
314 $this->circularRefGuard[$marker] = true;
315 $this->depth++;
316 $ret = $this->unstripType( $type, $value );
317 $this->depth--;
318 unset( $this->circularRefGuard[$marker] );
319
320 return $ret;
321 } else {
322 return $m[0];
323 }
324 };
325
326 $text = preg_replace_callback( $this->regex, $callback, $text );
327 return $text;
328 }
329
337 private function getLimitationWarning( $type, $max = '' ) {
338 if ( $this->parser ) {
339 $this->parser->limitationWarn( $type, $max );
340 }
341 return $this->getWarning( "$type-warning", $max );
342 }
343
351 private function getWarning( $message, $max = '' ) {
352 return '<span class="error">' .
353 wfMessage( $message )
354 ->numParams( $max )->inContentLanguage()->text() .
355 '</span>';
356 }
357
364 public function getLimitReport() {
365 return [
366 [ 'limitreport-unstrip-depth',
367 [
368 $this->highestDepth,
369 $this->depthLimit
370 ],
371 ],
372 [ 'limitreport-unstrip-size',
373 [
374 $this->expandSize,
375 $this->sizeLimit
376 ],
377 ]
378 ];
379 }
380
387 public function killMarkers( $text ) {
388 return preg_replace( $this->regex, '', $text );
389 }
390}
391
393class_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
addItem( $type, $marker, $value, ?string $extra=null)
__construct(?Parser $parser=null, $options=[])
replaceNoWikis(string $text, callable $callback)
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' => ....