Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/parser/BlockLevelPass.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This is the part of the wikitext parser which handles automatic paragraphs
5
 * and conversion of start-of-line prefixes to HTML lists.
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along
18
 * with this program; if not, write to the Free Software Foundation, Inc.,
19
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
 * http://www.gnu.org/copyleft/gpl.html
21
 *
22
 * @file
23
 * @ingroup Parser
24
 */
25
class BlockLevelPass {
0 ignored issues
show
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
26
	private $DTopen = false;
27
	private $inPre = false;
28
	private $lastSection = '';
29
	private $linestart;
0 ignored issues
show
The property $linestart is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
30
	private $text;
31
32
	# State constants for the definition list colon extraction
33
	const COLON_STATE_TEXT = 0;
34
	const COLON_STATE_TAG = 1;
35
	const COLON_STATE_TAGSTART = 2;
36
	const COLON_STATE_CLOSETAG = 3;
37
	const COLON_STATE_TAGSLASH = 4;
38
	const COLON_STATE_COMMENT = 5;
39
	const COLON_STATE_COMMENTDASH = 6;
40
	const COLON_STATE_COMMENTDASHDASH = 7;
41
42
	/**
43
	 * Make lists from lines starting with ':', '*', '#', etc.
44
	 *
45
	 * @param string $text
46
	 * @param bool $lineStart Whether or not this is at the start of a line.
47
	 * @return string The lists rendered as HTML
48
	 */
49
	public static function doBlockLevels( $text, $lineStart ) {
50
		$pass = new self( $text, $lineStart );
51
		return $pass->execute();
52
	}
53
54
	/**
55
	 * Private constructor
56
	 */
57
	private function __construct( $text, $lineStart ) {
58
		$this->text = $text;
59
		$this->lineStart = $lineStart;
0 ignored issues
show
The property lineStart does not seem to exist. Did you mean linestart?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
60
	}
61
62
	/**
63
	 * If a pre or p is open, return the corresponding close tag and update
64
	 * the state. If no tag is open, return an empty string.
65
	 * @return string
66
	 */
67
	private function closeParagraph() {
68
		$result = '';
69
		if ( $this->lastSection !== '' ) {
70
			$result = '</' . $this->lastSection . ">\n";
71
		}
72
		$this->inPre = false;
73
		$this->lastSection = '';
74
		return $result;
75
	}
76
77
	/**
78
	 * getCommon() returns the length of the longest common substring
79
	 * of both arguments, starting at the beginning of both.
80
	 *
81
	 * @param string $st1
82
	 * @param string $st2
83
	 *
84
	 * @return int
85
	 */
86
	private function getCommon( $st1, $st2 ) {
87
		$shorter = min( strlen( $st1 ), strlen( $st2 ) );
88
89
		for ( $i = 0; $i < $shorter; ++$i ) {
90
			if ( $st1[$i] !== $st2[$i] ) {
91
				break;
92
			}
93
		}
94
		return $i;
95
	}
96
97
	/**
98
	 * Open the list item element identified by the prefix character.
99
	 *
100
	 * @param string $char
101
	 *
102
	 * @return string
103
	 */
104
	private function openList( $char ) {
105
		$result = $this->closeParagraph();
106
107
		if ( '*' === $char ) {
108
			$result .= "<ul><li>";
109
		} elseif ( '#' === $char ) {
110
			$result .= "<ol><li>";
111
		} elseif ( ':' === $char ) {
112
			$result .= "<dl><dd>";
113
		} elseif ( ';' === $char ) {
114
			$result .= "<dl><dt>";
115
			$this->DTopen = true;
116
		} else {
117
			$result = '<!-- ERR 1 -->';
118
		}
119
120
		return $result;
121
	}
122
123
	/**
124
	 * Close the current list item and open the next one.
125
	 * @param string $char
126
	 *
127
	 * @return string
128
	 */
129
	private function nextItem( $char ) {
130
		if ( '*' === $char || '#' === $char ) {
131
			return "</li>\n<li>";
132
		} elseif ( ':' === $char || ';' === $char ) {
133
			$close = "</dd>\n";
134
			if ( $this->DTopen ) {
135
				$close = "</dt>\n";
136
			}
137
			if ( ';' === $char ) {
138
				$this->DTopen = true;
139
				return $close . '<dt>';
140
			} else {
141
				$this->DTopen = false;
142
				return $close . '<dd>';
143
			}
144
		}
145
		return '<!-- ERR 2 -->';
146
	}
147
148
	/**
149
	 * Close the current list item identified by the prefix character.
150
	 * @param string $char
151
	 *
152
	 * @return string
153
	 */
154
	private function closeList( $char ) {
155
		if ( '*' === $char ) {
156
			$text = "</li></ul>";
157
		} elseif ( '#' === $char ) {
158
			$text = "</li></ol>";
159
		} elseif ( ':' === $char ) {
160
			if ( $this->DTopen ) {
161
				$this->DTopen = false;
162
				$text = "</dt></dl>";
163
			} else {
164
				$text = "</dd></dl>";
165
			}
166
		} else {
167
			return '<!-- ERR 3 -->';
168
		}
169
		return $text;
170
	}
171
172
	/**
173
	 * Execute the pass.
174
	 * @return string
175
	 */
176
	private function execute() {
177
		$text = $this->text;
178
		# Parsing through the text line by line.  The main thing
179
		# happening here is handling of block-level elements p, pre,
180
		# and making lists from lines starting with * # : etc.
181
		$textLines = StringUtils::explode( "\n", $text );
182
183
		$lastPrefix = $output = '';
184
		$this->DTopen = $inBlockElem = false;
185
		$prefixLength = 0;
186
		$pendingPTag = false;
187
		$inBlockquote = false;
188
189
		foreach ( $textLines as $inputLine ) {
190
			# Fix up $lineStart
191
			if ( !$this->lineStart ) {
0 ignored issues
show
The property lineStart does not seem to exist. Did you mean linestart?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
192
				$output .= $inputLine;
193
				$this->lineStart = true;
0 ignored issues
show
The property lineStart does not seem to exist. Did you mean linestart?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
194
				continue;
195
			}
196
			# * = ul
197
			# # = ol
198
			# ; = dt
199
			# : = dd
200
201
			$lastPrefixLength = strlen( $lastPrefix );
202
			$preCloseMatch = preg_match( '/<\\/pre/i', $inputLine );
203
			$preOpenMatch = preg_match( '/<pre/i', $inputLine );
204
			# If not in a <pre> element, scan for and figure out what prefixes are there.
205
			if ( !$this->inPre ) {
206
				# Multiple prefixes may abut each other for nested lists.
207
				$prefixLength = strspn( $inputLine, '*#:;' );
208
				$prefix = substr( $inputLine, 0, $prefixLength );
209
210
				# eh?
211
				# ; and : are both from definition-lists, so they're equivalent
212
				#  for the purposes of determining whether or not we need to open/close
213
				#  elements.
214
				$prefix2 = str_replace( ';', ':', $prefix );
215
				$t = substr( $inputLine, $prefixLength );
216
				$this->inPre = (bool)$preOpenMatch;
217
			} else {
218
				# Don't interpret any other prefixes in preformatted text
219
				$prefixLength = 0;
220
				$prefix = $prefix2 = '';
221
				$t = $inputLine;
222
			}
223
224
			# List generation
225
			if ( $prefixLength && $lastPrefix === $prefix2 ) {
226
				# Same as the last item, so no need to deal with nesting or opening stuff
227
				$output .= $this->nextItem( substr( $prefix, -1 ) );
228
				$pendingPTag = false;
229
230
				if ( substr( $prefix, -1 ) === ';' ) {
231
					# The one nasty exception: definition lists work like this:
232
					# ; title : definition text
233
					# So we check for : in the remainder text to split up the
234
					# title and definition, without b0rking links.
235
					$term = $t2 = '';
236 View Code Duplication
					if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
237
						$t = $t2;
238
						$output .= $term . $this->nextItem( ':' );
239
					}
240
				}
241
			} elseif ( $prefixLength || $lastPrefixLength ) {
242
				# We need to open or close prefixes, or both.
243
244
				# Either open or close a level...
245
				$commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
246
				$pendingPTag = false;
247
248
				# Close all the prefixes which aren't shared.
249
				while ( $commonPrefixLength < $lastPrefixLength ) {
250
					$output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
251
					--$lastPrefixLength;
252
				}
253
254
				# Continue the current prefix if appropriate.
255
				if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
256
					$output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
257
				}
258
259
				# Open prefixes where appropriate.
260
				if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
261
					$output .= "\n";
262
				}
263
				while ( $prefixLength > $commonPrefixLength ) {
264
					$char = substr( $prefix, $commonPrefixLength, 1 );
265
					$output .= $this->openList( $char );
266
267 View Code Duplication
					if ( ';' === $char ) {
268
						# @todo FIXME: This is dupe of code above
269
						if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
270
							$t = $t2;
271
							$output .= $term . $this->nextItem( ':' );
272
						}
273
					}
274
					++$commonPrefixLength;
275
				}
