wikimedia /
mediawiki
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Preprocessor using PHP's dom extension |
||
| 4 | * |
||
| 5 | * This program is free software; you can redistribute it and/or modify |
||
| 6 | * it under the terms of the GNU General Public License as published by |
||
| 7 | * the Free Software Foundation; either version 2 of the License, or |
||
| 8 | * (at your option) any later version. |
||
| 9 | * |
||
| 10 | * This program is distributed in the hope that it will be useful, |
||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 13 | * GNU General Public License for more details. |
||
| 14 | * |
||
| 15 | * You should have received a copy of the GNU General Public License along |
||
| 16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
| 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 18 | * http://www.gnu.org/copyleft/gpl.html |
||
| 19 | * |
||
| 20 | * @file |
||
| 21 | * @ingroup Parser |
||
| 22 | */ |
||
| 23 | |||
| 24 | /** |
||
| 25 | * @ingroup Parser |
||
| 26 | */ |
||
| 27 | // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps |
||
| 28 | class Preprocessor_DOM extends Preprocessor { |
||
| 29 | // @codingStandardsIgnoreEnd |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @var Parser |
||
| 33 | */ |
||
| 34 | public $parser; |
||
| 35 | |||
| 36 | public $memoryLimit; |
||
| 37 | |||
| 38 | const CACHE_PREFIX = 'preprocess-xml'; |
||
| 39 | |||
| 40 | public function __construct( $parser ) { |
||
| 41 | $this->parser = $parser; |
||
| 42 | $mem = ini_get( 'memory_limit' ); |
||
| 43 | $this->memoryLimit = false; |
||
| 44 | if ( strval( $mem ) !== '' && $mem != -1 ) { |
||
| 45 | if ( preg_match( '/^\d+$/', $mem ) ) { |
||
| 46 | $this->memoryLimit = $mem; |
||
| 47 | } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) { |
||
| 48 | $this->memoryLimit = $m[1] * 1048576; |
||
| 49 | } |
||
| 50 | } |
||
| 51 | } |
||
| 52 | |||
| 53 | /** |
||
| 54 | * @return PPFrame_DOM |
||
| 55 | */ |
||
| 56 | public function newFrame() { |
||
| 57 | return new PPFrame_DOM( $this ); |
||
| 58 | } |
||
| 59 | |||
| 60 | /** |
||
| 61 | * @param array $args |
||
| 62 | * @return PPCustomFrame_DOM |
||
| 63 | */ |
||
| 64 | public function newCustomFrame( $args ) { |
||
| 65 | return new PPCustomFrame_DOM( $this, $args ); |
||
| 66 | } |
||
| 67 | |||
| 68 | /** |
||
| 69 | * @param array $values |
||
| 70 | * @return PPNode_DOM |
||
| 71 | * @throws MWException |
||
| 72 | */ |
||
| 73 | public function newPartNodeArray( $values ) { |
||
| 74 | // NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais) |
||
| 75 | $xml = "<list>"; |
||
| 76 | |||
| 77 | foreach ( $values as $k => $val ) { |
||
| 78 | if ( is_int( $k ) ) { |
||
| 79 | $xml .= "<part><name index=\"$k\"/><value>" |
||
| 80 | . htmlspecialchars( $val ) . "</value></part>"; |
||
| 81 | } else { |
||
| 82 | $xml .= "<part><name>" . htmlspecialchars( $k ) |
||
| 83 | . "</name>=<value>" . htmlspecialchars( $val ) . "</value></part>"; |
||
| 84 | } |
||
| 85 | } |
||
| 86 | |||
| 87 | $xml .= "</list>"; |
||
| 88 | |||
| 89 | $dom = new DOMDocument(); |
||
| 90 | MediaWiki\suppressWarnings(); |
||
| 91 | $result = $dom->loadXML( $xml ); |
||
| 92 | MediaWiki\restoreWarnings(); |
||
| 93 | View Code Duplication | if ( !$result ) { |
|
| 94 | // Try running the XML through UtfNormal to get rid of invalid characters |
||
| 95 | $xml = UtfNormal\Validator::cleanUp( $xml ); |
||
| 96 | // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 |
||
| 97 | // don't barf when the XML is >256 levels deep |
||
| 98 | $result = $dom->loadXML( $xml, 1 << 19 ); |
||
| 99 | } |
||
| 100 | |||
| 101 | if ( !$result ) { |
||
| 102 | throw new MWException( 'Parameters passed to ' . __METHOD__ . ' result in invalid XML' ); |
||
| 103 | } |
||
| 104 | |||
| 105 | $root = $dom->documentElement; |
||
| 106 | $node = new PPNode_DOM( $root->childNodes ); |
||
| 107 | return $node; |
||
| 108 | } |
||
| 109 | |||
| 110 | /** |
||
| 111 | * @throws MWException |
||
| 112 | * @return bool |
||
| 113 | */ |
||
| 114 | public function memCheck() { |
||
| 115 | if ( $this->memoryLimit === false ) { |
||
| 116 | return true; |
||
| 117 | } |
||
| 118 | $usage = memory_get_usage(); |
||
| 119 | if ( $usage > $this->memoryLimit * 0.9 ) { |
||
| 120 | $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 ); |
||
| 121 | throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" ); |
||
| 122 | } |
||
| 123 | return $usage <= $this->memoryLimit * 0.8; |
||
| 124 | } |
||
| 125 | |||
| 126 | /** |
||
| 127 | * Preprocess some wikitext and return the document tree. |
||
| 128 | * This is the ghost of Parser::replace_variables(). |
||
| 129 | * |
||
| 130 | * @param string $text The text to parse |
||
| 131 | * @param int $flags Bitwise combination of: |
||
| 132 | * Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" |
||
| 133 | * as if the text is being included. Default |
||
| 134 | * is to assume a direct page view. |
||
| 135 | * |
||
| 136 | * The generated DOM tree must depend only on the input text and the flags. |
||
| 137 | * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. |
||
| 138 | * |
||
| 139 | * Any flag added to the $flags parameter here, or any other parameter liable to cause a |
||
| 140 | * change in the DOM tree for a given text, must be passed through the section identifier |
||
| 141 | * in the section edit link and thus back to extractSections(). |
||
| 142 | * |
||
| 143 | * The output of this function is currently only cached in process memory, but a persistent |
||
| 144 | * cache may be implemented at a later date which takes further advantage of these strict |
||
| 145 | * dependency requirements. |
||
| 146 | * |
||
| 147 | * @throws MWException |
||
| 148 | * @return PPNode_DOM |
||
| 149 | */ |
||
| 150 | public function preprocessToObj( $text, $flags = 0 ) { |
||
| 151 | |||
| 152 | $xml = $this->cacheGetTree( $text, $flags ); |
||
| 153 | if ( $xml === false ) { |
||
| 154 | $xml = $this->preprocessToXml( $text, $flags ); |
||
| 155 | $this->cacheSetTree( $text, $flags, $xml ); |
||
| 156 | } |
||
| 157 | |||
| 158 | // Fail if the number of elements exceeds acceptable limits |
||
| 159 | // Do not attempt to generate the DOM |
||
| 160 | $this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' ); |
||
| 161 | $max = $this->parser->mOptions->getMaxGeneratedPPNodeCount(); |
||
| 162 | if ( $this->parser->mGeneratedPPNodeCount > $max ) { |
||
| 163 | // if ( $cacheable ) { ... } |
||
| 164 | throw new MWException( __METHOD__ . ': generated node count limit exceeded' ); |
||
| 165 | } |
||
| 166 | |||
| 167 | $dom = new DOMDocument; |
||
| 168 | MediaWiki\suppressWarnings(); |
||
| 169 | $result = $dom->loadXML( $xml ); |
||
| 170 | MediaWiki\restoreWarnings(); |
||
| 171 | View Code Duplication | if ( !$result ) { |
|
| 172 | // Try running the XML through UtfNormal to get rid of invalid characters |
||
| 173 | $xml = UtfNormal\Validator::cleanUp( $xml ); |
||
| 174 | // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 |
||
| 175 | // don't barf when the XML is >256 levels deep. |
||
| 176 | $result = $dom->loadXML( $xml, 1 << 19 ); |
||
| 177 | } |
||
| 178 | if ( $result ) { |
||
| 179 | $obj = new PPNode_DOM( $dom->documentElement ); |
||
| 180 | } |
||
| 181 | |||
| 182 | // if ( $cacheable ) { ... } |
||
| 183 | |||
| 184 | if ( !$result ) { |
||
| 185 | throw new MWException( __METHOD__ . ' generated invalid XML' ); |
||
| 186 | } |
||
| 187 | return $obj; |
||
|
0 ignored issues
–
show
|
|||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * @param string $text |
||
| 192 | * @param int $flags |
||
| 193 | * @return string |
||
| 194 | */ |
||
| 195 | public function preprocessToXml( $text, $flags = 0 ) { |
||
| 196 | $forInclusion = $flags & Parser::PTD_FOR_INCLUSION; |
||
| 197 | |||
| 198 | $xmlishElements = $this->parser->getStripList(); |
||
| 199 | $xmlishAllowMissingEndTag = [ 'includeonly', 'noinclude', 'onlyinclude' ]; |
||
| 200 | $enableOnlyinclude = false; |
||
| 201 | View Code Duplication | if ( $forInclusion ) { |
|
| 202 | $ignoredTags = [ 'includeonly', '/includeonly' ]; |
||
| 203 | $ignoredElements = [ 'noinclude' ]; |
||
| 204 | $xmlishElements[] = 'noinclude'; |
||
| 205 | if ( strpos( $text, '<onlyinclude>' ) !== false |
||
| 206 | && strpos( $text, '</onlyinclude>' ) !== false |
||
| 207 | ) { |
||
| 208 | $enableOnlyinclude = true; |
||
| 209 | } |
||
| 210 | } else { |
||
| 211 | $ignoredTags = [ 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ]; |
||
| 212 | $ignoredElements = [ 'includeonly' ]; |
||
| 213 | $xmlishElements[] = 'includeonly'; |
||
| 214 | } |
||
| 215 | $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) ); |
||
| 216 | |||
| 217 | // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset |
||
| 218 | $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA"; |
||
| 219 | |||
| 220 | $stack = new PPDStack; |
||
| 221 | |||
| 222 | $searchBase = "[{<\n"; # } |
||
| 223 | // For fast reverse searches |
||
| 224 | $revText = strrev( $text ); |
||
| 225 | $lengthText = strlen( $text ); |
||
| 226 | |||
| 227 | // Input pointer, starts out pointing to a pseudo-newline before the start |
||
| 228 | $i = 0; |
||
| 229 | // Current accumulator |
||
| 230 | $accum =& $stack->getAccum(); |
||
| 231 | $accum = '<root>'; |
||
| 232 | // True to find equals signs in arguments |
||
| 233 | $findEquals = false; |
||
| 234 | // True to take notice of pipe characters |
||
| 235 | $findPipe = false; |
||
| 236 | $headingIndex = 1; |
||
| 237 | // True if $i is inside a possible heading |
||
| 238 | $inHeading = false; |
||
| 239 | // True if there are no more greater-than (>) signs right of $i |
||
| 240 | $noMoreGT = false; |
||
| 241 | // Map of tag name => true if there are no more closing tags of given type right of $i |
||
| 242 | $noMoreClosingTag = []; |
||
| 243 | // True to ignore all input up to the next <onlyinclude> |
||
| 244 | $findOnlyinclude = $enableOnlyinclude; |
||
| 245 | // Do a line-start run without outputting an LF character |
||
| 246 | $fakeLineStart = true; |
||
| 247 | |||
| 248 | while ( true ) { |
||
| 249 | // $this->memCheck(); |
||
| 250 | |||
| 251 | if ( $findOnlyinclude ) { |
||
| 252 | // Ignore all input up to the next <onlyinclude> |
||
| 253 | $startPos = strpos( $text, '<onlyinclude>', $i ); |
||
| 254 | if ( $startPos === false ) { |
||
| 255 | // Ignored section runs to the end |
||
| 256 | $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i ) ) . '</ignore>'; |
||
| 257 | break; |
||
| 258 | } |
||
| 259 | $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end |
||
| 260 | $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i ) ) . '</ignore>'; |
||
| 261 | $i = $tagEndPos; |
||
| 262 | $findOnlyinclude = false; |
||
| 263 | } |
||
| 264 | |||
| 265 | if ( $fakeLineStart ) { |
||
| 266 | $found = 'line-start'; |
||
| 267 | $curChar = ''; |
||
| 268 | View Code Duplication | } else { |
|
| 269 | # Find next opening brace, closing brace or pipe |
||
| 270 | $search = $searchBase; |
||
| 271 | if ( $stack->top === false ) { |
||
| 272 | $currentClosing = ''; |
||
| 273 | } else { |
||
| 274 | $currentClosing = $stack->top->close; |
||
|
0 ignored issues
–
show
The property
close does not seem to exist in PPDStack.
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...
|
|||
| 275 | $search .= $currentClosing; |
||
| 276 | } |
||
| 277 | if ( $findPipe ) { |
||
| 278 | $search .= '|'; |
||
| 279 | } |
||
| 280 | if ( $findEquals ) { |
||
| 281 | // First equals will be for the template |
||
| 282 | $search .= '='; |
||
| 283 | } |
||
| 284 | $rule = null; |
||
| 285 | # Output literal section, advance input counter |
||
| 286 | $literalLength = strcspn( $text, $search, $i ); |
||
| 287 | if ( $literalLength > 0 ) { |
||
| 288 | $accum .= htmlspecialchars( substr( $text, $i, $literalLength ) ); |
||
| 289 | $i += $literalLength; |
||
| 290 | } |
||
| 291 | if ( $i >= $lengthText ) { |
||
| 292 | if ( $currentClosing == "\n" ) { |
||
| 293 | // Do a past-the-end run to finish off the heading |
||
| 294 | $curChar = ''; |
||
| 295 | $found = 'line-end'; |
||
| 296 | } else { |
||
| 297 | # All done |
||
| 298 | break; |
||
| 299 | } |
||
| 300 | } else { |
||
| 301 | $curChar = $text[$i]; |
||
| 302 | if ( $curChar == '|' ) { |
||
| 303 | $found = 'pipe'; |
||
| 304 | } elseif ( $curChar == '=' ) { |
||
| 305 | $found = 'equals'; |
||
| 306 | } elseif ( $curChar == '<' ) { |
||
| 307 | $found = 'angle'; |
||
| 308 | } elseif ( $curChar == "\n" ) { |
||
| 309 | if ( $inHeading ) { |
||
| 310 | $found = 'line-end'; |
||
| 311 | } else { |
||
| 312 | $found = 'line-start'; |
||
| 313 | } |
||
| 314 | } elseif ( $curChar == $currentClosing ) { |
||
| 315 | $found = 'close'; |
||
| 316 | } elseif ( isset( $this->rules[$curChar] ) ) { |
||
| 317 | $found = 'open'; |
||
| 318 | $rule = $this->rules[$curChar]; |
||
| 319 | } else { |
||
| 320 | # Some versions of PHP have a strcspn which stops on null characters |
||
| 321 | # Ignore and continue |
||
| 322 | ++$i; |
||
| 323 | continue; |
||
| 324 | } |
||
| 325 | } |
||
| 326 | } |
||
| 327 | |||
| 328 | if ( $found == 'angle' ) { |
||
| 329 | $matches = false; |
||
| 330 | // Handle </onlyinclude> |
||
| 331 | View Code Duplication | if ( $enableOnlyinclude |
|
| 332 | && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' |
||
| 333 | ) { |
||
| 334 | $findOnlyinclude = true; |
||
| 335 | continue; |
||
| 336 | } |
||
| 337 | |||
| 338 | // Determine element name |
||
| 339 | View Code Duplication | if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) { |
|
| 340 | // Element name missing or not listed |
||
| 341 | $accum .= '<'; |
||
| 342 | ++$i; |
||
| 343 | continue; |
||
| 344 | } |
||
| 345 | // Handle comments |
||
| 346 | if ( isset( $matches[2] ) && $matches[2] == '!--' ) { |
||
| 347 | |||
| 348 | // To avoid leaving blank lines, when a sequence of |
||
| 349 | // space-separated comments is both preceded and followed by |
||
| 350 | // a newline (ignoring spaces), then |
||
| 351 | // trim leading and trailing spaces and the trailing newline. |
||
| 352 | |||
| 353 | // Find the end |
||
| 354 | $endPos = strpos( $text, '-->', $i + 4 ); |
||
| 355 | if ( $endPos === false ) { |
||
| 356 | // Unclosed comment in input, runs to end |
||
| 357 | $inner = substr( $text, $i ); |
||
| 358 | $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>'; |
||
| 359 | $i = $lengthText; |
||
| 360 | } else { |
||
| 361 | // Search backwards for leading whitespace |
||
| 362 | $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0; |
||
| 363 | |||
| 364 | // Search forwards for trailing whitespace |
||
| 365 | // $wsEnd will be the position of the last space (or the '>' if there's none) |
||
| 366 | $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 ); |
||
| 367 | |||
| 368 | // Keep looking forward as long as we're finding more |
||
| 369 | // comments. |
||
| 370 | $comments = [ [ $wsStart, $wsEnd ] ]; |
||
| 371 | View Code Duplication | while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) { |
|
| 372 | $c = strpos( $text, '-->', $wsEnd + 4 ); |
||
| 373 | if ( $c === false ) { |
||
| 374 | break; |
||
| 375 | } |
||
| 376 | $c = $c + 2 + strspn( $text, " \t", $c + 3 ); |
||
| 377 | $comments[] = [ $wsEnd + 1, $c ]; |
||
| 378 | $wsEnd = $c; |
||
| 379 | } |
||
| 380 | |||
| 381 | // Eat the line if possible |
||
| 382 | // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at |
||
| 383 | // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but |
||
| 384 | // it's a possible beneficial b/c break. |
||
| 385 | if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" |
||
| 386 | && substr( $text, $wsEnd + 1, 1 ) == "\n" |
||
| 387 | ) { |
||
| 388 | // Remove leading whitespace from the end of the accumulator |
||
| 389 | // Sanity check first though |
||
| 390 | $wsLength = $i - $wsStart; |
||
| 391 | if ( $wsLength > 0 |
||
| 392 | && strspn( $accum, " \t", -$wsLength ) === $wsLength |
||
| 393 | ) { |
||
| 394 | $accum = substr( $accum, 0, -$wsLength ); |
||
| 395 | } |
||
| 396 | |||
| 397 | // Dump all but the last comment to the accumulator |
||
| 398 | foreach ( $comments as $j => $com ) { |
||
| 399 | $startPos = $com[0]; |
||
| 400 | $endPos = $com[1] + 1; |
||
| 401 | if ( $j == ( count( $comments ) - 1 ) ) { |
||
| 402 | break; |
||
| 403 | } |
||
| 404 | $inner = substr( $text, $startPos, $endPos - $startPos ); |
||
| 405 | $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>'; |
||
| 406 | } |
||
| 407 | |||
| 408 | // Do a line-start run next time to look for headings after the comment |
||
| 409 | $fakeLineStart = true; |
||
| 410 | } else { |
||
| 411 | // No line to eat, just take the comment itself |
||
| 412 | $startPos = $i; |
||
| 413 | $endPos += 2; |
||
| 414 | } |
||
| 415 | |||
| 416 | View Code Duplication | if ( $stack->top ) { |
|
| 417 | $part = $stack->top->getCurrentPart(); |
||
| 418 | if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) { |
||
| 419 | $part->visualEnd = $wsStart; |
||
| 420 | } |
||
| 421 | // Else comments abutting, no change in visual end |
||
| 422 | $part->commentEnd = $endPos; |
||
| 423 | } |
||
| 424 | $i = $endPos + 1; |
||
| 425 | $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); |
||
| 426 | $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>'; |
||
| 427 | } |
||
| 428 | continue; |
||
| 429 | } |
||
| 430 | $name = $matches[1]; |
||
| 431 | $lowerName = strtolower( $name ); |
||
| 432 | $attrStart = $i + strlen( $name ) + 1; |
||
| 433 | |||
| 434 | // Find end of tag |
||
| 435 | $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart ); |
||
| 436 | if ( $tagEndPos === false ) { |
||
| 437 | // Infinite backtrack |
||
| 438 | // Disable tag search to prevent worst-case O(N^2) performance |
||
| 439 | $noMoreGT = true; |
||
| 440 | $accum .= '<'; |
||
| 441 | ++$i; |
||
| 442 | continue; |
||
| 443 | } |
||
| 444 | |||
| 445 | // Handle ignored tags |
||
| 446 | if ( in_array( $lowerName, $ignoredTags ) ) { |
||
| 447 | $accum .= '<ignore>' |
||
| 448 | . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) ) |
||
| 449 | . '</ignore>'; |
||
| 450 | $i = $tagEndPos + 1; |
||
| 451 | continue; |
||
| 452 | } |
||
| 453 | |||
| 454 | $tagStartPos = $i; |
||
| 455 | if ( $text[$tagEndPos - 1] == '/' ) { |
||
| 456 | $attrEnd = $tagEndPos - 1; |
||
| 457 | $inner = null; |
||
| 458 | $i = $tagEndPos + 1; |
||
| 459 | $close = ''; |
||
| 460 | } else { |
||
| 461 | $attrEnd = $tagEndPos; |
||
| 462 | // Find closing tag |
||
| 463 | if ( |
||
| 464 | !isset( $noMoreClosingTag[$name] ) && |
||
| 465 | preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", |
||
| 466 | $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) |
||
| 467 | ) { |
||
| 468 | $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); |
||
| 469 | $i = $matches[0][1] + strlen( $matches[0][0] ); |
||
| 470 | $close = '<close>' . htmlspecialchars( $matches[0][0] ) . '</close>'; |
||
| 471 | View Code Duplication | } else { |
|
| 472 | // No end tag |
||
| 473 | if ( in_array( $name, $xmlishAllowMissingEndTag ) ) { |
||
| 474 | // Let it run out to the end of the text. |
||
| 475 | $inner = substr( $text, $tagEndPos + 1 ); |
||
| 476 | $i = $lengthText; |
||
| 477 | $close = ''; |
||
| 478 | } else { |
||
| 479 | // Don't match the tag, treat opening tag as literal and resume parsing. |
||
| 480 | $i = $tagEndPos + 1; |
||
| 481 | $accum .= htmlspecialchars( substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) ); |
||
| 482 | // Cache results, otherwise we have O(N^2) performance for input like <foo><foo><foo>... |
||
| 483 | $noMoreClosingTag[$name] = true; |
||
| 484 | continue; |
||
| 485 | } |
||
| 486 | } |
||
| 487 | } |
||
| 488 | // <includeonly> and <noinclude> just become <ignore> tags |
||
| 489 | if ( in_array( $lowerName, $ignoredElements ) ) { |
||
| 490 | $accum .= '<ignore>' . htmlspecialchars( substr( $text, $tagStartPos, $i - $tagStartPos ) ) |
||
| 491 | . '</ignore>'; |
||
| 492 | continue; |
||
| 493 | } |
||
| 494 | |||
| 495 | $accum .= '<ext>'; |
||
| 496 | View Code Duplication | if ( $attrEnd <= $attrStart ) { |
|
| 497 | $attr = ''; |
||
| 498 | } else { |
||
| 499 | $attr = substr( $text, $attrStart, $attrEnd - $attrStart ); |
||
| 500 | } |
||
| 501 | $accum .= '<name>' . htmlspecialchars( $name ) . '</name>' . |
||
| 502 | // Note that the attr element contains the whitespace between name and attribute, |
||
| 503 | // this is necessary for precise reconstruction during pre-save transform. |
||
| 504 | '<attr>' . htmlspecialchars( $attr ) . '</attr>'; |
||
| 505 | if ( $inner !== null ) { |
||
| 506 | $accum .= '<inner>' . htmlspecialchars( $inner ) . '</inner>'; |
||
| 507 | } |
||
| 508 | $accum .= $close . '</ext>'; |
||
| 509 | } elseif ( $found == 'line-start' ) { |
||
| 510 | // Is this the start of a heading? |
||
| 511 | // Line break belongs before the heading element in any case |
||
| 512 | if ( $fakeLineStart ) { |
||
| 513 | $fakeLineStart = false; |
||
| 514 | } else { |
||
| 515 | $accum .= $curChar; |
||
| 516 | $i++; |
||
| 517 | } |
||
| 518 | |||
| 519 | $count = strspn( $text, '=', $i, 6 ); |
||
| 520 | if ( $count == 1 && $findEquals ) { |
||
|
0 ignored issues
–
show
This
if statement is empty and can be removed.
This check looks for the bodies of These if (rand(1, 6) > 3) {
//print "Check failed";
} else {
print "Check succeeded";
}
could be turned into if (rand(1, 6) <= 3) {
print "Check succeeded";
}
This is much more concise to read. Loading history...
|
|||
| 521 | // DWIM: This looks kind of like a name/value separator. |
||
| 522 | // Let's let the equals handler have it and break the |
||
| 523 | // potential heading. This is heuristic, but AFAICT the |
||
| 524 | // methods for completely correct disambiguation are very |
||
| 525 | // complex. |
||
| 526 | } elseif ( $count > 0 ) { |
||
| 527 | $piece = [ |
||
| 528 | 'open' => "\n", |
||
| 529 | 'close' => "\n", |
||
| 530 | 'parts' => [ new PPDPart( str_repeat( '=', $count ) ) ], |
||
| 531 | 'startPos' => $i, |
||
| 532 | 'count' => $count ]; |
||
| 533 | $stack->push( $piece ); |
||
| 534 | $accum =& $stack->getAccum(); |
||
| 535 | $flags = $stack->getFlags(); |
||
| 536 | extract( $flags ); |
||
| 537 | $i += $count; |
||
| 538 | } |
||
| 539 | } elseif ( $found == 'line-end' ) { |
||
| 540 | $piece = $stack->top; |
||
| 541 | // A heading must be open, otherwise \n wouldn't have been in the search list |
||
| 542 | assert( $piece->open === "\n" ); |
||
|
0 ignored issues
–
show
The property
open does not seem to exist in PPDStack.
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...
|
|||
| 543 | $part = $piece->getCurrentPart(); |
||
| 544 | // Search back through the input to see if it has a proper close. |
||
| 545 | // Do this using the reversed string since the other solutions |
||
| 546 | // (end anchor, etc.) are inefficient. |
||
| 547 | $wsLength = strspn( $revText, " \t", $lengthText - $i ); |
||
| 548 | $searchStart = $i - $wsLength; |
||
| 549 | View Code Duplication | if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { |
|
| 550 | // Comment found at line end |
||
| 551 | // Search for equals signs before the comment |
||
| 552 | $searchStart = $part->visualEnd; |
||
| 553 | $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart ); |
||
| 554 | } |
||
| 555 | $count = $piece->count; |
||
|
0 ignored issues
–
show
The property
count does not seem to exist in PPDStack.
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...
|
|||
| 556 | $equalsLength = strspn( $revText, '=', $lengthText - $searchStart ); |
||
| 557 | if ( $equalsLength > 0 ) { |
||
| 558 | View Code Duplication | if ( $searchStart - $equalsLength == $piece->startPos ) { |
|
|
0 ignored issues
–
show
The property
startPos does not seem to exist in PPDStack.
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...
|
|||
| 559 | // This is just a single string of equals signs on its own line |
||
| 560 | // Replicate the doHeadings behavior /={count}(.+)={count}/ |
||
| 561 | // First find out how many equals signs there really are (don't stop at 6) |
||
| 562 | $count = $equalsLength; |
||
| 563 | if ( $count < 3 ) { |
||
| 564 | $count = 0; |
||
| 565 | } else { |
||
| 566 | $count = min( 6, intval( ( $count - 1 ) / 2 ) ); |
||
| 567 | } |
||
| 568 | } else { |
||
| 569 | $count = min( $equalsLength, $count ); |
||
| 570 | } |
||
| 571 | if ( $count > 0 ) { |
||
| 572 | // Normal match, output <h> |
||
| 573 | $element = "<h level=\"$count\" i=\"$headingIndex\">$accum</h>"; |
||
| 574 | $headingIndex++; |
||
| 575 | } else { |
||
| 576 | // Single equals sign on its own line, count=0 |
||
| 577 | $element = $accum; |
||
| 578 | } |
||
| 579 | } else { |
||
| 580 | // No match, no <h>, just pass down the inner text |
||
| 581 | $element = $accum; |
||
| 582 | } |
||
| 583 | // Unwind the stack |
||
| 584 | $stack->pop(); |
||
| 585 | $accum =& $stack->getAccum(); |
||
| 586 | $flags = $stack->getFlags(); |
||
| 587 | extract( $flags ); |
||
| 588 | |||
| 589 | // Append the result to the enclosing accumulator |
||
| 590 | $accum .= $element; |
||
| 591 | // Note that we do NOT increment the input pointer. |
||
| 592 | // This is because the closing linebreak could be the opening linebreak of |
||
| 593 | // another heading. Infinite loops are avoided because the next iteration MUST |
||
| 594 | // hit the heading open case above, which unconditionally increments the |
||
| 595 | // input pointer. |
||
| 596 | View Code Duplication | } elseif ( $found == 'open' ) { |
|
| 597 | # count opening brace characters |
||
| 598 | $count = strspn( $text, $curChar, $i ); |
||
| 599 | |||
| 600 | # we need to add to stack only if opening brace count is enough for one of the rules |
||
| 601 | if ( $count >= $rule['min'] ) { |
||
| 602 | # Add it to the stack |
||
| 603 | $piece = [ |
||
| 604 | 'open' => $curChar, |
||
| 605 | 'close' => $rule['end'], |
||
| 606 | 'count' => $count, |
||
| 607 | 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ), |
||
| 608 | ]; |
||
| 609 | |||
| 610 | $stack->push( $piece ); |
||
| 611 | $accum =& $stack->getAccum(); |
||
| 612 | $flags = $stack->getFlags(); |
||
| 613 | extract( $flags ); |
||
| 614 | } else { |
||
| 615 | # Add literal brace(s) |
||
| 616 | $accum .= htmlspecialchars( str_repeat( $curChar, $count ) ); |
||
| 617 | } |
||
| 618 | $i += $count; |
||
| 619 | } elseif ( $found == 'close' ) { |
||
| 620 | $piece = $stack->top; |
||
| 621 | # lets check if there are enough characters for closing brace |
||
| 622 | $maxCount = $piece->count; |
||
| 623 | $count = strspn( $text, $curChar, $i, $maxCount ); |
||
| 624 | |||
| 625 | # check for maximum matching characters (if there are 5 closing |
||
| 626 | # characters, we will probably need only 3 - depending on the rules) |
||
| 627 | $rule = $this->rules[$piece->open]; |
||
| 628 | View Code Duplication | if ( $count > $rule['max'] ) { |
|
| 629 | # The specified maximum exists in the callback array, unless the caller |
||
| 630 | # has made an error |
||
| 631 | $matchingCount = $rule['max']; |
||
| 632 | } else { |
||
| 633 | # Count is less than the maximum |
||
| 634 | # Skip any gaps in the callback array to find the true largest match |
||
| 635 | # Need to use array_key_exists not isset because the callback can be null |
||
| 636 | $matchingCount = $count; |
||
| 637 | while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) { |
||
| 638 | --$matchingCount; |
||
| 639 | } |
||
| 640 | } |
||
| 641 | |||
| 642 | if ( $matchingCount <= 0 ) { |
||
| 643 | # No matching element found in callback array |
||
| 644 | # Output a literal closing brace and continue |
||
| 645 | $accum .= htmlspecialchars( str_repeat( $curChar, $count ) ); |
||
| 646 | $i += $count; |
||
| 647 | continue; |
||
| 648 | } |
||
| 649 | $name = $rule['names'][$matchingCount]; |
||
| 650 | if ( $name === null ) { |
||
| 651 | // No element, just literal text |
||
| 652 | $element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule['end'], $matchingCount ); |
||
|
0 ignored issues
–
show
The method
breakSyntax() does not seem to exist on object<PPDStack>.
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. Loading history...
|
|||
| 653 | } else { |
||
| 654 | # Create XML element |
||
| 655 | # Note: $parts is already XML, does not need to be encoded further |
||
| 656 | $parts = $piece->parts; |
||
|
0 ignored issues
–
show
The property
parts does not seem to exist in PPDStack.
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...
|
|||
| 657 | $title = $parts[0]->out; |
||
| 658 | unset( $parts[0] ); |
||
| 659 | |||
| 660 | # The invocation is at the start of the line if lineStart is set in |
||
| 661 | # the stack, and all opening brackets are used up. |
||
| 662 | if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) { |
||
|
0 ignored issues
–
show
The property
lineStart does not seem to exist in PPDStack.
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...
|
|||
| 663 | $attr = ' lineStart="1"'; |
||
| 664 | } else { |
||
| 665 | $attr = ''; |
||
| 666 | } |
||
| 667 | |||
| 668 | $element = "<$name$attr>"; |
||
| 669 | $element .= "<title>$title</title>"; |
||
| 670 | $argIndex = 1; |
||
| 671 | foreach ( $parts as $part ) { |
||
| 672 | if ( isset( $part->eqpos ) ) { |
||
| 673 | $argName = substr( $part->out, 0, $part->eqpos ); |
||
| 674 | $argValue = substr( $part->out, $part->eqpos + 1 ); |
||
| 675 | $element .= "<part><name>$argName</name>=<value>$argValue</value></part>"; |
||
| 676 | } else { |
||
| 677 | $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>"; |
||
| 678 | $argIndex++; |
||
| 679 | } |
||
| 680 | } |
||
| 681 | $element .= "</$name>"; |
||
| 682 | } |
||
| 683 | |||
| 684 | # Advance input pointer |
||
| 685 | $i += $matchingCount; |
||
| 686 | |||
| 687 | # Unwind the stack |
||
| 688 | $stack->pop(); |
||
| 689 | $accum =& $stack->getAccum(); |
||
| 690 | |||
| 691 | # Re-add the old stack element if it still has unmatched opening characters remaining |
||
| 692 | View Code Duplication | if ( $matchingCount < $piece->count ) { |
|
| 693 | $piece->parts = [ new PPDPart ]; |
||
| 694 | $piece->count -= $matchingCount; |
||
| 695 | # do we still qualify for any callback with remaining count? |
||
| 696 | $min = $this->rules[$piece->open]['min']; |
||
| 697 | if ( $piece->count >= $min ) { |
||
| 698 | $stack->push( $piece ); |
||
| 699 | $accum =& $stack->getAccum(); |
||
| 700 | } else { |
||
| 701 | $accum .= str_repeat( $piece->open, $piece->count ); |
||
| 702 | } |
||
| 703 | } |
||
| 704 | $flags = $stack->getFlags(); |
||
| 705 | extract( $flags ); |
||
| 706 | |||
| 707 | # Add XML element to the enclosing accumulator |
||
| 708 | $accum .= $element; |
||
| 709 | View Code Duplication | } elseif ( $found == 'pipe' ) { |
|
| 710 | $findEquals = true; // shortcut for getFlags() |
||
| 711 | $stack->addPart(); |
||
| 712 | $accum =& $stack->getAccum(); |
||
| 713 | ++$i; |
||
| 714 | } elseif ( $found == 'equals' ) { |
||
| 715 | $findEquals = false; // shortcut for getFlags() |
||
| 716 | $stack->getCurrentPart()->eqpos = strlen( $accum ); |
||
| 717 | $accum .= '='; |
||
| 718 | ++$i; |
||
| 719 | } |
||
| 720 | } |
||
| 721 | |||
| 722 | # Output any remaining unclosed brackets |
||
| 723 | foreach ( $stack->stack as $piece ) { |
||
| 724 | $stack->rootAccum .= $piece->breakSyntax(); |
||
| 725 | } |
||
| 726 | $stack->rootAccum .= '</root>'; |
||
| 727 | $xml = $stack->rootAccum; |
||
| 728 | |||
| 729 | return $xml; |
||
| 730 | } |
||
| 731 | } |
||
| 732 | |||
| 733 | /** |
||
| 734 | * Stack class to help Preprocessor::preprocessToObj() |
||
| 735 | * @ingroup Parser |
||
| 736 | */ |
||
| 737 | class PPDStack { |
||
| 738 | public $stack, $rootAccum; |
||
|
0 ignored issues
–
show
|
|||
| 739 | |||
| 740 | /** |
||
| 741 | * @var PPDStack |
||
| 742 | */ |
||
| 743 | public $top; |
||
| 744 | public $out; |
||
| 745 | public $elementClass = 'PPDStackElement'; |
||
| 746 | |||
| 747 | public static $false = false; |
||
| 748 | |||
| 749 | public function __construct() { |
||
| 750 | $this->stack = []; |
||
| 751 | $this->top = false; |
||
|
0 ignored issues
–
show
It seems like
false of type false is incompatible with the declared type object<PPDStack> of property $top.
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. Loading history...
|
|||
| 752 | $this->rootAccum = ''; |
||
| 753 | $this->accum =& $this->rootAccum; |
||
|
0 ignored issues
–
show
The property
accum does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
Loading history...
|
|||
| 754 | } |
||
| 755 | |||
| 756 | /** |
||
| 757 | * @return int |
||
| 758 | */ |
||
| 759 | public function count() { |
||
| 760 | return count( $this->stack ); |
||
| 761 | } |
||
| 762 | |||
| 763 | public function &getAccum() { |
||
| 764 | return $this->accum; |
||
| 765 | } |
||
| 766 | |||
| 767 | public function getCurrentPart() { |
||
| 768 | if ( $this->top === false ) { |
||
| 769 | return false; |
||
| 770 | } else { |
||
| 771 | return $this->top->getCurrentPart(); |
||
| 772 | } |
||
| 773 | } |
||
| 774 | |||
| 775 | public function push( $data ) { |
||
| 776 | if ( $data instanceof $this->elementClass ) { |
||
| 777 | $this->stack[] = $data; |
||
| 778 | } else { |
||
| 779 | $class = $this->elementClass; |
||
| 780 | $this->stack[] = new $class( $data ); |
||
| 781 | } |
||
| 782 | $this->top = $this->stack[count( $this->stack ) - 1]; |
||
| 783 | $this->accum =& $this->top->getAccum(); |
||
| 784 | } |
||
| 785 | |||
| 786 | public function pop() { |
||
| 787 | if ( !count( $this->stack ) ) { |
||
| 788 | throw new MWException( __METHOD__ . ': no elements remaining' ); |
||
| 789 | } |
||
| 790 | $temp = array_pop( $this->stack ); |
||
| 791 | |||
| 792 | if ( count( $this->stack ) ) { |
||
| 793 | $this->top = $this->stack[count( $this->stack ) - 1]; |
||
| 794 | $this->accum =& $this->top->getAccum(); |
||
| 795 | } else { |
||
| 796 | $this->top = self::$false; |
||
|
0 ignored issues
–
show
It seems like
self::$false of type boolean is incompatible with the declared type object<PPDStack> of property $top.
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. Loading history...
|
|||
| 797 | $this->accum =& $this->rootAccum; |
||
| 798 | } |
||
| 799 | return $temp; |
||
| 800 | } |
||
| 801 | |||
| 802 | public function addPart( $s = '' ) { |
||
| 803 | $this->top->addPart( $s ); |
||
| 804 | $this->accum =& $this->top->getAccum(); |
||
| 805 | } |
||
| 806 | |||
| 807 | /** |
||
| 808 | * @return array |
||
| 809 | */ |
||
| 810 | public function getFlags() { |
||
| 811 | if ( !count( $this->stack ) ) { |
||
| 812 | return [ |
||
| 813 | 'findEquals' => false, |
||
| 814 | 'findPipe' => false, |
||
| 815 | 'inHeading' => false, |
||
| 816 | ]; |
||
| 817 | } else { |
||
| 818 | return $this->top->getFlags(); |
||
| 819 | } |
||
| 820 | } |
||
| 821 | } |
||
| 822 | |||
| 823 | /** |
||
| 824 | * @ingroup Parser |
||
| 825 | */ |
||
| 826 | class PPDStackElement { |
||
| 827 | /** |
||
| 828 | * @var string Opening character (\n for heading) |
||
| 829 | */ |
||
| 830 | public $open; |
||
| 831 | |||
| 832 | /** |
||
| 833 | * @var string Matching closing character |
||
| 834 | */ |
||
| 835 | public $close; |
||
| 836 | |||
| 837 | /** |
||
| 838 | * @var int Number of opening characters found (number of "=" for heading) |
||
| 839 | */ |
||
| 840 | public $count; |
||
| 841 | |||
| 842 | /** |
||
| 843 | * @var PPDPart[] Array of PPDPart objects describing pipe-separated parts. |
||
| 844 | */ |
||
| 845 | public $parts; |
||
| 846 | |||
| 847 | /** |
||
| 848 | * @var bool True if the open char appeared at the start of the input line. |
||
| 849 | * Not set for headings. |
||
| 850 | */ |
||
| 851 | public $lineStart; |
||
| 852 | |||
| 853 | public $partClass = 'PPDPart'; |
||
| 854 | |||
| 855 | public function __construct( $data = [] ) { |
||
| 856 | $class = $this->partClass; |
||
| 857 | $this->parts = [ new $class ]; |
||
|
0 ignored issues
–
show
It seems like
array(new $class()) of type array<integer,object,{"0":"object"}> is incompatible with the declared type array<integer,object<PPDPart>> of property $parts.
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. Loading history...
|
|||
| 858 | |||
| 859 | foreach ( $data as $name => $value ) { |
||
| 860 | $this->$name = $value; |
||
| 861 | } |
||
| 862 | } |
||
| 863 | |||
| 864 | public function &getAccum() { |
||
| 865 | return $this->parts[count( $this->parts ) - 1]->out; |
||
| 866 | } |
||
| 867 | |||
| 868 | public function addPart( $s = '' ) { |
||
| 869 | $class = $this->partClass; |
||
| 870 | $this->parts[] = new $class( $s ); |
||
| 871 | } |
||
| 872 | |||
| 873 | public function getCurrentPart() { |
||
| 874 | return $this->parts[count( $this->parts ) - 1]; |
||
| 875 | } |
||
| 876 | |||
| 877 | /** |
||
| 878 | * @return array |
||
| 879 | */ |
||
| 880 | public function getFlags() { |
||
| 881 | $partCount = count( $this->parts ); |
||
| 882 | $findPipe = $this->open != "\n" && $this->open != '['; |
||
| 883 | return [ |
||
| 884 | 'findPipe' => $findPipe, |
||
| 885 | 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ), |
||
| 886 | 'inHeading' => $this->open == "\n", |
||
| 887 | ]; |
||
| 888 | } |
||
| 889 | |||
| 890 | /** |
||
| 891 | * Get the output string that would result if the close is not found. |
||
| 892 | * |
||
| 893 | * @param bool|int $openingCount |
||
| 894 | * @return string |
||
| 895 | */ |
||
| 896 | public function breakSyntax( $openingCount = false ) { |
||
| 897 | if ( $this->open == "\n" ) { |
||
| 898 | $s = $this->parts[0]->out; |
||
| 899 | } else { |
||
| 900 | if ( $openingCount === false ) { |
||
| 901 | $openingCount = $this->count; |
||
| 902 | } |
||
| 903 | $s = str_repeat( $this->open, $openingCount ); |
||
| 904 | $first = true; |
||
| 905 | foreach ( $this->parts as $part ) { |
||
| 906 | if ( $first ) { |
||
| 907 | $first = false; |
||
| 908 | } else { |
||
| 909 | $s .= '|'; |
||
| 910 | } |
||
| 911 | $s .= $part->out; |
||
| 912 | } |
||
| 913 | } |
||
| 914 | return $s; |
||
| 915 | } |
||
| 916 | } |
||
| 917 | |||
| 918 | /** |
||
| 919 | * @ingroup Parser |
||
| 920 | */ |
||
| 921 | class PPDPart { |
||
| 922 | /** |
||
| 923 | * @var string Output accumulator string |
||
| 924 | */ |
||
| 925 | public $out; |
||
| 926 | |||
| 927 | // Optional member variables: |
||
| 928 | // eqpos Position of equals sign in output accumulator |
||
| 929 | // commentEnd Past-the-end input pointer for the last comment encountered |
||
| 930 | // visualEnd Past-the-end input pointer for the end of the accumulator minus comments |
||
| 931 | |||
| 932 | public function __construct( $out = '' ) { |
||
| 933 | $this->out = $out; |
||
| 934 | } |
||
| 935 | } |
||
| 936 | |||
| 937 | /** |
||
| 938 | * An expansion frame, used as a context to expand the result of preprocessToObj() |
||
| 939 | * @ingroup Parser |
||
| 940 | */ |
||
| 941 | // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps |
||
| 942 | class PPFrame_DOM implements PPFrame { |
||
| 943 | // @codingStandardsIgnoreEnd |
||
| 944 | |||
| 945 | /** |
||
| 946 | * @var Preprocessor |
||
| 947 | */ |
||
| 948 | public $preprocessor; |
||
| 949 | |||
| 950 | /** |
||
| 951 | * @var Parser |
||
| 952 | */ |
||
| 953 | public $parser; |
||
| 954 | |||
| 955 | /** |
||
| 956 | * @var Title |
||
| 957 | */ |
||
| 958 | public $title; |
||
| 959 | public $titleCache; |
||
| 960 | |||
| 961 | /** |
||
| 962 | * Hashtable listing templates which are disallowed for expansion in this frame, |
||
| 963 | * having been encountered previously in parent frames. |
||
| 964 | */ |
||
| 965 | public $loopCheckHash; |
||
| 966 | |||
| 967 | /** |
||
| 968 | * Recursion depth of this frame, top = 0 |
||
| 969 | * Note that this is NOT the same as expansion depth in expand() |
||
| 970 | */ |
||
| 971 | public $depth; |
||
| 972 | |||
| 973 | private $volatile = false; |
||
| 974 | private $ttl = null; |
||
| 975 | |||
| 976 | /** |
||
| 977 | * @var array |
||
| 978 | */ |
||
| 979 | protected $childExpansionCache; |
||
| 980 | |||
| 981 | /** |
||
| 982 | * Construct a new preprocessor frame. |
||
| 983 | * @param Preprocessor $preprocessor The parent preprocessor |
||
| 984 | */ |
||
| 985 | View Code Duplication | public function __construct( $preprocessor ) { |
|
| 986 | $this->preprocessor = $preprocessor; |
||
| 987 | $this->parser = $preprocessor->parser; |
||
|
0 ignored issues
–
show
The property
parser does not seem to exist in Preprocessor.
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...
|
|||
| 988 | $this->title = $this->parser->mTitle; |
||
| 989 | $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ]; |
||
| 990 | $this->loopCheckHash = []; |
||
| 991 | $this->depth = 0; |
||
| 992 | $this->childExpansionCache = []; |
||
| 993 | } |
||
| 994 | |||
| 995 | /** |
||
| 996 | * Create a new child frame |
||
| 997 | * $args is optionally a multi-root PPNode or array containing the template arguments |
||
| 998 | * |
||
| 999 | * @param bool|array $args |
||
| 1000 | * @param Title|bool $title |
||
| 1001 | * @param int $indexOffset |
||
| 1002 | * @return PPTemplateFrame_DOM |
||
| 1003 | */ |
||
| 1004 | public function newChild( $args = false, $title = false, $indexOffset = 0 ) { |
||
| 1005 | $namedArgs = []; |
||
| 1006 | $numberedArgs = []; |
||
| 1007 | if ( $title === false ) { |
||
| 1008 | $title = $this->title; |
||
| 1009 | } |
||
| 1010 | if ( $args !== false ) { |
||
| 1011 | $xpath = false; |
||
| 1012 | if ( $args instanceof PPNode ) { |
||
| 1013 | $args = $args->node; |
||
| 1014 | } |
||
| 1015 | foreach ( $args as $arg ) { |
||
| 1016 | if ( $arg instanceof PPNode ) { |
||
| 1017 | $arg = $arg->node; |
||
|
0 ignored issues
–
show
Accessing
node on the interface PPNode suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
Loading history...
|
|||
| 1018 | } |
||
| 1019 | if ( !$xpath || $xpath->document !== $arg->ownerDocument ) { |
||
| 1020 | $xpath = new DOMXPath( $arg->ownerDocument ); |
||
| 1021 | } |
||
| 1022 | |||
| 1023 | $nameNodes = $xpath->query( 'name', $arg ); |
||
| 1024 | $value = $xpath->query( 'value', $arg ); |
||
| 1025 | if ( $nameNodes->item( 0 )->hasAttributes() ) { |
||
| 1026 | // Numbered parameter |
||
| 1027 | $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent; |
||
| 1028 | $index = $index - $indexOffset; |
||
| 1029 | if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) { |
||
| 1030 | $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning', |
||
| 1031 | wfEscapeWikiText( $this->title ), |
||
| 1032 | wfEscapeWikiText( $title ), |
||
| 1033 | wfEscapeWikiText( $index ) )->text() ); |
||
| 1034 | $this->parser->addTrackingCategory( 'duplicate-args-category' ); |
||
| 1035 | } |
||
| 1036 | $numberedArgs[$index] = $value->item( 0 ); |
||
| 1037 | unset( $namedArgs[$index] ); |
||
| 1038 | View Code Duplication | } else { |
|
| 1039 | // Named parameter |
||
| 1040 | $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) ); |
||
| 1041 | if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) { |
||
| 1042 | $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning', |
||
| 1043 | wfEscapeWikiText( $this->title ), |
||
| 1044 | wfEscapeWikiText( $title ), |
||
| 1045 | wfEscapeWikiText( $name ) )->text() ); |
||
| 1046 | $this->parser->addTrackingCategory( 'duplicate-args-category' ); |
||
| 1047 | } |
||
| 1048 | $namedArgs[$name] = $value->item( 0 ); |
||
| 1049 | unset( $numberedArgs[$name] ); |
||
| 1050 | } |
||
| 1051 | } |
||
| 1052 | } |
||
| 1053 | return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); |
||
| 1054 | } |
||
| 1055 | |||
| 1056 | /** |
||
| 1057 | * @throws MWException |
||
| 1058 | * @param string|int $key |
||
| 1059 | * @param string|PPNode_DOM|DOMDocument $root |
||
| 1060 | * @param int $flags |
||
| 1061 | * @return string |
||
| 1062 | */ |
||
| 1063 | public function cachedExpand( $key, $root, $flags = 0 ) { |
||
| 1064 | // we don't have a parent, so we don't have a cache |
||
| 1065 | return $this->expand( $root, $flags ); |
||
| 1066 | } |
||
| 1067 | |||
| 1068 | /** |
||
| 1069 | * @throws MWException |
||
| 1070 | * @param string|PPNode_DOM|DOMDocument $root |
||
| 1071 | * @param int $flags |
||
| 1072 | * @return string |
||
| 1073 | */ |
||
| 1074 | public function expand( $root, $flags = 0 ) { |
||
| 1075 | static $expansionDepth = 0; |
||
| 1076 | if ( is_string( $root ) ) { |
||
| 1077 | return $root; |
||
| 1078 | } |
||
| 1079 | |||
| 1080 | View Code Duplication | if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) { |
|
| 1081 | $this->parser->limitationWarn( 'node-count-exceeded', |
||
| 1082 | $this->parser->mPPNodeCount, |
||
| 1083 | $this->parser->mOptions->getMaxPPNodeCount() |
||
| 1084 | ); |
||
| 1085 | return '<span class="error">Node-count limit exceeded</span>'; |
||
| 1086 | } |
||
| 1087 | |||
| 1088 | View Code Duplication | if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) { |
|
| 1089 | $this->parser->limitationWarn( 'expansion-depth-exceeded', |
||
| 1090 | $expansionDepth, |
||
| 1091 | $this->parser->mOptions->getMaxPPExpandDepth() |
||
| 1092 | ); |
||
| 1093 | return '<span class="error">Expansion depth limit exceeded</span>'; |
||
| 1094 | } |
||
| 1095 | ++$expansionDepth; |
||
| 1096 | if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) { |
||
| 1097 | $this->parser->mHighestExpansionDepth = $expansionDepth; |
||
| 1098 | } |
||
| 1099 | |||
| 1100 | if ( $root instanceof PPNode_DOM ) { |
||
| 1101 | $root = $root->node; |
||
| 1102 | } |
||
| 1103 | if ( $root instanceof DOMDocument ) { |
||
| 1104 | $root = $root->documentElement; |
||
| 1105 | } |
||
| 1106 | |||
| 1107 | $outStack = [ '', '' ]; |
||
| 1108 | $iteratorStack = [ false, $root ]; |
||
| 1109 | $indexStack = [ 0, 0 ]; |
||
| 1110 | |||
| 1111 | while ( count( $iteratorStack ) > 1 ) { |
||
| 1112 | $level = count( $outStack ) - 1; |
||
| 1113 | $iteratorNode =& $iteratorStack[$level]; |
||
| 1114 | $out =& $outStack[$level]; |
||
| 1115 | $index =& $indexStack[$level]; |
||
| 1116 | |||
| 1117 | if ( $iteratorNode instanceof PPNode_DOM ) { |
||
| 1118 | $iteratorNode = $iteratorNode->node; |
||
| 1119 | } |
||
| 1120 | |||
| 1121 | View Code Duplication | if ( is_array( $iteratorNode ) ) { |
|
| 1122 | if ( $index >= count( $iteratorNode ) ) { |
||
| 1123 | // All done with this iterator |
||
| 1124 | $iteratorStack[$level] = false; |
||
| 1125 | $contextNode = false; |
||
| 1126 | } else { |
||
| 1127 | $contextNode = $iteratorNode[$index]; |
||
| 1128 | $index++; |
||
| 1129 | } |
||
| 1130 | } elseif ( $iteratorNode instanceof DOMNodeList ) { |
||
| 1131 | if ( $index >= $iteratorNode->length ) { |
||
| 1132 | // All done with this iterator |
||
| 1133 | $iteratorStack[$level] = false; |
||
| 1134 | $contextNode = false; |
||
| 1135 | } else { |
||
| 1136 | $contextNode = $iteratorNode->item( $index ); |
||
| 1137 | $index++; |
||
| 1138 | } |
||
| 1139 | } else { |
||
| 1140 | // Copy to $contextNode and then delete from iterator stack, |
||
| 1141 | // because this is not an iterator but we do have to execute it once |
||
| 1142 | $contextNode = $iteratorStack[$level]; |
||
| 1143 | $iteratorStack[$level] = false; |
||
| 1144 | } |
||
| 1145 | |||
| 1146 | if ( $contextNode instanceof PPNode_DOM ) { |
||
| 1147 | $contextNode = $contextNode->node; |
||
| 1148 | } |
||
| 1149 | |||
| 1150 | $newIterator = false; |
||
| 1151 | |||
| 1152 | if ( $contextNode === false ) { |
||
|
0 ignored issues
–
show
This
if statement is empty and can be removed.
This check looks for the bodies of These if (rand(1, 6) > 3) {
//print "Check failed";
} else {
print "Check succeeded";
}
could be turned into if (rand(1, 6) <= 3) {
print "Check succeeded";
}
This is much more concise to read. Loading history...
|
|||
| 1153 | // nothing to do |
||
| 1154 | } elseif ( is_string( $contextNode ) ) { |
||
| 1155 | $out .= $contextNode; |
||
| 1156 | } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) { |
||
| 1157 | $newIterator = $contextNode; |
||
| 1158 | } elseif ( $contextNode instanceof DOMNode ) { |
||
| 1159 | if ( $contextNode->nodeType == XML_TEXT_NODE ) { |
||
| 1160 | $out .= $contextNode->nodeValue; |
||
| 1161 | } elseif ( $contextNode->nodeName == 'template' ) { |
||
| 1162 | # Double-brace expansion |
||
| 1163 | $xpath = new DOMXPath( $contextNode->ownerDocument ); |
||
| 1164 | $titles = $xpath->query( 'title', $contextNode ); |
||
| 1165 | $title = $titles->item( 0 ); |
||
| 1166 | $parts = $xpath->query( 'part', $contextNode ); |
||
| 1167 | if ( $flags & PPFrame::NO_TEMPLATES ) { |
||
| 1168 | $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts ); |
||
| 1169 | View Code Duplication | } else { |
|
| 1170 | $lineStart = $contextNode->getAttribute( 'lineStart' ); |
||
| 1171 | $params = [ |
||
| 1172 | 'title' => new PPNode_DOM( $title ), |
||
| 1173 | 'parts' => new PPNode_DOM( $parts ), |
||
| 1174 | 'lineStart' => $lineStart ]; |
||
| 1175 | $ret = $this->parser->braceSubstitution( $params, $this ); |
||
| 1176 | if ( isset( $ret['object'] ) ) { |
||
| 1177 | $newIterator = $ret['object']; |
||
| 1178 | } else { |
||
| 1179 | $out .= $ret['text']; |
||
| 1180 | } |
||
| 1181 | } |
||
| 1182 | } elseif ( $contextNode->nodeName == 'tplarg' ) { |
||
| 1183 | # Triple-brace expansion |
||
| 1184 | $xpath = new DOMXPath( $contextNode->ownerDocument ); |
||
| 1185 | $titles = $xpath->query( 'title', $contextNode ); |
||
| 1186 | $title = $titles->item( 0 ); |
||
| 1187 | $parts = $xpath->query( 'part', $contextNode ); |
||
| 1188 | View Code Duplication | if ( $flags & PPFrame::NO_ARGS ) { |
|
| 1189 | $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts ); |
||
| 1190 | } else { |
||
| 1191 | $params = [ |
||
| 1192 | 'title' => new PPNode_DOM( $title ), |
||
| 1193 | 'parts' => new PPNode_DOM( $parts ) ]; |
||
| 1194 | $ret = $this->parser->argSubstitution( $params, $this ); |
||
| 1195 | if ( isset( $ret['object'] ) ) { |
||
| 1196 | $newIterator = $ret['object']; |
||
| 1197 | } else { |
||
| 1198 | $out .= $ret['text']; |
||
| 1199 | } |
||
| 1200 | } |
||
| 1201 | } elseif ( $contextNode->nodeName == 'comment' ) { |
||
| 1202 | # HTML-style comment |
||
| 1203 | # Remove it in HTML, pre+remove and STRIP_COMMENTS modes |
||
| 1204 | # Not in RECOVER_COMMENTS mode (msgnw) though. |
||
| 1205 | if ( ( $this->parser->ot['html'] |
||
| 1206 | || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) |
||
| 1207 | || ( $flags & PPFrame::STRIP_COMMENTS ) |
||
| 1208 | ) && !( $flags & PPFrame::RECOVER_COMMENTS ) |
||
| 1209 | ) { |
||
| 1210 | $out .= ''; |
||
| 1211 | } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { |
||
| 1212 | # Add a strip marker in PST mode so that pstPass2() can |
||
| 1213 | # run some old-fashioned regexes on the result. |
||
| 1214 | # Not in RECOVER_COMMENTS mode (extractSections) though. |
||
| 1215 | $out .= $this->parser->insertStripItem( $contextNode->textContent ); |
||
| 1216 | } else { |
||
| 1217 | # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove |
||
| 1218 | $out .= $contextNode->textContent; |
||
| 1219 | } |
||
| 1220 | } elseif ( $contextNode->nodeName == 'ignore' ) { |
||
| 1221 | # Output suppression used by <includeonly> etc. |
||
| 1222 | # OT_WIKI will only respect <ignore> in substed templates. |
||
| 1223 | # The other output types respect it unless NO_IGNORE is set. |
||
| 1224 | # extractSections() sets NO_IGNORE and so never respects it. |
||
| 1225 | View Code Duplication | if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) |
|
| 1226 | || ( $flags & PPFrame::NO_IGNORE ) |
||
| 1227 | ) { |
||
| 1228 | $out .= $contextNode->textContent; |
||
| 1229 | } else { |
||
| 1230 | $out .= ''; |
||
| 1231 | } |
||
| 1232 | } elseif ( $contextNode->nodeName == 'ext' ) { |
||
| 1233 | # Extension tag |
||
| 1234 | $xpath = new DOMXPath( $contextNode->ownerDocument ); |
||
| 1235 | $names = $xpath->query( 'name', $contextNode ); |
||
| 1236 | $attrs = $xpath->query( 'attr', $contextNode ); |
||
| 1237 | $inners = $xpath->query( 'inner', $contextNode ); |
||
| 1238 | $closes = $xpath->query( 'close', $contextNode ); |
||
| 1239 | if ( $flags & PPFrame::NO_TAGS ) { |
||
| 1240 | $s = '<' . $this->expand( $names->item( 0 ), $flags ); |
||
| 1241 | if ( $attrs->length > 0 ) { |
||
| 1242 | $s .= $this->expand( $attrs->item( 0 ), $flags ); |
||
| 1243 | } |
||
| 1244 | if ( $inners->length > 0 ) { |
||
| 1245 | $s .= '>' . $this->expand( $inners->item( 0 ), $flags ); |
||
| 1246 | if ( $closes->length > 0 ) { |
||
| 1247 | $s .= $this->expand( $closes->item( 0 ), $flags ); |
||
| 1248 | } |
||
| 1249 | } else { |
||
| 1250 | $s .= '/>'; |
||
| 1251 | } |
||
| 1252 | $out .= $s; |
||
| 1253 | } else { |
||
| 1254 | $params = [ |
||
| 1255 | 'name' => new PPNode_DOM( $names->item( 0 ) ), |
||
| 1256 | 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null, |
||
| 1257 | 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null, |
||
| 1258 | 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null, |
||
| 1259 | ]; |
||
| 1260 | $out .= $this->parser->extensionSubstitution( $params, $this ); |
||
| 1261 | } |
||
| 1262 | } elseif ( $contextNode->nodeName == 'h' ) { |
||
| 1263 | # Heading |
||
| 1264 | $s = $this->expand( $contextNode->childNodes, $flags ); |
||
| 1265 | |||
| 1266 | # Insert a heading marker only for <h> children of <root> |
||
| 1267 | # This is to stop extractSections from going over multiple tree levels |
||
| 1268 | if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) { |
||
| 1269 | # Insert heading index marker |
||
| 1270 | $headingIndex = $contextNode->getAttribute( 'i' ); |
||
| 1271 | $titleText = $this->title->getPrefixedDBkey(); |
||
| 1272 | $this->parser->mHeadings[] = [ $titleText, $headingIndex ]; |
||
| 1273 | $serial = count( $this->parser->mHeadings ) - 1; |
||
| 1274 | $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX; |
||
| 1275 | $count = $contextNode->getAttribute( 'level' ); |
||
| 1276 | $s = substr( $s, 0, $count ) . $marker . substr( $s, $count ); |
||
| 1277 | $this->parser->mStripState->addGeneral( $marker, '' ); |
||
| 1278 | } |
||
| 1279 | $out .= $s; |
||
| 1280 | } else { |
||
| 1281 | # Generic recursive expansion |
||
| 1282 | $newIterator = $contextNode->childNodes; |
||
| 1283 | } |
||
| 1284 | } else { |
||
| 1285 | throw new MWException( __METHOD__ . ': Invalid parameter type' ); |
||
| 1286 | } |
||
| 1287 | |||
| 1288 | if ( $newIterator !== false ) { |
||
| 1289 | if ( $newIterator instanceof PPNode_DOM ) { |
||
| 1290 | $newIterator = $newIterator->node; |
||
| 1291 | } |
||
| 1292 | $outStack[] = ''; |
||
| 1293 | $iteratorStack[] = $newIterator; |
||
| 1294 | $indexStack[] = 0; |
||
| 1295 | View Code Duplication | } elseif ( $iteratorStack[$level] === false ) { |
|
| 1296 | // Return accumulated value to parent |
||
| 1297 | // With tail recursion |
||
| 1298 | while ( $iteratorStack[$level] === false && $level > 0 ) { |
||
| 1299 | $outStack[$level - 1] .= $out; |
||
| 1300 | array_pop( $outStack ); |
||
| 1301 | array_pop( $iteratorStack ); |
||
| 1302 | array_pop( $indexStack ); |
||
| 1303 | $level--; |
||
| 1304 | } |
||
| 1305 | } |
||
| 1306 | } |
||
| 1307 | --$expansionDepth; |
||
| 1308 | return $outStack[0]; |
||
| 1309 | } |
||
| 1310 | |||
| 1311 | /** |
||
| 1312 | * @param string $sep |
||
| 1313 | * @param int $flags |
||
| 1314 | * @param string|PPNode_DOM|DOMDocument $args,... |
||
|
0 ignored issues
–
show
There is no parameter named
$args,.... Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. Loading history...
|
|||
| 1315 | * @return string |
||
| 1316 | */ |
||
| 1317 | View Code Duplication | public function implodeWithFlags( $sep, $flags /*, ... */ ) { |
|
| 1318 | $args = array_slice( func_get_args(), 2 ); |
||
| 1319 | |||
| 1320 | $first = true; |
||
| 1321 | $s = ''; |
||
| 1322 | foreach ( $args as $root ) { |
||
| 1323 | if ( $root instanceof PPNode_DOM ) { |
||
| 1324 | $root = $root->node; |
||
| 1325 | } |
||
| 1326 | if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
||
| 1327 | $root = [ $root ]; |
||
| 1328 | } |
||
| 1329 | foreach ( $root as $node ) { |
||
| 1330 | if ( $first ) { |
||
| 1331 | $first = false; |
||
| 1332 | } else { |
||
| 1333 | $s .= $sep; |
||
| 1334 | } |
||
| 1335 | $s .= $this->expand( $node, $flags ); |
||
| 1336 | } |
||
| 1337 | } |
||
| 1338 | return $s; |
||
| 1339 | } |
||
| 1340 | |||
| 1341 | /** |
||
| 1342 | * Implode with no flags specified |
||
| 1343 | * This previously called implodeWithFlags but has now been inlined to reduce stack depth |
||
| 1344 | * |
||
| 1345 | * @param string $sep |
||
| 1346 | * @param string|PPNode_DOM|DOMDocument $args,... |
||
|
0 ignored issues
–
show
There is no parameter named
$args,.... Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. Loading history...
|
|||
| 1347 | * @return string |
||
| 1348 | */ |
||
| 1349 | View Code Duplication | public function implode( $sep /*, ... */ ) { |
|
| 1350 | $args = array_slice( func_get_args(), 1 ); |
||
| 1351 | |||
| 1352 | $first = true; |
||
| 1353 | $s = ''; |
||
| 1354 | foreach ( $args as $root ) { |
||
| 1355 | if ( $root instanceof PPNode_DOM ) { |
||
| 1356 | $root = $root->node; |
||
| 1357 | } |
||
| 1358 | if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
||
| 1359 | $root = [ $root ]; |
||
| 1360 | } |
||
| 1361 | foreach ( $root as $node ) { |
||
| 1362 | if ( $first ) { |
||
| 1363 | $first = false; |
||
| 1364 | } else { |
||
| 1365 | $s .= $sep; |
||
| 1366 | } |
||
| 1367 | $s .= $this->expand( $node ); |
||
| 1368 | } |
||
| 1369 | } |
||
| 1370 | return $s; |
||
| 1371 | } |
||
| 1372 | |||
| 1373 | /** |
||
| 1374 | * Makes an object that, when expand()ed, will be the same as one obtained |
||
| 1375 | * with implode() |
||
| 1376 | * |
||
| 1377 | * @param string $sep |
||
| 1378 | * @param string|PPNode_DOM|DOMDocument $args,... |
||
|
0 ignored issues
–
show
There is no parameter named
$args,.... Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. Loading history...
|
|||
| 1379 | * @return array |
||
| 1380 | */ |
||
| 1381 | View Code Duplication | public function virtualImplode( $sep /*, ... */ ) { |
|
| 1382 | $args = array_slice( func_get_args(), 1 ); |
||
| 1383 | $out = []; |
||
| 1384 | $first = true; |
||
| 1385 | |||
| 1386 | foreach ( $args as $root ) { |
||
| 1387 | if ( $root instanceof PPNode_DOM ) { |
||
| 1388 | $root = $root->node; |
||
| 1389 | } |
||
| 1390 | if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
||
| 1391 | $root = [ $root ]; |
||
| 1392 | } |
||
| 1393 | foreach ( $root as $node ) { |
||
| 1394 | if ( $first ) { |
||
| 1395 | $first = false; |
||
| 1396 | } else { |
||
| 1397 | $out[] = $sep; |
||
| 1398 | } |
||
| 1399 | $out[] = $node; |
||
| 1400 | } |
||
| 1401 | } |
||
| 1402 | return $out; |
||
| 1403 | } |
||
| 1404 | |||
| 1405 | /** |
||
| 1406 | * Virtual implode with brackets |
||
| 1407 | * @param string $start |
||
| 1408 | * @param string $sep |
||
| 1409 | * @param string $end |
||
| 1410 | * @param string|PPNode_DOM|DOMDocument $args,... |
||
|
0 ignored issues
–
show
There is no parameter named
$args,.... Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. Loading history...
|
|||
| 1411 | * @return array |
||
| 1412 | */ |
||
| 1413 | View Code Duplication | public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { |
|
| 1414 | $args = array_slice( func_get_args(), 3 ); |
||
| 1415 | $out = [ $start ]; |
||
| 1416 | $first = true; |
||
| 1417 | |||
| 1418 | foreach ( $args as $root ) { |
||
| 1419 | if ( $root instanceof PPNode_DOM ) { |
||
| 1420 | $root = $root->node; |
||
| 1421 | } |
||
| 1422 | if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
||
| 1423 | $root = [ $root ]; |
||
| 1424 | } |
||
| 1425 | foreach ( $root as $node ) { |
||
| 1426 | if ( $first ) { |
||
| 1427 | $first = false; |
||
| 1428 | } else { |
||
| 1429 | $out[] = $sep; |
||
| 1430 | } |
||
| 1431 | $out[] = $node; |
||
| 1432 | } |
||
| 1433 | } |
||
| 1434 | $out[] = $end; |
||
| 1435 | return $out; |
||
| 1436 | } |
||
| 1437 | |||
| 1438 | public function __toString() { |
||
| 1439 | return 'frame{}'; |
||
| 1440 | } |
||
| 1441 | |||
| 1442 | View Code Duplication | public function getPDBK( $level = false ) { |
|
| 1443 | if ( $level === false ) { |
||
| 1444 | return $this->title->getPrefixedDBkey(); |
||
| 1445 | } else { |
||
| 1446 | return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; |
||
| 1447 | } |
||
| 1448 | } |
||
| 1449 | |||
| 1450 | /** |
||
| 1451 | * @return array |
||
| 1452 | */ |
||
| 1453 | public function getArguments() { |
||
| 1454 | return []; |
||
| 1455 | } |
||
| 1456 | |||
| 1457 | /** |
||
| 1458 | * @return array |
||
| 1459 | */ |
||
| 1460 | public function getNumberedArguments() { |
||
| 1461 | return []; |
||
| 1462 | } |
||
| 1463 | |||
| 1464 | /** |
||
| 1465 | * @return array |
||
| 1466 | */ |
||
| 1467 | public function getNamedArguments() { |
||
| 1468 | return []; |
||
| 1469 | } |
||
| 1470 | |||
| 1471 | /** |
||
| 1472 | * Returns true if there are no arguments in this frame |
||
| 1473 | * |
||
| 1474 | * @return bool |
||
| 1475 | */ |
||
| 1476 | public function isEmpty() { |
||
| 1477 | return true; |
||
| 1478 | } |
||
| 1479 | |||
| 1480 | /** |
||
| 1481 | * @param int|string $name |
||
| 1482 | * @return bool Always false in this implementation. |
||
| 1483 | */ |
||
| 1484 | public function getArgument( $name ) { |
||
| 1485 | return false; |
||
| 1486 | } |
||
| 1487 | |||
| 1488 | /** |
||
| 1489 | * Returns true if the infinite loop check is OK, false if a loop is detected |
||
| 1490 | * |
||
| 1491 | * @param Title $title |
||
| 1492 | * @return bool |
||
| 1493 | */ |
||
| 1494 | public function loopCheck( $title ) { |
||
| 1495 | return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); |
||
| 1496 | } |
||
| 1497 | |||
| 1498 | /** |
||
| 1499 | * Return true if the frame is a template frame |
||
| 1500 | * |
||
| 1501 | * @return bool |
||
| 1502 | */ |
||
| 1503 | public function isTemplate() { |
||
| 1504 | return false; |
||
| 1505 | } |
||
| 1506 | |||
| 1507 | /** |
||
| 1508 | * Get a title of frame |
||
| 1509 | * |
||
| 1510 | * @return Title |
||
| 1511 | */ |
||
| 1512 | public function getTitle() { |
||
| 1513 | return $this->title; |
||
| 1514 | } |
||
| 1515 | |||
| 1516 | /** |
||
| 1517 | * Set the volatile flag |
||
| 1518 | * |
||
| 1519 | * @param bool $flag |
||
| 1520 | */ |
||
| 1521 | public function setVolatile( $flag = true ) { |
||
| 1522 | $this->volatile = $flag; |
||
| 1523 | } |
||
| 1524 | |||
| 1525 | /** |
||
| 1526 | * Get the volatile flag |
||
| 1527 | * |
||
| 1528 | * @return bool |
||
| 1529 | */ |
||
| 1530 | public function isVolatile() { |
||
| 1531 | return $this->volatile; |
||
| 1532 | } |
||
| 1533 | |||
| 1534 | /** |
||
| 1535 | * Set the TTL |
||
| 1536 | * |
||
| 1537 | * @param int $ttl |
||
| 1538 | */ |
||
| 1539 | public function setTTL( $ttl ) { |
||
| 1540 | View Code Duplication | if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) { |
|
| 1541 | $this->ttl = $ttl; |
||
| 1542 | } |
||
| 1543 | } |
||
| 1544 | |||
| 1545 | /** |
||
| 1546 | * Get the TTL |
||
| 1547 | * |
||
| 1548 | * @return int|null |
||
| 1549 | */ |
||
| 1550 | public function getTTL() { |
||
| 1551 | return $this->ttl; |
||
| 1552 | } |
||
| 1553 | } |
||
| 1554 | |||
| 1555 | /** |
||
| 1556 | * Expansion frame with template arguments |
||
| 1557 | * @ingroup Parser |
||
| 1558 | */ |
||
| 1559 | // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps |
||
| 1560 | View Code Duplication | class PPTemplateFrame_DOM extends PPFrame_DOM { |
|
|
0 ignored issues
–
show
This class seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 1561 | // @codingStandardsIgnoreEnd |
||
| 1562 | |||
| 1563 | public $numberedArgs, $namedArgs; |
||
|
0 ignored issues
–
show
|
|||
| 1564 | |||
| 1565 | /** |
||
| 1566 | * @var PPFrame_DOM |
||
| 1567 | */ |
||
| 1568 | public $parent; |
||
| 1569 | public $numberedExpansionCache, $namedExpansionCache; |
||
|
0 ignored issues
–
show
|
|||
| 1570 | |||
| 1571 | /** |
||
| 1572 | * @param Preprocessor $preprocessor |
||
| 1573 | * @param bool|PPFrame_DOM $parent |
||
| 1574 | * @param array $numberedArgs |
||
| 1575 | * @param array $namedArgs |
||
| 1576 | * @param bool|Title $title |
||
| 1577 | */ |
||
| 1578 | public function __construct( $preprocessor, $parent = false, $numberedArgs = [], |
||
| 1579 | $namedArgs = [], $title = false |
||
| 1580 | ) { |
||
| 1581 | parent::__construct( $preprocessor ); |
||
| 1582 | |||
| 1583 | $this->parent = $parent; |
||
|
0 ignored issues
–
show
It seems like
$parent can also be of type boolean. However, the property $parent is declared as type object<PPFrame_DOM>. Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
| 1584 | $this->numberedArgs = $numberedArgs; |
||
| 1585 | $this->namedArgs = $namedArgs; |
||
| 1586 | $this->title = $title; |
||
|
0 ignored issues
–
show
It seems like
$title can also be of type boolean. However, the property $title is declared as type object<Title>. Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
| 1587 | $pdbk = $title ? $title->getPrefixedDBkey() : false; |
||
|
0 ignored issues
–
show
It seems like
$title is not always an object, but can also be of type boolean. Maybe add an additional type check?
If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe: function someFunction(A $objectMaybe = null)
{
if ($objectMaybe instanceof A) {
$objectMaybe->doSomething();
}
}
Loading history...
|
|||
| 1588 | $this->titleCache = $parent->titleCache; |
||
| 1589 | $this->titleCache[] = $pdbk; |
||
| 1590 | $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; |
||
| 1591 | if ( $pdbk !== false ) { |
||
| 1592 | $this->loopCheckHash[$pdbk] = true; |
||
| 1593 | } |
||
| 1594 | $this->depth = $parent->depth + 1; |
||
| 1595 | $this->numberedExpansionCache = $this->namedExpansionCache = []; |
||
| 1596 | } |
||
| 1597 | |||
| 1598 | public function __toString() { |
||
| 1599 | $s = 'tplframe{'; |
||
| 1600 | $first = true; |
||
| 1601 | $args = $this->numberedArgs + $this->namedArgs; |
||
| 1602 | foreach ( $args as $name => $value ) { |
||
| 1603 | if ( $first ) { |
||
| 1604 | $first = false; |
||
| 1605 | } else { |
||
| 1606 | $s .= ', '; |
||
| 1607 | } |
||
| 1608 | $s .= "\"$name\":\"" . |
||
| 1609 | str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"'; |
||
| 1610 | } |
||
| 1611 | $s .= '}'; |
||
| 1612 | return $s; |
||
| 1613 | } |
||
| 1614 | |||
| 1615 | /** |
||
| 1616 | * @throws MWException |
||
| 1617 | * @param string|int $key |
||
| 1618 | * @param string|PPNode_DOM|DOMDocument $root |
||
| 1619 | * @param int $flags |
||
| 1620 | * @return string |
||
| 1621 | */ |
||
| 1622 | public function cachedExpand( $key, $root, $flags = 0 ) { |
||
| 1623 | if ( isset( $this->parent->childExpansionCache[$key] ) ) { |
||
| 1624 | return $this->parent->childExpansionCache[$key]; |
||
| 1625 | } |
||
| 1626 | $retval = $this->expand( $root, $flags ); |
||
| 1627 | if ( !$this->isVolatile() ) { |
||
| 1628 | $this->parent->childExpansionCache[$key] = $retval; |
||
| 1629 | } |
||
| 1630 | return $retval; |
||
| 1631 | } |
||
| 1632 | |||
| 1633 | /** |
||
| 1634 | * Returns true if there are no arguments in this frame |
||
| 1635 | * |
||
| 1636 | * @return bool |
||
| 1637 | */ |
||
| 1638 | public function isEmpty() { |
||
| 1639 | return !count( $this->numberedArgs ) && !count( $this->namedArgs ); |
||
| 1640 | } |
||
| 1641 | |||
| 1642 | public function getArguments() { |
||
| 1643 | $arguments = []; |
||
| 1644 | foreach ( array_merge( |
||
| 1645 | array_keys( $this->numberedArgs ), |
||
| 1646 | array_keys( $this->namedArgs ) ) as $key ) { |
||
| 1647 | $arguments[$key] = $this->getArgument( $key ); |
||
| 1648 | } |
||
| 1649 | return $arguments; |
||
| 1650 | } |
||
| 1651 | |||
| 1652 | public function getNumberedArguments() { |
||
| 1653 | $arguments = []; |
||
| 1654 | foreach ( array_keys( $this->numberedArgs ) as $key ) { |
||
| 1655 | $arguments[$key] = $this->getArgument( $key ); |
||
| 1656 | } |
||
| 1657 | return $arguments; |
||
| 1658 | } |
||
| 1659 | |||
| 1660 | public function getNamedArguments() { |
||
| 1661 | $arguments = []; |
||
| 1662 | foreach ( array_keys( $this->namedArgs ) as $key ) { |
||
| 1663 | $arguments[$key] = $this->getArgument( $key ); |
||
| 1664 | } |
||
| 1665 | return $arguments; |
||
| 1666 | } |
||
| 1667 | |||
| 1668 | /** |
||
| 1669 | * @param int $index |
||
| 1670 | * @return string|bool |
||
| 1671 | */ |
||
| 1672 | public function getNumberedArgument( $index ) { |
||
| 1673 | if ( !isset( $this->numberedArgs[$index] ) ) { |
||
| 1674 | return false; |
||
| 1675 | } |
||
| 1676 | if ( !isset( $this->numberedExpansionCache[$index] ) ) { |
||
| 1677 | # No trimming for unnamed arguments |
||
| 1678 | $this->numberedExpansionCache[$index] = $this->parent->expand( |
||
| 1679 | $this->numberedArgs[$index], |
||
| 1680 | PPFrame::STRIP_COMMENTS |
||
| 1681 | ); |
||
| 1682 | } |
||
| 1683 | return $this->numberedExpansionCache[$index]; |
||
| 1684 | } |
||
| 1685 | |||
| 1686 | /** |
||
| 1687 | * @param string $name |
||
| 1688 | * @return string|bool |
||
| 1689 | */ |
||
| 1690 | public function getNamedArgument( $name ) { |
||
| 1691 | if ( !isset( $this->namedArgs[$name] ) ) { |
||
| 1692 | return false; |
||
| 1693 | } |
||
| 1694 | if ( !isset( $this->namedExpansionCache[$name] ) ) { |
||
| 1695 | # Trim named arguments post-expand, for backwards compatibility |
||
| 1696 | $this->namedExpansionCache[$name] = trim( |
||
| 1697 | $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) ); |
||
| 1698 | } |
||
| 1699 | return $this->namedExpansionCache[$name]; |
||
| 1700 | } |
||
| 1701 | |||
| 1702 | /** |
||
| 1703 | * @param int|string $name |
||
| 1704 | * @return string|bool |
||
| 1705 | */ |
||
| 1706 | public function getArgument( $name ) { |
||
| 1707 | $text = $this->getNumberedArgument( $name ); |
||
| 1708 | if ( $text === false ) { |
||
| 1709 | $text = $this->getNamedArgument( $name ); |
||
| 1710 | } |
||
| 1711 | return $text; |
||
| 1712 | } |
||
| 1713 | |||
| 1714 | /** |
||
| 1715 | * Return true if the frame is a template frame |
||
| 1716 | * |
||
| 1717 | * @return bool |
||
| 1718 | */ |
||
| 1719 | public function isTemplate() { |
||
| 1720 | return true; |
||
| 1721 | } |
||
| 1722 | |||
| 1723 | public function setVolatile( $flag = true ) { |
||
| 1724 | parent::setVolatile( $flag ); |
||
| 1725 | $this->parent->setVolatile( $flag ); |
||
| 1726 | } |
||
| 1727 | |||
| 1728 | public function setTTL( $ttl ) { |
||
| 1729 | parent::setTTL( $ttl ); |
||
| 1730 | $this->parent->setTTL( $ttl ); |
||
| 1731 | } |
||
| 1732 | } |
||
| 1733 | |||
| 1734 | /** |
||
| 1735 | * Expansion frame with custom arguments |
||
| 1736 | * @ingroup Parser |
||
| 1737 | */ |
||
| 1738 | // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps |
||
| 1739 | View Code Duplication | class PPCustomFrame_DOM extends PPFrame_DOM { |
|
|
0 ignored issues
–
show
This class seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 1740 | // @codingStandardsIgnoreEnd |
||
| 1741 | |||
| 1742 | public $args; |
||
| 1743 | |||
| 1744 | public function __construct( $preprocessor, $args ) { |
||
| 1745 | parent::__construct( $preprocessor ); |
||
| 1746 | $this->args = $args; |
||
| 1747 | } |
||
| 1748 | |||
| 1749 | public function __toString() { |
||
| 1750 | $s = 'cstmframe{'; |
||
| 1751 | $first = true; |
||
| 1752 | foreach ( $this->args as $name => $value ) { |
||
| 1753 | if ( $first ) { |
||
| 1754 | $first = false; |
||
| 1755 | } else { |
||
| 1756 | $s .= ', '; |
||
| 1757 | } |
||
| 1758 | $s .= "\"$name\":\"" . |
||
| 1759 | str_replace( '"', '\\"', $value->__toString() ) . '"'; |
||
| 1760 | } |
||
| 1761 | $s .= '}'; |
||
| 1762 | return $s; |
||
| 1763 | } |
||
| 1764 | |||
| 1765 | /** |
||
| 1766 | * @return bool |
||
| 1767 | */ |
||
| 1768 | public function isEmpty() { |
||
| 1769 | return !count( $this->args ); |
||
| 1770 | } |
||
| 1771 | |||
| 1772 | /** |
||
| 1773 | * @param int|string $index |
||
| 1774 | * @return string|bool |
||
| 1775 | */ |
||
| 1776 | public function getArgument( $index ) { |
||
| 1777 | if ( !isset( $this->args[$index] ) ) { |
||
| 1778 | return false; |
||
| 1779 | } |
||
| 1780 | return $this->args[$index]; |
||
| 1781 | } |
||
| 1782 | |||
| 1783 | public function getArguments() { |
||
| 1784 | return $this->args; |
||
| 1785 | } |
||
| 1786 | } |
||
| 1787 | |||
| 1788 | /** |
||
| 1789 | * @ingroup Parser |
||
| 1790 | */ |
||
| 1791 | // @codingStandardsIgnoreStart Squiz.Classes.ValidClassName.NotCamelCaps |
||
| 1792 | class PPNode_DOM implements PPNode { |
||
| 1793 | // @codingStandardsIgnoreEnd |
||
| 1794 | |||
| 1795 | /** |
||
| 1796 | * @var DOMElement |
||
| 1797 | */ |
||
| 1798 | public $node; |
||
| 1799 | public $xpath; |
||
| 1800 | |||
| 1801 | public function __construct( $node, $xpath = false ) { |
||
| 1802 | $this->node = $node; |
||
| 1803 | } |
||
| 1804 | |||
| 1805 | /** |
||
| 1806 | * @return DOMXPath |
||
| 1807 | */ |
||
| 1808 | public function getXPath() { |
||
| 1809 | if ( $this->xpath === null ) { |
||
| 1810 | $this->xpath = new DOMXPath( $this->node->ownerDocument ); |
||
| 1811 | } |
||
| 1812 | return $this->xpath; |
||
| 1813 | } |
||
| 1814 | |||
| 1815 | public function __toString() { |
||
| 1816 | if ( $this->node instanceof DOMNodeList ) { |
||
| 1817 | $s = ''; |
||
| 1818 | foreach ( $this->node as $node ) { |
||
| 1819 | $s .= $node->ownerDocument->saveXML( $node ); |
||
| 1820 | } |
||
| 1821 | } else { |
||
| 1822 | $s = $this->node->ownerDocument->saveXML( $this->node ); |
||
| 1823 | } |
||
| 1824 | return $s; |
||
| 1825 | } |
||
| 1826 | |||
| 1827 | /** |
||
| 1828 | * @return bool|PPNode_DOM |
||
| 1829 | */ |
||
| 1830 | public function getChildren() { |
||
| 1831 | return $this->node->childNodes ? new self( $this->node->childNodes ) : false; |
||
| 1832 | } |
||
| 1833 | |||
| 1834 | /** |
||
| 1835 | * @return bool|PPNode_DOM |
||
| 1836 | */ |
||
| 1837 | public function getFirstChild() { |
||
| 1838 | return $this->node->firstChild ? new self( $this->node->firstChild ) : false; |
||
| 1839 | } |
||
| 1840 | |||
| 1841 | /** |
||
| 1842 | * @return bool|PPNode_DOM |
||
| 1843 | */ |
||
| 1844 | public function getNextSibling() { |
||
| 1845 | return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false; |
||
| 1846 | } |
||
| 1847 | |||
| 1848 | /** |
||
| 1849 | * @param string $type |
||
| 1850 | * |
||
| 1851 | * @return bool|PPNode_DOM |
||
| 1852 | */ |
||
| 1853 | public function getChildrenOfType( $type ) { |
||
| 1854 | return new self( $this->getXPath()->query( $type, $this->node ) ); |
||
| 1855 | } |
||
| 1856 | |||
| 1857 | /** |
||
| 1858 | * @return int |
||
| 1859 | */ |
||
| 1860 | public function getLength() { |
||
| 1861 | if ( $this->node instanceof DOMNodeList ) { |
||
| 1862 | return $this->node->length; |
||
| 1863 | } else { |
||
| 1864 | return false; |
||
| 1865 | } |
||
| 1866 | } |
||
| 1867 | |||
| 1868 | /** |
||
| 1869 | * @param int $i |
||
| 1870 | * @return bool|PPNode_DOM |
||
| 1871 | */ |
||
| 1872 | public function item( $i ) { |
||
| 1873 | $item = $this->node->item( $i ); |
||
| 1874 | return $item ? new self( $item ) : false; |
||
| 1875 | } |
||
| 1876 | |||
| 1877 | /** |
||
| 1878 | * @return string |
||
| 1879 | */ |
||
| 1880 | public function getName() { |
||
| 1881 | if ( $this->node instanceof DOMNodeList ) { |
||
| 1882 | return '#nodelist'; |
||
| 1883 | } else { |
||
| 1884 | return $this->node->nodeName; |
||
| 1885 | } |
||
| 1886 | } |
||
| 1887 | |||
| 1888 | /** |
||
| 1889 | * Split a "<part>" node into an associative array containing: |
||
| 1890 | * - name PPNode name |
||
| 1891 | * - index String index |
||
| 1892 | * - value PPNode value |
||
| 1893 | * |
||
| 1894 | * @throws MWException |
||
| 1895 | * @return array |
||
| 1896 | */ |
||
| 1897 | public function splitArg() { |
||
| 1898 | $xpath = $this->getXPath(); |
||
| 1899 | $names = $xpath->query( 'name', $this->node ); |
||
| 1900 | $values = $xpath->query( 'value', $this->node ); |
||
| 1901 | if ( !$names->length || !$values->length ) { |
||
| 1902 | throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); |
||
| 1903 | } |
||
| 1904 | $name = $names->item( 0 ); |
||
| 1905 | $index = $name->getAttribute( 'index' ); |
||
| 1906 | return [ |
||
| 1907 | 'name' => new self( $name ), |
||
| 1908 | 'index' => $index, |
||
| 1909 | 'value' => new self( $values->item( 0 ) ) ]; |
||
| 1910 | } |
||
| 1911 | |||
| 1912 | /** |
||
| 1913 | * Split an "<ext>" node into an associative array containing name, attr, inner and close |
||
| 1914 | * All values in the resulting array are PPNodes. Inner and close are optional. |
||
| 1915 | * |
||
| 1916 | * @throws MWException |
||
| 1917 | * @return array |
||
| 1918 | */ |
||
| 1919 | public function splitExt() { |
||
| 1920 | $xpath = $this->getXPath(); |
||
| 1921 | $names = $xpath->query( 'name', $this->node ); |
||
| 1922 | $attrs = $xpath->query( 'attr', $this->node ); |
||
| 1923 | $inners = $xpath->query( 'inner', $this->node ); |
||
| 1924 | $closes = $xpath->query( 'close', $this->node ); |
||
| 1925 | if ( !$names->length || !$attrs->length ) { |
||
| 1926 | throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); |
||
| 1927 | } |
||
| 1928 | $parts = [ |
||
| 1929 | 'name' => new self( $names->item( 0 ) ), |
||
| 1930 | 'attr' => new self( $attrs->item( 0 ) ) ]; |
||
| 1931 | if ( $inners->length ) { |
||
| 1932 | $parts['inner'] = new self( $inners->item( 0 ) ); |
||
| 1933 | } |
||
| 1934 | if ( $closes->length ) { |
||
| 1935 | $parts['close'] = new self( $closes->item( 0 ) ); |
||
| 1936 | } |
||
| 1937 | return $parts; |
||
| 1938 | } |
||
| 1939 | |||
| 1940 | /** |
||
| 1941 | * Split a "<h>" node |
||
| 1942 | * @throws MWException |
||
| 1943 | * @return array |
||
| 1944 | */ |
||
| 1945 | public function splitHeading() { |
||
| 1946 | if ( $this->getName() !== 'h' ) { |
||
| 1947 | throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); |
||
| 1948 | } |
||
| 1949 | return [ |
||
| 1950 | 'i' => $this->node->getAttribute( 'i' ), |
||
| 1951 | 'level' => $this->node->getAttribute( 'level' ), |
||
| 1952 | 'contents' => $this->getChildren() |
||
| 1953 | ]; |
||
| 1954 | } |
||
| 1955 | } |
||
| 1956 |
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:
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
Check for existence of the variable explicitly:
Define a default value for the variable:
Add a value for the missing path: