X-Git-Url: http://git.cyclocoop.org/%7B%24admin_url%7Dmembres/cotisations/gestion/rappel_supprimer.php?a=blobdiff_plain;f=includes%2Fparser%2FParser.php;h=b0f8235006be9509f0b8c303d87557fb41235a66;hb=3e11266ae5e5a33498abb7f028bbc79b5ca93515;hp=3f9dccfd432e6a2ad1c58f5b42044e566da0aa4d;hpb=b974497da87c899ed96009003f39860a5e9703cd;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 3f9dccfd43..b0f8235006 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -34,10 +34,10 @@ * Globals used: * objects: $wgLang, $wgContLang * - * NOT $wgArticle, $wgUser or $wgTitle. Keep them away! + * NOT $wgUser or $wgTitle. Keep them away! * * settings: - * $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*, + * $wgUseDynamicDates*, $wgInterwikiMagic*, * $wgNamespacesWithSubpages, $wgAllowExternalImages*, * $wgLocaltimezone, $wgAllowSpecialInclusion*, * $wgMaxArticleSize* @@ -53,7 +53,7 @@ class Parser { * changes in an incompatible way, so the parser cache * can automatically discard old data. */ - const VERSION = '1.6.4'; + const VERSION = '1.6.5'; /** * Update this version number when the output of serialiseHalfParsedText() @@ -113,7 +113,11 @@ class Parser { var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor # Cleared with clearState(): - var $mOutput, $mAutonumber, $mDTopen; + /** + * @var ParserOutput + */ + var $mOutput; + var $mAutonumber, $mDTopen; /** * @var StripState @@ -154,10 +158,8 @@ class Parser { /** * Constructor - * - * @public */ - function __construct( $conf = array() ) { + public function __construct( $conf = array() ) { $this->mConf = $conf; $this->mUrlProtocols = wfUrlProtocols(); $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'. @@ -180,7 +182,7 @@ class Parser { */ function __destruct() { if ( isset( $this->mLinkHolders ) ) { - $this->mLinkHolders->__destruct(); + unset( $this->mLinkHolders ); } foreach ( $this as $name => $value ) { unset( $this->$name ); @@ -276,7 +278,7 @@ class Parser { * Do not call this function recursively. * * @param $text String: text we want to parse - * @param $title A title object + * @param $title Title object * @param $options ParserOptions * @param $linestart boolean * @param $clearState boolean @@ -493,8 +495,6 @@ class Parser { /** * Get a random string - * - * @static */ static public function getRandomString() { return dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) ); @@ -517,7 +517,7 @@ class Parser { */ public function uniqPrefix() { if ( !isset( $this->mUniqPrefix ) ) { - # @todo Fixme: this is probably *horribly wrong* + # @todo FIXME: This is probably *horribly wrong* # LanguageConverter seems to want $wgParser's uniqPrefix, however # if this is called for a parser cache hit, the parser may not # have ever been initialized in the first place. @@ -682,10 +682,8 @@ class Parser { * @param $matches Out parameter, Array: extracted tags * @param $uniq_prefix * @return String: stripped text - * - * @static */ - public function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) { + public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) { static $n = 1; $stripped = ''; $matches = array(); @@ -754,41 +752,6 @@ class Parser { return $this->mStripList; } - /** - * @deprecated use replaceVariables - */ - function strip( $text, $state, $stripcomments = false , $dontstrip = array() ) { - return $text; - } - - /** - * Restores pre, math, and other extensions removed by strip() - * - * always call unstripNoWiki() after this one - * @private - * @deprecated use $this->mStripState->unstrip() - */ - function unstrip( $text, $state ) { - return $state->unstripGeneral( $text ); - } - - /** - * Always call this after unstrip() to preserve the order - * - * @private - * @deprecated use $this->mStripState->unstrip() - */ - function unstripNoWiki( $text, $state ) { - return $state->unstripNoWiki( $text ); - } - - /** - * @deprecated use $this->mStripState->unstripBoth() - */ - function unstripForHTML( $text ) { - return $this->mStripState->unstripBoth( $text ); - } - /** * Add an item to the strip state * Returns the unique tag which must be inserted into the stripped text @@ -803,15 +766,6 @@ class Parser { return $rnd; } - /** - * Interface with html tidy - * @deprecated Use MWTidy::tidy() - */ - public static function tidy( $text ) { - wfDeprecated( __METHOD__ ); - return MWTidy::tidy( $text ); - } - /** * parse the wiki syntax used to render tables * @@ -822,189 +776,301 @@ class Parser { $lines = StringUtils::explode( "\n", $text ); $out = ''; - $td_history = array(); # Is currently a td tag open? - $last_tag_history = array(); # Save history of last lag activated (td, th or caption) - $tr_history = array(); # Is currently a tr tag open? - $tr_attributes = array(); # history of tr attributes - $has_opened_tr = array(); # Did this table open a element? - $indent_level = 0; # indent level of the table + $output =& $out; foreach ( $lines as $outLine ) { $line = trim( $outLine ); - if ( $line === '' ) { # empty line, go to next line - $out .= $outLine."\n"; + # empty line, go to next line, + # but only append \n if outside of table + if ( $line === '') { + $output .= $outLine . "\n"; continue; } - - $first_character = $line[0]; + $firstChars = $line[0]; + if ( strlen( $line ) > 1 ) { + $firstChars .= in_array( $line[1], array( '}', '+', '-' ) ) ? $line[1] : ''; + } $matches = array(); - if ( preg_match( '/^(:*)\{\|(.*)$/', $line , $matches ) ) { - # First check if we are starting a new table - $indent_level = strlen( $matches[1] ); + if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line , $matches ) ) { + $tables[] = array(); + $table =& $this->last( $tables ); + $table[0] = array(); // first row + $currentRow =& $table[0]; + $table['indent'] = strlen( $matches[1] ); $attributes = $this->mStripState->unstripBoth( $matches[2] ); $attributes = Sanitizer::fixTagAttributes( $attributes , 'table' ); - $outLine = str_repeat( '
' , $indent_level ) . ""; - array_push( $td_history , false ); - array_push( $last_tag_history , '' ); - array_push( $tr_history , false ); - array_push( $tr_attributes , '' ); - array_push( $has_opened_tr , false ); - } elseif ( count( $td_history ) == 0 ) { - # Don't do any of the following - $out .= $outLine."\n"; - continue; - } elseif ( substr( $line , 0 , 2 ) === '|}' ) { - # We are ending a table - $line = '' . substr( $line , 2 ); - $last_tag = array_pop( $last_tag_history ); + if ( $attributes !== '' ) { + $table['attributes'] = $attributes; + } + } else if ( !isset( $tables[0] ) ) { + // we're outside the table + + $out .= $outLine . "\n"; + } else if ( $firstChars === '|}' ) { + // trim the |} code from the line + $line = substr ( $line , 2 ); + + // Shorthand for last row + $lastRow =& $this->last( $table ); + + // a thead at the end becomes a tfoot, unless there is only one row + // Do this before deleting empty last lines to allow headers at the bottom of tables + if ( isset( $lastRow['type'] ) && $lastRow['type'] == 'thead' && isset( $table[1] ) ) { + $lastRow['type'] = 'tfoot'; + for ( $i = 0; isset( $lastRow[$i] ); $i++ ) { + $lastRow[$i]['type'] = 'th'; + } + } - if ( !array_pop( $has_opened_tr ) ) { - $line = "{$line}"; + // Delete empty last lines + if ( empty( $lastRow ) ) { + $lastRow = NULL; } + $o = ''; + $curtable = array_pop( $tables ); - if ( array_pop( $tr_history ) ) { - $line = "{$line}"; + #Add a line-ending before the table, but only if there isn't one already + if ( substr( $out, -1 ) !== "\n" ) { + $o .= "\n"; } + $o .= $this->generateTableHTML( $curtable ) . $line . "\n"; + + if ( count( $tables ) > 0 ) { + $table =& $this->last( $tables ); + $currentRow =& $this->last( $table ); + $currentElement =& $this->last( $currentRow ); - if ( array_pop( $td_history ) ) { - $line = "{$line}"; + $output =& $currentElement['content']; + } else { + $output =& $out; } - array_pop( $tr_attributes ); - $outLine = $line . str_repeat( '
' , $indent_level ); - } elseif ( substr( $line , 0 , 2 ) === '|-' ) { - # Now we have a table row - $line = preg_replace( '#^\|-+#', '', $line ); - # Whats after the tag is now only attributes + $output .= $o; + + } else if ( $firstChars === '|-' ) { + // start a new row element + // but only when we haven't started one already + if ( count( $currentRow ) != 0 ) { + $table[] = array(); + $currentRow =& $this->last( $table ); + } + // Get the attributes, there's nothing else useful in $line now + $line = substr ( $line , 2 ); $attributes = $this->mStripState->unstripBoth( $line ); $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' ); - array_pop( $tr_attributes ); - array_push( $tr_attributes, $attributes ); - - $line = ''; - $last_tag = array_pop( $last_tag_history ); - array_pop( $has_opened_tr ); - array_push( $has_opened_tr , true ); - - if ( array_pop( $tr_history ) ) { - $line = ''; + if ( $attributes !== '' ) { + $currentRow['attributes'] = $attributes; } - if ( array_pop( $td_history ) ) { - $line = "{$line}"; + } else if ( $firstChars === '|+' ) { + // a table caption, but only proceed if there isn't one already + if ( !isset ( $table['caption'] ) ) { + $line = substr ( $line , 2 ); + + $c = $this->getCellAttr( $line , 'caption' ); + $table['caption'] = array(); + $table['caption']['content'] = $c[0]; + if ( isset( $c[1] ) ) $table['caption']['attributes'] = $c[1]; + unset( $c ); + $output =& $table['caption']['content']; + } + } else if ( $firstChars === '|' || $firstChars === '!' || $firstChars === '!+' ) { + // Which kind of cells are we dealing with + $currentTag = 'td'; + $line = substr ( $line , 1 ); + + if ( $firstChars === '!' || $firstChars === '!+' ) { + $line = str_replace ( '!!' , '||' , $line ); + $currentTag = 'th'; } - $outLine = $line; - array_push( $tr_history , false ); - array_push( $td_history , false ); - array_push( $last_tag_history , '' ); - } elseif ( $first_character === '|' || $first_character === '!' || substr( $line , 0 , 2 ) === '|+' ) { - # This might be cell elements, td, th or captions - if ( substr( $line , 0 , 2 ) === '|+' ) { - $first_character = '+'; - $line = substr( $line , 1 ); + // Split up multiple cells on the same line. + $cells = StringUtils::explodeMarkup( '||' , $line ); + $line = ''; // save memory + + // decide whether thead to tbody + if ( !array_key_exists( 'type', $currentRow ) ) { + $currentRow['type'] = ( $firstChars === '!' ) ? 'thead' : 'tbody' ; + } else if ( $firstChars === '|' ) { + $currentRow['type'] = 'tbody'; } - $line = substr( $line , 1 ); + // Loop through each table cell + foreach ( $cells as $cell ) { + // a new cell + $currentRow[] = array(); + $currentElement =& $this->last( $currentRow ); + + $currentElement['type'] = $currentTag; - if ( $first_character === '!' ) { - $line = str_replace( '!!' , '||' , $line ); + $c = $this->getCellAttr( $cell , $currentTag ); + $currentElement['content'] = $c[0]; + if ( isset( $c[1] ) ) $currentElement['attributes'] = $c[1]; + unset( $c ); } + $output =& $currentElement['content']; - # Split up multiple cells on the same line. - # FIXME : This can result in improper nesting of tags processed - # by earlier parser steps, but should avoid splitting up eg - # attribute values containing literal "||". - $cells = StringUtils::explodeMarkup( '||' , $line ); + } else { + $output .= "\n$outLine"; + } + } - $outLine = ''; + # Remove trailing line-ending (b/c) + if ( substr( $out, -1 ) === "\n" ) { + $out = substr( $out, 0, -1 ); + } - # Loop through each table cell - foreach ( $cells as $cell ) { - $previous = ''; - if ( $first_character !== '+' ) { - $tr_after = array_pop( $tr_attributes ); - if ( !array_pop( $tr_history ) ) { - $previous = "\n"; - } - array_push( $tr_history , true ); - array_push( $tr_attributes , '' ); - array_pop( $has_opened_tr ); - array_push( $has_opened_tr , true ); - } + # Close any unclosed tables + if ( isset( $tables ) && count( $tables ) > 0 ) { + for ( $i = 0; $i < count( $tables ); $i++ ) { + $curtable = array_pop( $tables ); + $curtable = $this->generateTableHTML( $curtable ); + #Add a line-ending before the table, but only if there isn't one already + if ( substr( $out, -1 ) !== "\n" && $curtable !== "" ) { + $out .= "\n"; + } + $out .= $curtable; + } + } - $last_tag = array_pop( $last_tag_history ); + wfProfileOut( __METHOD__ ); - if ( array_pop( $td_history ) ) { - $previous = "\n{$previous}"; - } + return $out; + } - if ( $first_character === '|' ) { - $last_tag = 'td'; - } elseif ( $first_character === '!' ) { - $last_tag = 'th'; - } elseif ( $first_character === '+' ) { - $last_tag = 'caption'; - } else { - $last_tag = ''; - } + /** + * Helper function for doTableStuff() separating the contents of cells from + * attributes. Particularly useful as there's a possible bug and this action + * is repeated twice. + * + * @private + */ + function getCellAttr ( $cell, $tagName ) { + $content = null; + $attributes = null; - array_push( $last_tag_history , $last_tag ); + $cell = trim ( $cell ); - # A cell could contain both parameters and data - $cell_data = explode( '|' , $cell , 2 ); + // A cell could contain both parameters and data + $cellData = explode ( '|' , $cell , 2 ); - # Bug 553: Note that a '|' inside an invalid link should not - # be mistaken as delimiting cell parameters - if ( strpos( $cell_data[0], '[[' ) !== false ) { - $cell = "{$previous}<{$last_tag}>{$cell}"; - } elseif ( count( $cell_data ) == 1 ) { - $cell = "{$previous}<{$last_tag}>{$cell_data[0]}"; - } else { - $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); - $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag ); - $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; - } + // Bug 553: Note that a '|' inside an invalid link should not + // be mistaken as delimiting cell parameters + if ( strpos( $cellData[0], '[[' ) !== false ) { + $content = trim ( $cell ); + } + else if ( count ( $cellData ) == 1 ) { + $content = trim ( $cellData[0] ); + } + else { + $attributes = $this->mStripState->unstripBoth( $cellData[0] ); + $attributes = Sanitizer::fixTagAttributes( $attributes , $tagName ); - $outLine .= $cell; - array_push( $td_history , true ); - } - } - $out .= $outLine . "\n"; + $content = trim ( $cellData[1] ); } + return array( $content, $attributes ); + } + - # Closing open td, tr && table - while ( count( $td_history ) > 0 ) { - if ( array_pop( $td_history ) ) { - $out .= "\n"; + /** + * Helper function for doTableStuff(). This converts the structured array into html. + * + * @private + */ + function generateTableHTML ( &$table ) { + $return = ""; + $return .= str_repeat( '
' , $table['indent'] ); + $return .= ''; } - $out .= "\n"; - } + $return .= "\n'; + unset( $table[$i][$j] ); + } + $return .= "\n"; - # Remove trailing line-ending (b/c) - if ( substr( $out, -1 ) === "\n" ) { - $out = substr( $out, 0, -1 ); + if ( ( !isset( $table[$i + 1] ) && !$simple ) || ( isset( $table[$i + 1] ) && isset( $table[$i + 1]['type'] ) && $table[$i]['type'] != $table[$i + 1]['type'] ) ) { + $return .= ''; + } + $lastSection = $table[$i]['type']; + unset( $table[$i] ); } - - # special case: don't return empty table - if ( $out === "\n\n
" ) { - $out = ''; + if ( $empty ) { + if ( isset( $table['caption'] ) ) { + $return .= "\n"; + } else { + return ''; + } } + $return .= "\n"; + $return .= str_repeat( '
' , $table['indent'] ); - wfProfileOut( __METHOD__ ); + return $return; + } - return $out; + /** + * like end() but only works on the numeric array index and php's internal pointers + * returns a reference to the last element of an array much like "\$arr[-1]" in perl + * ignores associative elements and will create a 0 key will a NULL value if there were + * no numric elements and an array itself if not previously defined. + * + * @private + */ + function &last ( &$arr ) { + for ( $i = count( $arr ); ( !isset( $arr[$i] ) && $i > 0 ); $i-- ) { } + return $arr[$i]; } /** @@ -1127,8 +1193,7 @@ class Parser { substr( $m[0], 0, 20 ) . '"' ); } $url = wfMsgForContent( $urlmsg, $id ); - $sk = $this->mOptions->getSkin( $this->mTitle ); - return $sk->makeExternalLink( $url, "{$keyword} {$id}", true, $CssClass ); + return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $CssClass ); } elseif ( isset( $m[5] ) && $m[5] !== '' ) { # ISBN $isbn = $m[5]; @@ -1155,7 +1220,6 @@ class Parser { global $wgContLang; wfProfileIn( __METHOD__ ); - $sk = $this->mOptions->getSkin( $this->mTitle ); $trail = ''; # The characters '<' and '>' (which were escaped by @@ -1186,7 +1250,7 @@ class Parser { $text = $this->maybeMakeExternalImage( $url ); if ( $text === false ) { # Not an image, make a link - $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free', + $text = Linker::makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free', $this->getExternalLinkAttribs( $url ) ); # Register it in the output object... # Replace unnecessary URL escape codes with their equivalent characters @@ -1402,8 +1466,6 @@ class Parser { global $wgContLang; wfProfileIn( __METHOD__ ); - $sk = $this->mOptions->getSkin( $this->mTitle ); - $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE ); $s = array_shift( $bits ); @@ -1461,7 +1523,7 @@ class Parser { # This means that users can paste URLs directly into the text # Funny characters like ö aren't valid in URLs anyway # This was changed in August 2004 - $s .= $sk->makeExternalLink( $url, $text, false, $linktype, + $s .= Linker::makeExternalLink( $url, $text, false, $linktype, $this->getExternalLinkAttribs( $url ) ) . $dtrail . $trail; # Register link in the output object. @@ -1511,7 +1573,6 @@ class Parser { return $attribs; } - /** * Replace unusual URL escape codes with their equivalent characters * @@ -1551,7 +1612,6 @@ class Parser { * @private */ function maybeMakeExternalImage( $url ) { - $sk = $this->mOptions->getSkin( $this->mTitle ); $imagesfrom = $this->mOptions->getAllowExternalImagesFrom(); $imagesexception = !empty( $imagesfrom ); $text = false; @@ -1573,7 +1633,7 @@ class Parser { || ( $imagesexception && $imagematch ) ) { if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) { # Image found - $text = $sk->makeExternalImage( $url ); + $text = Linker::makeExternalImage( $url ); } } if ( !$text && $this->mOptions->getEnableImageWhitelist() @@ -1586,7 +1646,7 @@ class Parser { } if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) { # Image matches a whitelist entry - $text = $sk->makeExternalImage( $url ); + $text = Linker::makeExternalImage( $url ); break; } } @@ -1627,7 +1687,6 @@ class Parser { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; } - $sk = $this->mOptions->getSkin( $this->mTitle ); $holders = new LinkHolderArray( $this ); # split the entire text string on occurences of [[ @@ -1862,14 +1921,13 @@ class Parser { $holders->merge( $this->replaceInternalLinks2( $text ) ); } # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them - $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text, $holders ) ) . $trail; + $s .= $prefix . $this->armorLinks( + $this->makeImage( $nt, $text, $holders ) ) . $trail; } else { $s .= $prefix . $trail; } - $this->mOutput->addImage( $nt->getDBkey() ); wfProfileOut( __METHOD__."-image" ); continue; - } if ( $ns == NS_CATEGORY ) { @@ -1900,26 +1958,24 @@ class Parser { # Self-link checking if ( $nt->getFragment() === '' && $ns != NS_SPECIAL ) { if ( in_array( $nt->getPrefixedText(), $selflink, true ) ) { - $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail ); + $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail ); continue; } } # NS_MEDIA is a pseudo-namespace for linking directly to a file - # FIXME: Should do batch file existence checks, see comment below + # @todo FIXME: Should do batch file existence checks, see comment below if ( $ns == NS_MEDIA ) { wfProfileIn( __METHOD__."-media" ); # Give extensions a chance to select the file revision for us - $skip = $time = false; - wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$nt, &$skip, &$time ) ); - if ( $skip ) { - $link = $sk->link( $nt ); - } else { - $link = $sk->makeMediaLinkObj( $nt, $text, $time ); - } + $time = $sha1 = $descQuery = false; + wfRunHooks( 'BeforeParserFetchFileAndTitle', + array( $this, $nt, &$time, &$sha1, &$descQuery ) ); + # Fetch and register the file (file title may be different via hooks) + list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $time, $sha1 ); # Cloak with NOPARSE to avoid replacement in replaceExternalLinks - $s .= $prefix . $this->armorLinks( $link ) . $trail; - $this->mOutput->addImage( $nt->getDBkey() ); + $s .= $prefix . $this->armorLinks( + Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail; wfProfileOut( __METHOD__."-media" ); continue; } @@ -1928,7 +1984,7 @@ class Parser { # Some titles, such as valid special pages or files in foreign repos, should # be shown as bluelinks even though they're not included in the page table # - # FIXME: isAlwaysKnown() can be expensive for file links; we should really do + # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do # batch file existence checks for NS_FILE and NS_MEDIA if ( $iw == '' && $nt->isAlwaysKnown() ) { $this->mOutput->addLink( $nt ); @@ -1943,18 +1999,6 @@ class Parser { return $holders; } - /** - * Make a link placeholder. The text returned can be later resolved to a real link with - * replaceLinkHolders(). This is done for two reasons: firstly to avoid further - * parsing of interwiki links, and secondly to allow all existence checks and - * article length checks (for stub links) to be bundled into a single query. - * - * @deprecated - */ - function makeLinkHolder( &$nt, $text = '', $query = array(), $trail = '', $prefix = '' ) { - return $this->mLinkHolders->makeHolder( $nt, $text, $query, $trail, $prefix ); - } - /** * Render a forced-blue link inline; protect against double expansion of * URLs if we're in a mode that prepends full URL prefixes to internal links. @@ -1975,14 +2019,11 @@ class Parser { if ( is_string( $query ) ) { $query = wfCgiToArray( $query ); } - - $sk = $this->mOptions->getSkin( $this->mTitle ); - if ( $text == '' ) { - $text = $sk->linkText( $title ); + $text = htmlspecialchars( $nt->getPrefixedText() ); } - $link = $sk->linkKnown( $nt, "$prefix$text$inside", array(), $query ); + $link = Linker::linkKnown( $nt, "$prefix$text$inside", array(), $query ); return $this->armorLinks( $link ) . $trail; } @@ -2026,6 +2067,8 @@ class Parser { /**#@+ * Used by doBlockLevels() * @private + * + * @return string */ function closeParagraph() { $result = ''; @@ -2050,7 +2093,7 @@ class Parser { } for ( $i = 0; $i < $shorter; ++$i ) { - if ( $st1{$i} != $st2{$i} ) { + if ( $st1[$i] != $st2[$i] ) { break; } } @@ -2061,6 +2104,8 @@ class Parser { * These next three functions open, continue, and close the list * element appropriate to the prefix character passed into them. * @private + * + * @return string */ function openList( $char ) { $result = $this->closeParagraph(); @@ -2085,6 +2130,8 @@ class Parser { * TODO: document * @param $char String * @private + * + * @return string */ function nextItem( $char ) { if ( '*' === $char || '#' === $char ) { @@ -2109,6 +2156,8 @@ class Parser { * TODO: document * @param $char String * @private + * + * @return string */ function closeList( $char ) { if ( '*' === $char ) { @@ -2227,7 +2276,7 @@ class Parser { $output .= $this->openList( $char ); if ( ';' === $char ) { - # FIXME: This is dupe of code above + # @todo FIXME: This is dupe of code above if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) { $t = $t2; $output .= $term . $this->nextItem( ':' ); @@ -2249,7 +2298,7 @@ class Parser { 'mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t ); if ( $openmatch or $closematch ) { $paragraphStack = false; - # TODO bug 5718: paragraph closed + # TODO bug 5718: paragraph closed $output .= $this->closeParagraph(); if ( $preOpenMatch and !$preCloseMatch ) { $this->mInPre = true; @@ -2348,7 +2397,7 @@ class Parser { $stack = 0; $len = strlen( $str ); for( $i = 0; $i < $len; $i++ ) { - $c = $str{$i}; + $c = $str[$i]; switch( $state ) { # (Using the number is a performance hack for common cases) @@ -2484,6 +2533,9 @@ class Parser { * Return value of a magic variable (like PAGENAME) * * @private + * + * @param $index integer + * @param $frame PPFrame */ function getVariableValue( $index, $frame=false ) { global $wgContLang, $wgSitename, $wgServer; @@ -2832,6 +2884,8 @@ class Parser { * dependency requirements. * * @private + * + * @return PPNode */ function preprocessToDom( $text, $flags = 0 ) { $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags ); @@ -2840,6 +2894,8 @@ class Parser { /** * Return a three-element array: leading whitespace, string contents, trailing whitespace + * + * @return array */ public static function splitWhitespace( $s ) { $ltrimmed = ltrim( $s ); @@ -2870,6 +2926,8 @@ class Parser { * Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly. * @param $argsOnly Boolean: only do argument (triple-brace) expansion, not double-brace expansion * @private + * + * @return string */ function replaceVariables( $text, $frame = false, $argsOnly = false ) { # Is there any text? Also, Prevent too big inclusions! @@ -2893,7 +2951,11 @@ class Parser { return $text; } - # Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. + /** + * Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. + * + * @return array + */ static function createAssocArgs( $args ) { $assocArgs = array(); $index = 1; @@ -2979,10 +3041,11 @@ class Parser { $originalTitle = $part1; # $args is a list of argument nodes, starting from index 0, not including $part1 - # *** FIXME if piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object + # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object $args = ( null == $piece['parts'] ) ? array() : $piece['parts']; wfProfileOut( __METHOD__.'-setup' ); - + wfProfileIn( __METHOD__."-title-$originalTitle" ); + # SUBST wfProfileIn( __METHOD__.'-modifiers' ); if ( !$found ) { @@ -3150,7 +3213,7 @@ class Parser { && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) { - $text = SpecialPage::capturePath( $title ); + $text = SpecialPageFactory::capturePath( $title ); if ( is_string( $text ) ) { $found = true; $isHTML = true; @@ -3200,6 +3263,7 @@ class Parser { # Recover the source wikitext and return it if ( !$found ) { $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args ); + wfProfileOut( __METHOD__."-title-$originalTitle" ); wfProfileOut( __METHOD__ ); return array( 'object' => $text ); } @@ -3268,6 +3332,7 @@ class Parser { $ret = array( 'text' => $text ); } + wfProfileOut( __METHOD__."-title-$originalTitle" ); wfProfileOut( __METHOD__ ); return $ret; } @@ -3275,6 +3340,8 @@ class Parser { /** * Get the semi-parsed DOM representation of a template with a given title, * and its redirect destination title. Cached. + * + * @return array */ function getTemplateDom( $title ) { $cacheTitle = $title; @@ -3310,6 +3377,8 @@ class Parser { /** * Fetch the unparsed text of a template and register a reference to it. + * @param Title $title + * @return Array ( string or false, Title ) */ function fetchTemplateAndTitle( $title ) { $templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate() @@ -3324,6 +3393,11 @@ class Parser { return array( $text, $finalTitle ); } + /** + * Fetch the unparsed text of a template and register a reference to it. + * @param Title $title + * @return mixed string or false + */ function fetchTemplate( $title ) { $rv = $this->fetchTemplateAndTitle( $title ); return $rv[0]; @@ -3332,8 +3406,10 @@ class Parser { /** * Static function to get a template * Can be overridden via ParserOptions::setTemplateCallback(). + * + * @return array */ - static function statelessFetchTemplate( $title, $parser=false ) { + static function statelessFetchTemplate( $title, $parser = false ) { $text = $skip = false; $finalTitle = $title; $deps = array(); @@ -3342,17 +3418,22 @@ class Parser { for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) { # Give extensions a chance to select the revision instead $id = false; # Assume current - wfRunHooks( 'BeforeParserFetchTemplateAndtitle', array( $parser, &$title, &$skip, &$id ) ); + wfRunHooks( 'BeforeParserFetchTemplateAndtitle', + array( $parser, $title, &$skip, &$id ) ); if ( $skip ) { $text = false; $deps[] = array( - 'title' => $title, - 'page_id' => $title->getArticleID(), - 'rev_id' => null ); + 'title' => $title, + 'page_id' => $title->getArticleID(), + 'rev_id' => null + ); break; } - $rev = $id ? Revision::newFromId( $id ) : Revision::newFromTitle( $title ); + # Get the revision + $rev = $id + ? Revision::newFromId( $id ) + : Revision::newFromTitle( $title ); $rev_id = $rev ? $rev->getId() : 0; # If there is no current revision, there is no page if ( $id === false && !$rev ) { @@ -3361,9 +3442,16 @@ class Parser { } $deps[] = array( - 'title' => $title, - 'page_id' => $title->getArticleID(), - 'rev_id' => $rev_id ); + 'title' => $title, + 'page_id' => $title->getArticleID(), + 'rev_id' => $rev_id ); + if ( $rev && !$title->equals( $rev->getTitle() ) ) { + # We fetched a rev from a different title; register it too... + $deps[] = array( + 'title' => $rev->getTitle(), + 'page_id' => $rev->getPage(), + 'rev_id' => $rev_id ); + } if ( $rev ) { $text = $rev->getText(); @@ -3391,8 +3479,48 @@ class Parser { 'deps' => $deps ); } + /** + * Fetch a file and its title and register a reference to it. + * @param Title $title + * @param string $time MW timestamp + * @param string $sha1 base 36 SHA-1 + * @return mixed File or false + */ + function fetchFile( $title, $time = false, $sha1 = false ) { + $res = $this->fetchFileAndTitle( $title, $time, $sha1 ); + return $res[0]; + } + + /** + * Fetch a file and its title and register a reference to it. + * @param Title $title + * @param string $time MW timestamp + * @param string $sha1 base 36 SHA-1 + * @return Array ( File or false, Title of file ) + */ + function fetchFileAndTitle( $title, $time = false, $sha1 = false ) { + if ( $time === '0' ) { + $file = false; // broken thumbnail forced by hook + } elseif ( $sha1 ) { // get by (sha1,timestamp) + $file = RepoGroup::singleton()->findFileFromKey( $sha1, array( 'time' => $time ) ); + } else { // get by (name,timestamp) + $file = wfFindFile( $title, array( 'time' => $time ) ); + } + $time = $file ? $file->getTimestamp() : false; + $sha1 = $file ? $file->getSha1() : false; + # Register the file as a dependency... + $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 ); + if ( $file && !$title->equals( $file->getTitle() ) ) { + # Update fetched file title + $title = $file->getTitle(); + } + return array( $file, $title ); + } + /** * Transclude an interwiki link. + * + * @return string */ function interwikiTransclude( $title, $action ) { global $wgEnableScaryTranscluding; @@ -3409,6 +3537,10 @@ class Parser { return $this->fetchScaryTemplateMaybeFromCache( $url ); } + /** + * @param $url string + * @return Mixed|String + */ function fetchScaryTemplateMaybeFromCache( $url ) { global $wgTranscludeCacheExpiry; $dbr = wfGetDB( DB_SLAVE ); @@ -3433,10 +3565,14 @@ class Parser { return $text; } - /** * Triple brace replacement -- used for template arguments * @private + * + * @param $peice array + * @param $frame PPFrame + * + * @return array */ function argSubstitution( $piece, $frame ) { wfProfileIn( __METHOD__ ); @@ -3490,6 +3626,8 @@ class Parser { * inner Contents of extension element * noClose Original text did not have a close tag * @param $frame PPFrame + * + * @return string */ function extensionSubstitution( $params, $frame ) { $name = $frame->expand( $params['name'] ); @@ -3632,7 +3770,7 @@ class Parser { } # (bug 8068) Allow control over whether robots index a page. # - # FIXME (bug 14899): __INDEX__ always overrides __NOINDEX__ here! This + # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here! This # is not desirable, the last one on the page should win. if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) { $this->mOutput->setIndexPolicy( 'noindex' ); @@ -3734,9 +3872,6 @@ class Parser { $enoughToc = true; } - # We need this to perform operations on the HTML - $sk = $this->mOptions->getSkin( $this->mTitle ); - # headline counter $headlineCount = 0; $numVisible = 0; @@ -3787,7 +3922,7 @@ class Parser { $sublevelCount[$toclevel] = 0; if ( $toclevel<$wgMaxTocLevel ) { $prevtoclevel = $toclevel; - $toc .= $sk->tocIndent(); + $toc .= Linker::tocIndent(); $numVisible++; } } elseif ( $level < $prevlevel && $toclevel > 1 ) { @@ -3810,16 +3945,16 @@ class Parser { if ( $toclevel<$wgMaxTocLevel ) { if ( $prevtoclevel < $wgMaxTocLevel ) { # Unindent only if the previous toc level was shown :p - $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel ); + $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel ); $prevtoclevel = $toclevel; } else { - $toc .= $sk->tocLineEnd(); + $toc .= Linker::tocLineEnd(); } } } else { # No change in level, end TOC line if ( $toclevel<$wgMaxTocLevel ) { - $toc .= $sk->tocLineEnd(); + $toc .= Linker::tocLineEnd(); } } @@ -3891,7 +4026,7 @@ class Parser { # HTML names must be case-insensitively unique (bug 10721). # This does not apply to Unicode characters per # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison - # FIXME: We may be changing them depending on the current locale. + # @todo FIXME: We may be changing them depending on the current locale. $arrayKey = strtolower( $safeHeadline ); if ( $legacyHeadline === false ) { $legacyArrayKey = false; @@ -3927,7 +4062,7 @@ class Parser { $legacyAnchor .= '_' . $refers[$legacyArrayKey]; } if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) { - $toc .= $sk->tocLine( $anchor, $tocline, + $toc .= Linker::tocLine( $anchor, $tocline, $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) ); } @@ -3982,7 +4117,7 @@ class Parser { } else { $editlink = ''; } - $head[$headlineCount] = $sk->makeHeadline( $level, + $head[$headlineCount] = Linker::makeHeadline( $level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink, $legacyAnchor ); @@ -3998,9 +4133,9 @@ class Parser { if ( $enoughToc ) { if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) { - $toc .= $sk->tocUnindent( $prevtoclevel - 1 ); + $toc .= Linker::tocUnindent( $prevtoclevel - 1 ); } - $toc = $sk->tocList( $toc, $this->mOptions->getUserLang() ); + $toc = Linker::tocList( $toc, $this->mOptions->getUserLang() ); $this->mOutput->setTOCHTML( $toc ); } @@ -4245,7 +4380,7 @@ class Parser { return $text; } - # FIXME: regex doesn't respect extension tags or nowiki + # @todo FIXME: Regex doesn't respect extension tags or nowiki # => Move this logic to braceSubstitution() $substWord = MagicWord::get( 'subst' ); $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase(); @@ -4329,11 +4464,22 @@ class Parser { /** * Create an HTML-style tag, e.g. special text * The callback should have the following form: - * function myParserHook( $text, $params, $parser ) { ... } + * function myParserHook( $text, $params, $parser, $frame ) { ... } * * Transform and return $text. Use $parser for any required context, e.g. use * $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions * + * Hooks may return extended information by returning an array, of which the + * first numbered element (index 0) must be the return string, and all other + * entries are extracted into local variables within an internal function + * in the Parser class. + * + * This interface (introduced r61913) appears to be undocumented, but + * 'markerName' is used by some core tag hooks to override which strip + * array their results are placed in. **Use great caution if attempting + * this interface, as it is not documented and injudicious use could smash + * private variables.** + * * @param $tag Mixed: the tag to use, e.g. 'hook' for * @param $callback Mixed: the callback function (and object) to use for the tag * @return The old value of the mTagHooks array associated with the hook @@ -4350,6 +4496,22 @@ class Parser { return $oldVal; } + /** + * As setHook(), but letting the contents be parsed. + * + * Transparent tag hooks are like regular XML-style tag hooks, except they + * operate late in the transformation sequence, on HTML instead of wikitext. + * + * This is probably obsoleted by things dealing with parser frames? + * The only extension currently using it is geoserver. + * + * @since 1.10 + * @todo better document or deprecate this + * + * @param $tag Mixed: the tag to use, e.g. 'hook' for + * @param $callback Mixed: the callback function (and object) to use for the tag + * @return The old value of the mTagHooks array associated with the hook + */ function setTransparentTagHook( $tag, $callback ) { $tag = strtolower( $tag ); if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" ); @@ -4470,7 +4632,7 @@ class Parser { } /** - * FIXME: update documentation. makeLinkObj() is deprecated. + * @todo FIXME: Update documentation. makeLinkObj() is deprecated. * Replace link placeholders with actual links, in the buffer * Placeholders created in Skin::makeLinkObj() * Returns an array of link CSS classes, indexed by PDBK. @@ -4498,6 +4660,10 @@ class Parser { * given as text will return the HTML of a gallery with two images, * labeled 'The number "1"' and * 'A tree'. + * + * @param string $text + * @param array $param + * @return string HTML */ function renderImageGallery( $text, $params ) { $ig = new ImageGallery(); @@ -4507,8 +4673,6 @@ class Parser { $ig->setParser( $this ); $ig->setHideBadImages(); $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) ); - $ig->useSkin( $this->mOptions->getSkin( $this->mTitle ) ); - $ig->mRevisionId = $this->mRevisionId; if ( isset( $params['showfilename'] ) ) { $ig->setShowFilename( true ); @@ -4547,26 +4711,38 @@ class Parser { if ( strpos( $matches[0], '%' ) !== false ) { $matches[1] = rawurldecode( $matches[1] ); } - $tp = Title::newFromText( $matches[1], NS_FILE ); - $nt =& $tp; - if ( is_null( $nt ) ) { + $title = Title::newFromText( $matches[1], NS_FILE ); + if ( is_null( $title ) ) { # Bogus title. Ignore these so we don't bomb out later. continue; } + + $label = ''; + $alt = ''; if ( isset( $matches[3] ) ) { - $label = $matches[3]; - } else { - $label = ''; + // look for an |alt= definition while trying not to break existing + // captions with multiple pipes (|) in it, until a more sensible grammar + // is defined for images in galleries + + $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) ); + $altmatches = StringUtils::explode('|', $matches[3]); + $magicWordAlt = MagicWord::get( 'img_alt' ); + + foreach ( $altmatches as $altmatch ) { + $match = $magicWordAlt->matchVariableStartToEnd( $altmatch ); + if ( $match ) { + $alt = $this->stripAltText( $match, false ); + } + else { + // concatenate all other pipes + $label .= '|' . $altmatch; + } + } + // remove the first pipe + $label = substr( $label, 1 ); } - $html = $this->recursiveTagParse( trim( $label ) ); - - $ig->add( $nt, $html ); - - # Only add real images (bug #5586) - if ( $nt->getNamespace() == NS_FILE ) { - $this->mOutput->addImage( $nt->getDBkey() ); - } + $ig->add( $title, $label, $alt ); } return $ig->toHTML(); } @@ -4617,6 +4793,7 @@ class Parser { * @param $title Title * @param $options String * @param $holders LinkHolderArray + * @return string HTML */ function makeImage( $title, $options, $holders = false ) { # Check if the options text is of the form "options|alt text" @@ -4645,23 +4822,23 @@ class Parser { # * text-bottom $parts = StringUtils::explode( "|", $options ); - $sk = $this->mOptions->getSkin( $this->mTitle ); # Give extensions a chance to select the file revision for us - $skip = $time = $descQuery = false; - wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$title, &$skip, &$time, &$descQuery ) ); - - if ( $skip ) { - return $sk->link( $title ); - } + $time = $sha1 = $descQuery = false; + wfRunHooks( 'BeforeParserFetchFileAndTitle', + array( $this, $title, &$time, &$sha1, &$descQuery ) ); + # Fetch and register the file (file title may be different via hooks) + list( $file, $title ) = $this->fetchFileAndTitle( $title, $time, $sha1 ); - # Get the file - $file = wfFindFile( $title, array( 'time' => $time ) ); # Get parameter map $handler = $file ? $file->getHandler() : false; list( $paramMap, $mwArray ) = $this->getImageParams( $handler ); + if ( !$file ) { + $this->addTrackingCategory( 'broken-file-category' ); + } + # Process the input parameters $caption = ''; $params = array( 'frame' => array(), 'handler' => array(), @@ -4705,7 +4882,7 @@ class Parser { switch( $paramName ) { case 'manualthumb': case 'alt': - # @todo Fixme: possibly check validity here for + # @todo FIXME: Possibly check validity here for # manualthumb? downstream behavior seems odd with # missing manual thumbs. $validated = true; @@ -4811,7 +4988,8 @@ class Parser { wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params ) ); # Linker does the rest - $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'], $time, $descQuery, $this->mOptions->getThumbSize() ); + $ret = Linker::makeImageLink2( $title, $file, $params['frame'], $params['handler'], + $time, $descQuery, $this->mOptions->getThumbSize() ); # Give the handler a chance to modify the parser object if ( $handler ) { @@ -4857,7 +5035,6 @@ class Parser { * @param $text String * @param $frame PPFrame * @return String - * @private */ function attributeStripCallback( &$text, $frame = false ) { $text = $this->replaceVariables( $text, $frame ); @@ -4867,6 +5044,8 @@ class Parser { /** * Accessor + * + * @return array */ function getTags() { return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ) ); @@ -4881,7 +5060,7 @@ class Parser { function replaceTransparentTags( $text ) { $matches = array(); $elements = array_keys( $this->mTransparentTagHooks ); - $text = $this->extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix ); + $text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix ); foreach ( $matches as $marker => $data ) { list( $element, $content, $params, $tag ) = $data; @@ -4947,6 +5126,10 @@ class Parser { if ( $sectionIndex == 0 ) { # Section zero doesn't nest, level=big $targetLevel = 1000; + if ( !$node ) { + # The page definitely exists - we checked that earlier - so it must be blank: see bug #14005 + return $text; + } } else { while ( $node ) { if ( $node->getName() === 'h' ) { @@ -5031,10 +5214,10 @@ class Parser { * This function returns $oldtext after the content of the section * specified by $section has been replaced with $text. * - * @param $text String: former text of the article + * @param $oldtext String: former text of the article * @param $section Numeric: section identifier * @param $text String: replacing text - * #return String: modified text + * @return String: modified text */ public function replaceSection( $oldtext, $section, $text ) { return $this->extractSections( $oldtext, $section, "replace", $text ); @@ -5052,7 +5235,7 @@ class Parser { /** * Get the revision object for $this->mRevisionId * - * @return either a Revision object or null + * @return Revision|null either a Revision object or null */ protected function getRevisionObject() { if ( !is_null( $this->mRevisionObject ) ) { @@ -5198,7 +5381,8 @@ class Parser { $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text ); $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text ); - # Strip external link markup (FIXME: Not Tolerant to blank link text + # Strip external link markup + # @todo FIXME: Not tolerant to blank link text # I.E. [http://www.mediawiki.org] will render as [1] or something depending # on how many empty links there are on the page - need to figure that out. $text = preg_replace( '/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text ); @@ -5213,6 +5397,8 @@ class Parser { /** * strip/replaceVariables/unstrip for preprocessor regression testing + * + * @return string */ function testSrvus( $text, $title, ParserOptions $options, $outputType = self::OT_HTML ) { if ( !$title instanceof Title ) { @@ -5251,6 +5437,8 @@ class Parser { * This will call the callback function twice, with 'aaa' and 'bbb'. Those * two strings will be replaced with the value returned by the callback in * each case. + * + * @return string */ function markerSkipCallback( $s, $callback ) { $i = 0; @@ -5287,6 +5475,8 @@ class Parser { * array can later be loaded into another parser instance with * unserializeHalfParsedText(). The text can then be safely incorporated into * the return value of a parser hook. + * + * @return array */ function serializeHalfParsedText( $text ) { wfProfileIn( __METHOD__ ); @@ -5335,7 +5525,9 @@ class Parser { * serializeHalfParsedText(), is compatible with the current version of the * parser. * - * @param $data Array. + * @param $data Array + * + * @return bool */ function isValidHalfParsedText( $data ) { return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;