276
				if ( !$prefixLength && $lastPrefix ) {
277
					$output .= "\n";
278
				}
279
				$lastPrefix = $prefix2;
280
			}
281
282
			# If we have no prefixes, go to paragraph mode.
283
			if ( 0 == $prefixLength ) {
284
				# No prefix (not in list)--go to paragraph mode
285
				# @todo consider using a stack for nestable elements like span, table and div
286
				$openMatch = preg_match(
287
					'/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
288
						. '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS',
289
					$t
290
				);
291
				$closeMatch = preg_match(
292
					'/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
293
						. '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
294
						. Parser::MARKER_PREFIX
295
						. '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS',
296
					$t
297
				);
298
299
				if ( $openMatch || $closeMatch ) {
300
					$pendingPTag = false;
301
					# @todo bug 5718: paragraph closed
302
					$output .= $this->closeParagraph();
303
					if ( $preOpenMatch && !$preCloseMatch ) {
304
						$this->inPre = true;
305
					}
306
					$bqOffset = 0;
307
					while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t,
308
						$bqMatch, PREG_OFFSET_CAPTURE, $bqOffset )
309
					) {
310
						$inBlockquote = !$bqMatch[1][0]; // is this a close tag?
311
						$bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
312
					}
313
					$inBlockElem = !$closeMatch;
