MediaWiki master
StripState.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Parser;
11
12use Closure;
13use InvalidArgumentException;
14use Wikimedia\Parsoid\Fragments\PFragment;
15
23 protected $data;
25 protected $extra;
27 protected $regex;
28
29 protected ?Parser $parser;
30
34 protected $depth = 0;
36 protected $highestDepth = 0;
38 protected $expandSize = 0;
39
41 protected $depthLimit = 20;
43 protected $sizeLimit = 5_000_000;
44
51 public function __construct( ?Parser $parser = null, $options = [] ) {
52 $this->data = [
53 'nowiki' => [],
54 'general' => []
55 ];
56 $this->extra = [];
57 $this->regex = '/' . Parser::MARKER_PREFIX . "([^\x7f<>&'\"]+)" . Parser::MARKER_SUFFIX . '/';
58 $this->circularRefGuard = [];
59 $this->parser = $parser;
60
61 if ( isset( $options['depthLimit'] ) ) {
62 $this->depthLimit = $options['depthLimit'];
63 }
64 if ( isset( $options['sizeLimit'] ) ) {
65 $this->sizeLimit = $options['sizeLimit'];
66 }
67 }
68
76 public function addNoWiki( $marker, $value, ?string $extra = null ) {
77 $this->addItem( 'nowiki', $marker, $value, $extra );
78 }
79
84 public function addGeneral( $marker, $value ) {
85 $this->addItem( 'general', $marker, $value );
86 }
87
95 public function addExtTag( $marker, $value, $frame ) {
96 $this->addItem( 'exttag', $marker, $value, $frame );
97 }
98
105 public function addParsoidOpaque( $marker, PFragment $extra ) {
106 $this->addItem( 'parsoid', $marker, '<parsoid opaque>', $extra );
107 }
108
118 protected function addItem( $type, $marker, $value, $extra = null ) {
119 if ( !preg_match( $this->regex, $marker, $m ) ) {
120 throw new InvalidArgumentException( "Invalid marker: $marker" );
121 }
122
123 $this->data[$type][$m[1]] = $value;
124 if ( $extra !== null ) {
125 $this->extra[$type][$m[1]] = $extra;
126 }
127 }
128
133 public function unstripGeneral( $text ) {
134 return $this->unstripType( 'general', $text );
135 }
136
141 public function unstripNoWiki( $text ) {
142 return $this->unstripType( 'nowiki', $text );
143 }
144
151 public function replaceNoWikis( string $text, callable $callback ): string {
152 // Shortcut
153 if ( !count( $this->data['nowiki'] ) ) {
154 return $text;
155 }
156
157 $callback = function ( $m ) use ( $callback ) {
158 $marker = $m[1];
159 if ( isset( $this->data['nowiki'][$marker] ) ) {
160 $value = $this->data['nowiki'][$marker];
161 if ( $value instanceof Closure ) {
162 $value = $value();
163 }
164
165 $this->expandSize += strlen( $value );
166 if ( $this->expandSize > $this->sizeLimit ) {
167 return $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit );
168 }
169
170 $extra = $this->extra['nowiki'][$marker] ?? null;
171 return $callback( $value, $extra );
172 } else {
173 return $m[0];
174 }
175 };
176
177 return preg_replace_callback( $this->regex, $callback, $text );
178 }
179
188 public function split( string $text, bool $keepExtTag = false ): array {
189 $result = [];
190 $pieces = preg_split( $this->regex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
191 for ( $i = 0; $i < count( $pieces ); $i++ ) {
192 if ( $i % 2 === 0 ) {
193 $result[] = [
194 'type' => 'string',
195 'content' => $pieces[$i],
196 ];
197 continue;
198 }
199 $marker = $pieces[$i];
200 foreach ( $this->data as $type => $items ) {
201 if ( isset( $items[$marker] ) ) {
202 $value = $items[$marker];
203 $extra = $this->extra[$type][$marker] ?? null;
204 if ( $value instanceof Closure ) {
205 $value = $value();
206 }
207
208 if ( $type === 'exttag' ) {
209 // Catch circular refs / enforce depth limits
210 // similar to code in unstripType().
211 if ( isset( $this->circularRefGuard[$marker] ) ) {
212 $result[] = [
213 'type' => 'string',
214 'content' => $this->getWarning( 'parser-unstrip-loop-warning' )
215 ];
216 continue;
217 }
218
219 if ( $this->depth > $this->highestDepth ) {
220 $this->highestDepth = $this->depth;
221 }
222 if ( $this->depth >= $this->depthLimit ) {
223 $result[] = [
224 'type' => 'string',
225 'content' => $this->getLimitationWarning( 'unstrip-depth', $this->depthLimit )
226 ];
227 continue;
228 }
229
230 // For exttag types, the output size should include the output of
231 // the extension, but don't think unstripType is doing that, and so
232 // we aren't doing that either here. But this is kinda broken.
233 // See T380758#10355050.
234 $this->expandSize += strlen( $value );
235 if ( $this->expandSize > $this->sizeLimit ) {
236 $result[] = [
237 'type' => 'string',
238 'content' => $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit )
239 ];
240 continue;
241 }
242
243 $this->circularRefGuard[$marker] = true;
244 $this->depth++;
245 if ( $keepExtTag ) {
246 $result[] = [
247 'type' => 'exttag',
248 'content' => $value,
249 'extra' => $extra,
250 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
251 ];
252 } else {
253 $result = array_merge( $result, $this->split( $value ) );
254 }
255 $this->depth--;
256 unset( $this->circularRefGuard[$marker] );
257 } else {
258 $result[] = [
259 'type' => $type,
260 'content' => $value,
261 'extra' => $extra,
262 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
263 ];
264 }
265 continue 2;
266 }
267 }
268 $result[] = [
269 'type' => 'unknown',
270 'content' => null,
271 'marker' => Parser::MARKER_PREFIX . $marker . Parser::MARKER_SUFFIX,
272 ];
273 }
274 return $result;
275 }
276
281 public function unstripBoth( $text ) {
282 $text = $this->unstripType( 'general', $text );
283 $text = $this->unstripType( 'nowiki', $text );
284 return $text;
285 }
286
292 protected function unstripType( $type, $text ) {
293 // Shortcut
294 if ( !count( $this->data[$type] ) ) {
295 return $text;
296 }
297
298 $callback = function ( $m ) use ( $type ) {
299 $marker = $m[1];
300 if ( isset( $this->data[$type][$marker] ) ) {
301 if ( isset( $this->circularRefGuard[$marker] ) ) {
302 return $this->getWarning( 'parser-unstrip-loop-warning' );
303 }
304
305 if ( $this->depth > $this->highestDepth ) {
306 $this->highestDepth = $this->depth;
307 }
308 if ( $this->depth >= $this->depthLimit ) {
309 return $this->getLimitationWarning( 'unstrip-depth', $this->depthLimit );
310 }
311
312 $value = $this->data[$type][$marker];
313 if ( $value instanceof Closure ) {
314 $value = $value();
315 }
316
317 $this->expandSize += strlen( $value );
318 if ( $this->expandSize > $this->sizeLimit ) {
319 return $this->getLimitationWarning( 'unstrip-size', $this->sizeLimit );
320 }
321
322 $this->circularRefGuard[$marker] = true;
323 $this->depth++;
324 $ret = $this->unstripType( $type, $value );
325 $this->depth--;
326 unset( $this->circularRefGuard[$marker] );
327
328 return $ret;
329 } else {
330 return $m[0];
331 }
332 };
333
334 $text = preg_replace_callback( $this->regex, $callback, $text );
335 return $text;
336 }
337
345 private function getLimitationWarning( $type, $max = '' ) {
346 if ( $this->parser ) {
347 $this->parser->limitationWarn( $type, $max );
348 }
349 return $this->getWarning( "$type-warning", $max );
350 }
351
359 private function getWarning( $message, $max = '' ) {
360 return '<span class="error">' .
361 wfMessage( $message )
362 ->numParams( $max )->inContentLanguage()->text() .
363 '</span>';
364 }
365
372 public function getLimitReport() {
373 return [
374 [ 'limitreport-unstrip-depth',
375 [
376 $this->highestDepth,
377 $this->depthLimit
378 ],
379 ],
380 [ 'limitreport-unstrip-size',
381 [
382 $this->expandSize,
383 $this->sizeLimit
384 ],
385 ]
386 ];
387 }
388
395 public function killMarkers( $text ) {
396 return preg_replace( $this->regex, '', $text );
397 }
398}
399
401class_alias( StripState::class, 'StripState' );
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:138
addExtTag( $marker, $value, $frame)
__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)
split(string $text, bool $keepExtTag=false)
Split the given text by strip markers, returning an array of [ 'type' => ..., 'content' => ....