314
				} elseif ( !$inBlockElem && !$this->inPre ) {
315
					if ( ' ' == substr( $t, 0, 1 )
316
						&& ( $this->lastSection === 'pre' || trim( $t ) != '' )
317
						&& !$inBlockquote
318
					) {
319
						# pre
320 View Code Duplication
						if ( $this->lastSection !== 'pre' ) {
321
							$pendingPTag = false;
322
							$output .= $this->closeParagraph() . '<pre>';
323
							$this->lastSection = 'pre';
324
						}
325
						$t = substr( $t, 1 );
326
					} else {
327
						# paragraph
328
						if ( trim( $t ) === '' ) {
329
							if ( $pendingPTag ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pendingPTag of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
330
								$output .= $pendingPTag . '<br />';
331
								$pendingPTag = false;
332
								$this->lastSection = 'p';
333
							} else {
334 View Code Duplication
								if ( $this->lastSection !== 'p' ) {
335
									$output .= $this->closeParagraph();
336
									$this->lastSection = '';
337
									$pendingPTag = '<p>';
338
								} else {
339
									$pendingPTag = '</p><p>';
340
								}
341
							}
342
						} else {
343
							if ( $pendingPTag ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pendingPTag of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
344
								$output .= $pendingPTag;
345
								$pendingPTag = false;
346
								$this->lastSection = 'p';
347
							} elseif ( $this->lastSection !== 'p' ) {
348
								$output .= $this->closeParagraph() . '<p>';
349
								$this->lastSection = 'p';
350
							}
351
						}
352
					}
353
				}
354
			}
355
			# somewhere above we forget to get out of pre block (bug 785)
356
			if ( $preCloseMatch && $this->inPre ) {
357
				$this->inPre = false;
358
			}
359
			if ( $pendingPTag === false ) {
360
				$output .= $t;
361
				if ( $prefixLength === 0 ) {
362
					$output .= "\n";
363
				}
364
			}
365
		}
366
		while ( $prefixLength ) {
367
			$output .= $this->closeList( $prefix2[$prefixLength - 1] );
0 ignored issues
show
The variable $prefix2 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
368
			--$prefixLength;
369
			if ( !$prefixLength ) {
370
				$output .= "\n";
371
			}
372
		}
373
		if ( $this->lastSection !== '' ) {
374
			$output .= '</' . $this->lastSection . '>';
375
			$this->lastSection = '';
376
		}
377
378
		return $output;
379
	}
380
381
	/**
382
	 * Split up a string on ':', ignoring any occurrences inside tags
383
	 * to prevent illegal overlapping.
384
	 *
385
	 * @param string $str The string to split
386
	 * @param string &$before Set to everything before the ':'
387
	 * @param string &$after Set to everything after the ':'
388
	 * @throws MWException
389
	 * @return string The position of the ':', or false if none found
390
	 */
391
	private function findColonNoLinks( $str, &$before, &$after ) {
392
		$colonPos = strpos( $str, ':' );
393
		if ( $colonPos === false ) {
394
			# Nothing to find!
395
			return false;
396
		}
397
398
		$ltPos = strpos( $str, '<' );
399 View Code Duplication
		if ( $ltPos === false || $ltPos > $colonPos ) {
400
			# Easy; no tag nesting to worry about
401
			$before = substr( $str, 0, $colonPos );
402
			$after = substr( $str, $colonPos + 1 );
403
			return $colonPos;
404
		}
405
406
		# Ugly state machine to walk through avoiding tags.
407
		$state = self::COLON_STATE_TEXT;
408
		$level = 0;
409
		$len = strlen( $str );
410
		for ( $i = 0; $i < $len; $i++ ) {
411
			$c = $str[$i];
412
413
			switch ( $state ) {
414
			case self::COLON_STATE_TEXT:
415
				switch ( $c ) {
416
				case "<":
417
					# Could be either a <start> tag or an </end> tag
418
					$state = self::COLON_STATE_TAGSTART;
419
					break;
420
				case ":":
421 View Code Duplication
					if ( $level === 0 ) {
422
						# We found it!
423
						$before = substr( $str, 0, $i );
424
						$after = substr( $str, $i + 1 );
425
						return $i;
426
					}
427
					# Embedded in a tag; don't break it.
428
					break;
429
				default:
430
					# Skip ahead looking for something interesting
431
					$colonPos = strpos( $str, ':', $i );
432
					if ( $colonPos === false ) {
433
						# Nothing else interesting
434
						return false;
435
					}
436
					$ltPos = strpos( $str, '<', $i );
437 View Code Duplication
					if ( $level === 0 ) {
438
						if ( $ltPos === false || $colonPos < $ltPos ) {
439
							# We found it!
440
							$before = substr( $str, 0, $colonPos );
441
							$after = substr( $str, $colonPos + 1 );
442
							return $i;
443
						}
444
					}
445
					if ( $ltPos === false ) {
446
						# Nothing else interesting to find; abort!
447
						# We're nested, but there's no close tags left. Abort!
448
						break 2;
449
					}
450
					# Skip ahead to next tag start
451
					$i = $ltPos;
452
					$state = self::COLON_STATE_TAGSTART;
453
				}
454
				break;
455
			case self::COLON_STATE_TAG:
456
				# In a <tag>
457
				switch ( $c ) {
458
				case ">":
459
					$level++;
460
					$state = self::COLON_STATE_TEXT;
461
					break;
462
				case "/":
463
					# Slash may be followed by >?
464
					$state = self::COLON_STATE_TAGSLASH;
465
					break;
466
				default:
467
					# ignore
468
				}
469
				break;
470
			case self::COLON_STATE_TAGSTART:
471
				switch ( $c ) {
472
				case "/":
473
					$state = self::COLON_STATE_CLOSETAG;
474
					break;
475
				case "!":
476
					$state = self::COLON_STATE_COMMENT;
477
					break;
478
				case ">":
479
					# Illegal early close? This shouldn't happen D:
480
					$state = self::COLON_STATE_TEXT;
481
					break;
482
				default:
483
					$state = self::COLON_STATE_TAG;
484
				}
485
				break;
486
			case self::COLON_STATE_CLOSETAG:
487
				# In a </tag>
488
				if ( $c === ">" ) {
489
					$level--;
490
					if ( $level < 0 ) {
491
						wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
492
						return false;
493
					}
494
					$state = self::COLON_STATE_TEXT;
495
				}
496
				break;
497 View Code Duplication
			case self::COLON_STATE_TAGSLASH:
498
				if ( $c === ">" ) {
499
					# Yes, a self-closed tag <blah/>
500
					$state = self::COLON_STATE_TEXT;
501
				} else {
502
					# Probably we're jumping the gun, and this is an attribute
503
					$state = self::COLON_STATE_TAG;
504
				}
505
				break;
506
			case self::COLON_STATE_COMMENT:
507
				if ( $c === "-" ) {
508
					$state = self::COLON_STATE_COMMENTDASH;
509
				}
510
				break;
511 View Code Duplication
			case self::COLON_STATE_COMMENTDASH:
512
				if ( $c === "-" ) {
513
					$state = self::COLON_STATE_COMMENTDASHDASH;
514
				} else {
515
					$state = self::COLON_STATE_COMMENT;
516
				}
517
				break;
518 View Code Duplication
			case self::COLON_STATE_COMMENTDASHDASH:
519
				if ( $c === ">" ) {
520
					$state = self::COLON_STATE_TEXT;
521
				} else {
522
					$state = self::COLON_STATE_COMMENT;
523
				}
524
				break;
525
			default:
526
				throw new MWException( "State machine error in " . __METHOD__ );
527
			}
528
		}
529
		if ( $level > 0 ) {
530
			wfDebug( __METHOD__ . ": Invalid input; not enough close tags (level $level, state $state)\n" );
531
			return false;
532
		}
533
		return false;
534
	}
535
}
536