From 63601400e476c6cf43d985f3e7b9864681695ed4 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 18 Jan 2013 16:46:04 +0100 Subject: Update to MediaWiki 1.20.2 this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024 --- includes/parser/CacheTime.php | 132 +++++++++++ includes/parser/CoreLinkFunctions.php | 16 ++ includes/parser/CoreParserFunctions.php | 169 +++++++++----- includes/parser/CoreTagHooks.php | 16 ++ includes/parser/DateFormatter.php | 91 +++++--- includes/parser/LinkHolderArray.php | 32 ++- includes/parser/Parser.php | 373 +++++++++++++++++++------------ includes/parser/ParserCache.php | 33 ++- includes/parser/ParserOptions.php | 55 ++++- includes/parser/ParserOutput.php | 175 ++++++--------- includes/parser/Parser_DiffTest.php | 16 ++ includes/parser/Parser_LinkHooks.php | 20 +- includes/parser/Preprocessor.php | 28 ++- includes/parser/Preprocessor_DOM.php | 80 +++++-- includes/parser/Preprocessor_Hash.php | 78 +++++-- includes/parser/Preprocessor_HipHop.hphp | 106 +++++++-- includes/parser/StripState.php | 55 ++++- includes/parser/Tidy.php | 22 +- 18 files changed, 1071 insertions(+), 426 deletions(-) create mode 100644 includes/parser/CacheTime.php (limited to 'includes/parser') diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php new file mode 100644 index 00000000..881dded7 --- /dev/null +++ b/includes/parser/CacheTime.php @@ -0,0 +1,132 @@ +mCacheTime; } + + function containsOldMagic() { return $this->mContainsOldMagic; } + function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } + + /** + * setCacheTime() sets the timestamp expressing when the page has been rendered. + * This doesn not control expiry, see updateCacheExpiry() for that! + * @param $t string + * @return string + */ + function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } + + /** + * Sets the number of seconds after which this object should expire. + * This value is used with the ParserCache. + * If called with a value greater than the value provided at any previous call, + * the new call has no effect. The value returned by getCacheExpiry is smaller + * or equal to the smallest number that was provided as an argument to + * updateCacheExpiry(). + * + * @param $seconds number + */ + function updateCacheExpiry( $seconds ) { + $seconds = (int)$seconds; + + if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds ) { + $this->mCacheExpiry = $seconds; + } + + // hack: set old-style marker for uncacheable entries. + if ( $this->mCacheExpiry !== null && $this->mCacheExpiry <= 0 ) { + $this->mCacheTime = -1; + } + } + + /** + * Returns the number of seconds after which this object should expire. + * This method is used by ParserCache to determine how long the ParserOutput can be cached. + * The timestamp of expiry can be calculated by adding getCacheExpiry() to getCacheTime(). + * The value returned by getCacheExpiry is smaller or equal to the smallest number + * that was provided to a call of updateCacheExpiry(), and smaller or equal to the + * value of $wgParserCacheExpireTime. + * @return int|mixed|null + */ + function getCacheExpiry() { + global $wgParserCacheExpireTime; + + if ( $this->mCacheTime < 0 ) { + return 0; + } // old-style marker for "not cachable" + + $expire = $this->mCacheExpiry; + + if ( $expire === null ) { + $expire = $wgParserCacheExpireTime; + } else { + $expire = min( $expire, $wgParserCacheExpireTime ); + } + + if( $this->containsOldMagic() ) { //compatibility hack + $expire = min( $expire, 3600 ); # 1 hour + } + + if ( $expire <= 0 ) { + return 0; // not cachable + } else { + return $expire; + } + } + + /** + * @return bool + */ + function isCacheable() { + return $this->getCacheExpiry() > 0; + } + + /** + * Return true if this cached output object predates the global or + * per-article cache invalidation timestamps, or if it comes from + * an incompatible older version. + * + * @param $touched String: the affected article's last touched timestamp + * @return Boolean + */ + public function expired( $touched ) { + global $wgCacheEpoch; + return !$this->isCacheable() || // parser says it's uncacheable + $this->getCacheTime() < $touched || + $this->getCacheTime() <= $wgCacheEpoch || + $this->getCacheTime() < wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) || // expiry period has passed + !isset( $this->mVersion ) || + version_compare( $this->mVersion, Parser::VERSION, "lt" ); + } + +} diff --git a/includes/parser/CoreLinkFunctions.php b/includes/parser/CoreLinkFunctions.php index 8de13278..4bfa9d35 100644 --- a/includes/parser/CoreLinkFunctions.php +++ b/includes/parser/CoreLinkFunctions.php @@ -2,7 +2,23 @@ /** * Link functions provided by MediaWiki core; experimental * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php index 0e5702b7..8917b6d0 100644 --- a/includes/parser/CoreParserFunctions.php +++ b/includes/parser/CoreParserFunctions.php @@ -2,7 +2,23 @@ /** * Parser functions provided by MediaWiki core * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** @@ -55,6 +71,7 @@ class CoreParserFunctions { $parser->setFunctionHook( 'padright', array( __CLASS__, 'padright' ), SFH_NO_HASH ); $parser->setFunctionHook( 'anchorencode', array( __CLASS__, 'anchorencode' ), SFH_NO_HASH ); $parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) ); + $parser->setFunctionHook( 'speciale', array( __CLASS__, 'speciale' ) ); $parser->setFunctionHook( 'defaultsort', array( __CLASS__, 'defaultsort' ), SFH_NO_HASH ); $parser->setFunctionHook( 'filepath', array( __CLASS__, 'filepath' ), SFH_NO_HASH ); $parser->setFunctionHook( 'pagesincategory', array( __CLASS__, 'pagesincategory' ), SFH_NO_HASH ); @@ -62,6 +79,7 @@ class CoreParserFunctions { $parser->setFunctionHook( 'protectionlevel', array( __CLASS__, 'protectionlevel' ), SFH_NO_HASH ); $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH ); $parser->setFunctionHook( 'namespacee', array( __CLASS__, 'namespacee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'namespacenumber', array( __CLASS__, 'namespacenumber' ), SFH_NO_HASH ); $parser->setFunctionHook( 'talkspace', array( __CLASS__, 'talkspace' ), SFH_NO_HASH ); $parser->setFunctionHook( 'talkspacee', array( __CLASS__, 'talkspacee' ), SFH_NO_HASH ); $parser->setFunctionHook( 'subjectspace', array( __CLASS__, 'subjectspace' ), SFH_NO_HASH ); @@ -111,7 +129,8 @@ class CoreParserFunctions { * @return mixed|string */ static function formatDate( $parser, $date, $defaultPref = null ) { - $df = DateFormatter::getInstance(); + $lang = $parser->getFunctionLang(); + $df = DateFormatter::getInstance( $lang ); $date = trim( $date ); @@ -158,6 +177,7 @@ class CoreParserFunctions { * @param $parser Parser object * @param $s String: The text to encode. * @param $arg String (optional): The type of encoding. + * @return string */ static function urlencode( $parser, $s = '', $arg = null ) { static $magicWords = null; @@ -283,8 +303,10 @@ class CoreParserFunctions { // Some shortcuts to avoid loading user data unnecessarily if ( count( $forms ) === 0 ) { + wfProfileOut( __METHOD__ ); return ''; } elseif ( count( $forms ) === 1 ) { + wfProfileOut( __METHOD__ ); return $forms[0]; } @@ -303,9 +325,9 @@ class CoreParserFunctions { // check parameter, or use the ParserOptions if in interface message $user = User::newFromName( $username ); if ( $user ) { - $gender = $user->getOption( 'gender' ); + $gender = GenderCache::singleton()->getGenderOf( $user, __METHOD__ ); } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) { - $gender = $parser->getOptions()->getUser()->getOption( 'gender' ); + $gender = GenderCache::singleton()->getGenderOf( $parser->getOptions()->getUser(), __METHOD__ ); } $ret = $parser->getFunctionLang()->gender( $gender, $forms ); wfProfileOut( __METHOD__ ); @@ -320,6 +342,7 @@ class CoreParserFunctions { static function plural( $parser, $text = '' ) { $forms = array_slice( func_get_args(), 2 ); $text = $parser->getFunctionLang()->parseFormattedNumber( $text ); + settype( $text, ctype_digit( $text ) ? 'int' : 'float' ); return $parser->getFunctionLang()->convertPlural( $text, $forms ); } @@ -420,6 +443,7 @@ class CoreParserFunctions { * corresponding magic word * Note: function name changed to "mwnamespace" rather than "namespace" * to not break PHP 5.3 + * @return mixed|string */ static function mwnamespace( $parser, $title = null ) { $t = Title::newFromText( $title ); @@ -433,6 +457,12 @@ class CoreParserFunctions { return ''; return wfUrlencode( $t->getNsText() ); } + static function namespacenumber( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) + return ''; + return $t->getNamespace(); + } static function talkspace( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) @@ -461,6 +491,7 @@ class CoreParserFunctions { /** * Functions to get and normalize pagenames, corresponding to the magic words * of the same names + * @return String */ static function pagename( $parser, $title = null ) { $t = Title::newFromText( $title ); @@ -536,28 +567,64 @@ class CoreParserFunctions { } /** - * Return the number of pages in the given category, or 0 if it's nonexis- - * tent. This is an expensive parser function and can't be called too many - * times per page. + * Return the number of pages, files or subcats in the given category, + * or 0 if it's nonexistent. This is an expensive parser function and + * can't be called too many times per page. + * @return string */ - static function pagesincategory( $parser, $name = '', $raw = null ) { + static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) { + static $magicWords = null; + if ( is_null( $magicWords ) ) { + $magicWords = new MagicWordArray( array( + 'pagesincategory_all', + 'pagesincategory_pages', + 'pagesincategory_subcats', + 'pagesincategory_files' + ) ); + } static $cache = array(); - $category = Category::newFromName( $name ); - if( !is_object( $category ) ) { - $cache[$name] = 0; + // split the given option to its variable + if( self::isRaw( $arg1 ) ) { + //{{pagesincategory:|raw[|type]}} + $raw = $arg1; + $type = $magicWords->matchStartToEnd( $arg2 ); + } else { + //{{pagesincategory:[|type[|raw]]}} + $type = $magicWords->matchStartToEnd( $arg1 ); + $raw = $arg2; + } + if( !$type ) { //backward compatibility + $type = 'pagesincategory_all'; + } + + $title = Title::makeTitleSafe( NS_CATEGORY, $name ); + if( !$title ) { # invalid title return self::formatRaw( 0, $raw ); } - # Normalize name for cache - $name = $category->getName(); + // Normalize name for cache + $name = $title->getDBkey(); - $count = 0; - if( isset( $cache[$name] ) ) { - $count = $cache[$name]; - } elseif( $parser->incrementExpensiveFunctionCount() ) { - $count = $cache[$name] = (int)$category->getPageCount(); + if( !isset( $cache[$name] ) ) { + $category = Category::newFromTitle( $title ); + + $allCount = $subcatCount = $fileCount = $pagesCount = 0; + if( $parser->incrementExpensiveFunctionCount() ) { + // $allCount is the total number of cat members, + // not the count of how many members are normal pages. + $allCount = (int)$category->getPageCount(); + $subcatCount = (int)$category->getSubcatCount(); + $fileCount = (int)$category->getFileCount(); + $pagesCount = $allCount - $subcatCount - $fileCount; + } + $cache[$name]['pagesincategory_all'] = $allCount; + $cache[$name]['pagesincategory_pages'] = $pagesCount; + $cache[$name]['pagesincategory_subcats'] = $subcatCount; + $cache[$name]['pagesincategory_files'] = $fileCount; } + + $count = $cache[$name][$type]; return self::formatRaw( $count, $raw ); } @@ -576,6 +643,7 @@ class CoreParserFunctions { * @param $parser Parser * @param $page String TODO DOCUMENT (Default: empty string) * @param $raw TODO DOCUMENT (Default: null) + * @return string */ static function pagesize( $parser, $page = '', $raw = null ) { static $cache = array(); @@ -593,7 +661,7 @@ class CoreParserFunctions { if( isset( $cache[$page] ) ) { $length = $cache[$page]; } elseif( $parser->incrementExpensiveFunctionCount() ) { - $rev = Revision::newFromTitle( $title ); + $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); $id = $rev ? $rev->getPage() : 0; $length = $cache[$page] = $rev ? $rev->getSize() : 0; @@ -605,7 +673,8 @@ class CoreParserFunctions { /** * Returns the requested protection level for the current page - */ + * @return string + */ static function protectionlevel( $parser, $type = '' ) { $restrictions = $parser->mTitle->getRestrictions( strtolower( $type ) ); # Title::getRestrictions returns an array, its possible it may have @@ -616,26 +685,20 @@ class CoreParserFunctions { /** * Gives language names. * @param $parser Parser - * @param $code String Language code - * @param $language String Language code + * @param $code String Language code (of which to get name) + * @param $inLanguage String Language code (in which to get name) * @return String */ - static function language( $parser, $code = '', $language = '' ) { - global $wgContLang; + static function language( $parser, $code = '', $inLanguage = '' ) { $code = strtolower( $code ); - $language = strtolower( $language ); - - if ( $language !== '' ) { - $names = Language::getTranslatedLanguageNames( $language ); - return isset( $names[$code] ) ? $names[$code] : wfBCP47( $code ); - } - - $lang = $wgContLang->getLanguageName( $code ); + $inLanguage = strtolower( $inLanguage ); + $lang = Language::fetchLanguageName( $code, $inLanguage ); return $lang !== '' ? $lang : wfBCP47( $code ); } /** * Unicode-safe str_pad with the restriction that $length is forced to be <= 500 + * @return string */ static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) { $padding = $parser->killMarkers( $padding ); @@ -683,12 +746,16 @@ class CoreParserFunctions { list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text ); if ( $page ) { $title = SpecialPage::getTitleFor( $page, $subpage ); - return $title; + return $title->getPrefixedText(); } else { - return wfMsgForContent( 'nosuchspecialpage' ); + return wfMessage( 'nosuchspecialpage' )->inContentLanguage()->text(); } } + static function speciale( $parser, $text ) { + return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) ); + } + /** * @param $parser Parser * @param $text String The sortkey to use @@ -716,48 +783,39 @@ class CoreParserFunctions { return ''; } else { return( '' . - wfMsgForContent( 'duplicate-defaultsort', - htmlspecialchars( $old ), - htmlspecialchars( $text ) ) . + wfMessage( 'duplicate-defaultsort', $old, $text )->inContentLanguage()->escaped() . '' ); } } // Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}} + // or {{filepath|300px}}, {{filepath|200x300px}}, {{filepath|nowiki|200x300px}}, {{filepath|200x300px|nowiki}} public static function filepath( $parser, $name='', $argA='', $argB='' ) { $file = wfFindFile( $name ); - $size = ''; - $argA_int = intval( $argA ); - $argB_int = intval( $argB ); - - if ( $argB_int > 0 ) { - // {{filepath: | option | size }} - $size = $argB_int; - $option = $argA; - - } elseif ( $argA_int > 0 ) { - // {{filepath: | size [|option] }} - $size = $argA_int; - $option = $argB; + if( $argA == 'nowiki' ) { + // {{filepath: | option [| size] }} + $isNowiki = true; + $parsedWidthParam = $parser->parseWidthParam( $argB ); } else { - // {{filepath: [|option] }} - $option = $argA; + // {{filepath: [| size [|option]] }} + $parsedWidthParam = $parser->parseWidthParam( $argA ); + $isNowiki = ($argB == 'nowiki'); } if ( $file ) { $url = $file->getFullUrl(); // If a size is requested... - if ( is_integer( $size ) ) { - $mto = $file->transform( array( 'width' => $size ) ); + if ( count( $parsedWidthParam ) ) { + $mto = $file->transform( $parsedWidthParam ); // ... and we can if ( $mto && !$mto->isError() ) { // ... change the URL to point to a thumbnail. $url = wfExpandUrl( $mto->getUrl(), PROTO_RELATIVE ); } } - if ( $option == 'nowiki' ) { + if ( $isNowiki ) { return array( $url, 'nowiki' => true ); } return $url; @@ -768,6 +826,7 @@ class CoreParserFunctions { /** * Parser function to extension tag adaptor + * @return string */ public static function tagObj( $parser, $frame, $args ) { if ( !count( $args ) ) { @@ -784,7 +843,7 @@ class CoreParserFunctions { $stripList = $parser->getStripList(); if ( !in_array( $tagName, $stripList ) ) { return '' . - wfMsgForContent( 'unknown_extension_tag', $tagName ) . + wfMessage( 'unknown_extension_tag', $tagName )->inContentLanguage()->text() . ''; } diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php index 7d488c4b..296be66f 100644 --- a/includes/parser/CoreTagHooks.php +++ b/includes/parser/CoreTagHooks.php @@ -2,7 +2,23 @@ /** * Tag hooks provided by MediaWiki core * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php index 6559e886..2917b4a7 100644 --- a/includes/parser/DateFormatter.php +++ b/includes/parser/DateFormatter.php @@ -2,7 +2,23 @@ /** * Date formatter * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** @@ -10,14 +26,15 @@ * @todo preferences, OutputPage * @ingroup Parser */ -class DateFormatter -{ +class DateFormatter { var $mSource, $mTarget; var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD; var $regexes, $pDays, $pMonths, $pYears; var $rules, $xMonths, $preferences; + protected $lang; + const ALL = -1; const NONE = 0; const MDY = 1; @@ -32,15 +49,15 @@ class DateFormatter const LAST = 8; /** - * @todo document + * @param $lang Language In which language to format the date */ - function __construct() { - global $wgContLang; + function __construct( Language $lang ) { + $this->lang = $lang; $this->monthNames = $this->getMonthRegex(); for ( $i=1; $i<=12; $i++ ) { - $this->xMonths[$wgContLang->lc( $wgContLang->getMonthName( $i ) )] = $i; - $this->xMonths[$wgContLang->lc( $wgContLang->getMonthAbbreviation( $i ) )] = $i; + $this->xMonths[$this->lang->lc( $this->lang->getMonthName( $i ) )] = $i; + $this->xMonths[$this->lang->lc( $this->lang->getMonthAbbreviation( $i ) )] = $i; } $this->regexTrail = '(?![a-z])/iu'; @@ -103,16 +120,20 @@ class DateFormatter /** * Get a DateFormatter object * + * @param $lang Language|string|null In which language to format the date + * Defaults to the site content language * @return DateFormatter object */ - public static function &getInstance() { - global $wgMemc; + public static function &getInstance( $lang = null ) { + global $wgMemc, $wgContLang; static $dateFormatter = false; + $lang = $lang ? wfGetLangObj( $lang ) : $wgContLang; + $key = wfMemcKey( 'dateformatter', $lang->getCode() ); if ( !$dateFormatter ) { - $dateFormatter = $wgMemc->get( wfMemcKey( 'dateformatter' ) ); + $dateFormatter = $wgMemc->get( $key ); if ( !$dateFormatter ) { - $dateFormatter = new DateFormatter; - $wgMemc->set( wfMemcKey( 'dateformatter' ), $dateFormatter, 3600 ); + $dateFormatter = new DateFormatter( $lang ); + $wgMemc->set( $key, $dateFormatter, 3600 ); } } return $dateFormatter; @@ -122,12 +143,12 @@ class DateFormatter * @param $preference String: User preference * @param $text String: Text to reformat * @param $options Array: can contain 'linked' and/or 'match-whole' + * @return mixed|String */ function reformat( $preference, $text, $options = array('linked') ) { - $linked = in_array( 'linked', $options ); $match_whole = in_array( 'match-whole', $options ); - + if ( isset( $this->preferences[$preference] ) ) { $preference = $this->preferences[$preference]; } else { @@ -149,19 +170,19 @@ class DateFormatter $this->mTarget = $i; } $regex = $this->regexes[$i]; - + // Horrible hack if (!$linked) { $regex = str_replace( array( '\[\[', '\]\]' ), '', $regex ); } - + if ($match_whole) { // Let's hope this works $regex = preg_replace( '!^/!', '/^', $regex ); $regex = str_replace( $this->regexTrail, '$'.$this->regexTrail, $regex ); } - + // Another horrible hack $this->mLinked = $linked; $text = preg_replace_callback( $regex, array( &$this, 'replace' ), $text ); @@ -172,6 +193,7 @@ class DateFormatter /** * @param $matches + * @return string */ function replace( $matches ) { # Extract information from $matches @@ -186,10 +208,15 @@ class DateFormatter $bits[$key[$p]] = $matches[$p+1]; } } - + return $this->formatDate( $bits, $linked ); } - + + /** + * @param $bits array + * @param $link bool + * @return string + */ function formatDate( $bits, $link = true ) { $format = $this->targets[$this->mTarget]; @@ -203,13 +230,13 @@ class DateFormatter # Construct new date $text = ''; $fail = false; - + // Pre-generate y/Y stuff because we need the year for the title. if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) $bits['y'] = $this->makeIsoYear( $bits['Y'] ); if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) $bits['Y'] = $this->makeNormalYear( $bits['y'] ); - + if ( !isset( $bits['m'] ) ) { $m = $this->makeIsoMonth( $bits['F'] ); if ( !$m || $m == '00' ) { @@ -218,7 +245,7 @@ class DateFormatter $bits['m'] = $m; } } - + if ( !isset($bits['d']) ) { $bits['d'] = sprintf( '%02d', $bits['j'] ); } @@ -248,8 +275,7 @@ class DateFormatter if ( $m > 12 || $m < 1 ) { $fail = true; } else { - global $wgContLang; - $text .= $wgContLang->getMonthName( $m ); + $text .= $this->lang->getMonthName( $m ); } } else { $text .= ucfirst( $bits['F'] ); @@ -265,30 +291,30 @@ class DateFormatter if ( $fail ) { $text = $matches[0]; } - + $isoBits = array(); if ( isset($bits['y']) ) $isoBits[] = $bits['y']; $isoBits[] = $bits['m']; $isoBits[] = $bits['d']; $isoDate = implode( '-', $isoBits ); - + // Output is not strictly HTML (it's wikitext), but is whitelisted. $text = Html::rawElement( 'span', array( 'class' => 'mw-formatted-date', 'title' => $isoDate ), $text ); - + return $text; } /** * @todo document + * @return string */ function getMonthRegex() { - global $wgContLang; $names = array(); for( $i = 1; $i <= 12; $i++ ) { - $names[] = $wgContLang->getMonthName( $i ); - $names[] = $wgContLang->getMonthAbbreviation( $i ); + $names[] = $this->lang->getMonthName( $i ); + $names[] = $this->lang->getMonthAbbreviation( $i ); } return implode( '|', $names ); } @@ -299,9 +325,7 @@ class DateFormatter * @return string ISO month name */ function makeIsoMonth( $monthName ) { - global $wgContLang; - - $n = $this->xMonths[$wgContLang->lc( $monthName )]; + $n = $this->xMonths[$this->lang->lc( $monthName )]; return sprintf( '%02d', $n ); } @@ -325,6 +349,7 @@ class DateFormatter /** * @todo document + * @return int|string */ function makeNormalYear( $iso ) { if ( $iso[0] == '-' ) { diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php index fb013047..d9356b48 100644 --- a/includes/parser/LinkHolderArray.php +++ b/includes/parser/LinkHolderArray.php @@ -2,7 +2,23 @@ /** * Holder of replacement pairs for wiki links * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** @@ -33,7 +49,8 @@ class LinkHolderArray { * serializing at present. * * Compact the titles, only serialize the text form. - */ + * @return array + */ function __sleep() { foreach ( $this->internals as &$nsLinks ) { foreach ( $nsLinks as &$entry ) { @@ -134,6 +151,7 @@ class LinkHolderArray { /** * Get a subset of the current LinkHolderArray which is sufficient to * interpret the given text. + * @return LinkHolderArray */ function getSubArray( $text ) { $sub = new LinkHolderArray( $this->parent ); @@ -167,6 +185,7 @@ class LinkHolderArray { /** * Returns true if the memory requirements of this object are getting large + * @return bool */ function isBig() { global $wgLinkHolderBatchSize; @@ -190,6 +209,11 @@ class LinkHolderArray { * article length checks (for stub links) to be bundled into a single query. * * @param $nt Title + * @param $text String + * @param $query Array [optional] + * @param $trail String [optional] + * @param $prefix String [optional] + * @return string */ function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) { wfProfileIn( __METHOD__ ); @@ -433,7 +457,7 @@ class LinkHolderArray { foreach ( $entries as $index => $entry ) { $pdbk = $entry['pdbk']; // we only deal with new links (in its first query) - if ( !isset( $colours[$pdbk] ) ) { + if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) { $title = $entry['title']; $titleText = $title->getText(); $titlesAttrs[] = array( @@ -449,7 +473,7 @@ class LinkHolderArray { } // Now do the conversion and explode string to text of titles - $titlesAllVariants = $wgContLang->autoConvertToAllVariants( $titlesToBeConverted ); + $titlesAllVariants = $wgContLang->autoConvertToAllVariants( rtrim( $titlesToBeConverted, "\0" ) ); $allVariantsName = array_keys( $titlesAllVariants ); foreach ( $titlesAllVariants as &$titlesVariant ) { $titlesVariant = explode( "\0", $titlesVariant ); @@ -517,7 +541,7 @@ class LinkHolderArray { $entry =& $this->internals[$ns][$index]; $pdbk = $entry['pdbk']; - if(!isset($colours[$pdbk])){ + if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] === 'new' ) { // found link in some of the variants, replace the link holder data $entry['title'] = $variantTitle; $entry['pdbk'] = $varPdbk; diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index d4f167c9..2a24bee7 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -1,12 +1,29 @@ - * There are five main entry points into the Parser class: - * parse() + * There are seven main entry points into the Parser class: + * + * - Parser::parse() * produces HTML output - * preSaveTransform(). + * - Parser::preSaveTransform(). * produces altered wiki markup. - * preprocess() + * - Parser::preprocess() * removes HTML comments and expands templates - * cleanSig() / cleanSigInSig() + * - Parser::cleanSig() and Parser::cleanSigInSig() * Cleans a signature before saving it to preferences - * getSection() + * - Parser::getSection() * Return the content of a section from an article for section editing - * replaceSection() + * - Parser::replaceSection() * Replaces a section by number inside an article - * getPreloadText() + * - Parser::getPreloadText() * Removes sections, and tags. * * Globals used: * object: $wgContLang * - * NOT $wgUser or $wgTitle or $wgRequest or $wgLang. Keep them away! + * @warning $wgUser or $wgTitle or $wgRequest or $wgLang. Keep them away! * - * settings: - * $wgUseDynamicDates*, $wgInterwikiMagic*, - * $wgNamespacesWithSubpages, $wgAllowExternalImages*, - * $wgLocaltimezone, $wgAllowSpecialInclusion*, - * $wgMaxArticleSize* + * @par Settings: + * $wgLocaltimezone + * $wgNamespacesWithSubpages * - * * only within ParserOptions - * + * @par Settings only within ParserOptions: + * $wgAllowExternalImages + * $wgAllowSpecialInclusion + * $wgInterwikiMagic + * $wgMaxArticleSize + * $wgUseDynamicDates * * @ingroup Parser */ @@ -144,7 +163,8 @@ class Parser { var $mLinkHolders; var $mLinkID; - var $mIncludeSizes, $mPPNodeCount, $mDefaultSort; + var $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth; + var $mDefaultSort; var $mTplExpandCache; # empty-frame expansion cache var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores; var $mExpensiveFunctionCount; # number of expensive parser function calls @@ -188,7 +208,7 @@ class Parser { public function __construct( $conf = array() ) { $this->mConf = $conf; $this->mUrlProtocols = wfUrlProtocols(); - $this->mExtLinkBracketedRegex = '/\[((' . wfUrlProtocols() . ')'. + $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')'. self::EXT_LINK_URL_CLASS.'+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su'; if ( isset( $conf['preprocessorClass'] ) ) { $this->mPreprocessorClass = $conf['preprocessorClass']; @@ -273,8 +293,6 @@ class Parser { * Must not consist of all title characters, or else it will change * the behaviour of in a link. */ - # $this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString(); - # Changed to \x7f to allow XML double-parsing -- TS $this->mUniqPrefix = "\x7fUNIQ" . self::getRandomString(); $this->mStripState = new StripState( $this->mUniqPrefix ); @@ -289,6 +307,8 @@ class Parser { 'arg' => 0, ); $this->mPPNodeCount = 0; + $this->mGeneratedPPNodeCount = 0; + $this->mHighestExpansionDepth = 0; $this->mDefaultSort = false; $this->mHeadings = array(); $this->mDoubleUnderscores = array(); @@ -321,13 +341,18 @@ class Parser { * to internalParse() which does all the real work. */ - global $wgUseTidy, $wgAlwaysUseTidy, $wgDisableLangConversion, $wgDisableTitleConversion; + global $wgUseTidy, $wgAlwaysUseTidy; $fname = __METHOD__.'-' . wfGetCaller(); wfProfileIn( __METHOD__ ); wfProfileIn( $fname ); $this->startParse( $title, $options, self::OT_HTML, $clearState ); + # Remove the strip marker tag prefix from the input, if present. + if ( $clearState ) { + $text = str_replace( $this->mUniqPrefix, '', $text ); + } + $oldRevisionId = $this->mRevisionId; $oldRevisionObject = $this->mRevisionObject; $oldRevisionTimestamp = $this->mRevisionTimestamp; @@ -343,6 +368,7 @@ class Parser { # No more strip! wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); $text = $this->internalParse( $text ); + wfRunHooks( 'ParserAfterParse', array( &$this, &$text, &$this->mStripState ) ); $text = $this->mStripState->unstripGeneral( $text ); @@ -368,9 +394,8 @@ class Parser { * c) It's a conversion table * d) it is an interface message (which is in the user language) */ - if ( !( $wgDisableLangConversion - || isset( $this->mDoubleUnderscores['nocontentconvert'] ) - || $this->mTitle->isConversionTable() ) ) + if ( !( $options->getDisableContentConversion() + || isset( $this->mDoubleUnderscores['nocontentconvert'] ) ) ) { # Run convert unconditionally in 1.18-compatible mode global $wgBug34832TransitionalRollback; @@ -389,8 +414,7 @@ class Parser { * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over * automatic link conversion. */ - if ( !( $wgDisableLangConversion - || $wgDisableTitleConversion + if ( !( $options->getDisableTitleConversion() || isset( $this->mDoubleUnderscores['nocontentconvert'] ) || isset( $this->mDoubleUnderscores['notitleconvert'] ) || $this->mOutput->getDisplayTitle() !== false ) ) @@ -442,9 +466,12 @@ class Parser { array_values( $tidyregs ), $text ); } - global $wgExpensiveParserFunctionLimit; - if ( $this->mExpensiveFunctionCount > $wgExpensiveParserFunctionLimit ) { - $this->limitationWarn( 'expensive-parserfunction', $this->mExpensiveFunctionCount, $wgExpensiveParserFunctionLimit ); + + if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) { + $this->limitationWarn( 'expensive-parserfunction', + $this->mExpensiveFunctionCount, + $this->mOptions->getExpensiveParserFunctionLimit() + ); } wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) ); @@ -452,12 +479,15 @@ class Parser { # Information on include size limits, for the benefit of users who try to skirt them if ( $this->mOptions->getEnableLimitReport() ) { $max = $this->mOptions->getMaxIncludeSize(); - $PFreport = "Expensive parser function count: {$this->mExpensiveFunctionCount}/$wgExpensiveParserFunctionLimit\n"; + $PFreport = "Expensive parser function count: {$this->mExpensiveFunctionCount}/{$this->mOptions->getExpensiveParserFunctionLimit()}\n"; $limitReport = "NewPP limit report\n" . - "Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->getMaxPPNodeCount()}\n" . + "Preprocessor visited node count: {$this->mPPNodeCount}/{$this->mOptions->getMaxPPNodeCount()}\n" . + "Preprocessor generated node count: " . + "{$this->mGeneratedPPNodeCount}/{$this->mOptions->getMaxGeneratedPPNodeCount()}\n" . "Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" . "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n". + "Highest expansion depth: {$this->mHighestExpansionDepth}/{$this->mOptions->getMaxPPExpandDepth()}\n". $PFreport; wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) ); $text .= "\n\n"; @@ -497,6 +527,7 @@ class Parser { /** * Expand templates and variables in the text, producing valid, static wikitext. * Also removes comments. + * @return mixed|string */ function preprocess( $text, Title $title, ParserOptions $options, $revid = null ) { wfProfileIn( __METHOD__ ); @@ -530,10 +561,11 @@ class Parser { } /** - * Process the wikitext for the ?preload= feature. (bug 5210) + * Process the wikitext for the "?preload=" feature. (bug 5210) * - * , etc. are parsed as for template transclusion, - * comments, templates, arguments, tags hooks and parser functions are untouched. + * "", "" etc. are parsed as for template + * transclusion, comments, templates, arguments, tags hooks and parser + * functions are untouched. * * @param $text String * @param $title Title @@ -557,7 +589,7 @@ class Parser { * @return string */ static public function getRandomString() { - return dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) ); + return wfRandomString( 16 ); } /** @@ -619,7 +651,7 @@ class Parser { /** * Accessor/mutator for the Title object * - * @param $x New Title object or null to just get the current one + * @param $x Title object or null to just get the current one * @return Title object */ function Title( $x = null ) { @@ -645,7 +677,7 @@ class Parser { /** * Accessor/mutator for the output type * - * @param $x New value or null to just get the current one + * @param $x int|null New value or null to just get the current one * @return Integer */ function OutputType( $x = null ) { @@ -673,8 +705,8 @@ class Parser { /** * Accessor/mutator for the ParserOptions object * - * @param $x New value or null to just get the current one - * @return Current ParserOptions object + * @param $x ParserOptions New value or null to just get the current one + * @return ParserOptions Current ParserOptions object */ function Options( $x = null ) { return wfSetVar( $this->mOptions, $x ); @@ -703,18 +735,24 @@ class Parser { } /** - * Get the target language for the content being parsed. This is usually the - * language that the content is in. + * Get the target language for the content being parsed. This is usually the + * language that the content is in. + * + * @since 1.19 + * + * @return Language|null */ - function getTargetLanguage() { + public function getTargetLanguage() { $target = $this->mOptions->getTargetLanguage(); + if ( $target !== null ) { return $target; } elseif( $this->mOptions->getInterfaceMessage() ) { return $this->mOptions->getUserLangObj(); } elseif( is_null( $this->mTitle ) ) { - throw new MWException( __METHOD__.': $this->mTitle is null' ); + throw new MWException( __METHOD__ . ': $this->mTitle is null' ); } + return $this->mTitle->getPageLanguage(); } @@ -761,11 +799,14 @@ class Parser { * in the text with a random marker and returns the next text. The output * parameter $matches will be an associative array filled with data in * the form: + * + * @code * 'UNIQ-xxxxx' => array( * 'element', * 'tag content', * array( 'param' => 'x' ), * 'tag content' ) ) + * @endcode * * @param $elements array list of element names. Comments are always extracted. * @param $text string Source text string. @@ -864,6 +905,7 @@ class Parser { * parse the wiki syntax used to render tables * * @private + * @return string */ function doTableStuff( $text ) { wfProfileIn( __METHOD__ ); @@ -1094,6 +1136,7 @@ class Parser { $text = $this->replaceVariables( $text ); } + wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) ); $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) ); wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) ); @@ -1146,7 +1189,7 @@ class Parser { '!(?: # Start cases (].*?) | # m[1]: Skip link text (<.*?>) | # m[2]: Skip stuff inside HTML elements' . " - (\\b(?:$prots)$urlChar+) | # m[3]: Free external links" . ' + (\\b(?i:$prots)$urlChar+) | # m[3]: Free external links" . ' (?:RFC|PMID)\s+([0-9]+) | # m[4]: RFC or PMID, capture number ISBN\s+(\b # m[5]: ISBN, capture number (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix @@ -1189,7 +1232,7 @@ class Parser { throw new MWException( __METHOD__.': unrecognised match type "' . substr( $m[0], 0, 20 ) . '"' ); } - $url = wfMsgForContent( $urlmsg, $id ); + $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text(); return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $CssClass ); } elseif ( isset( $m[5] ) && $m[5] !== '' ) { # ISBN @@ -1249,7 +1292,7 @@ class Parser { $text = $this->maybeMakeExternalImage( $url ); if ( $text === false ) { # Not an image, make a link - $text = Linker::makeExternalLink( $url, + $text = Linker::makeExternalLink( $url, $this->getConverterLanguage()->markNoConversion($url), true, 'free', $this->getExternalLinkAttribs( $url ) ); # Register it in the output object... @@ -1646,7 +1689,7 @@ class Parser { } if ( !$text && $this->mOptions->getEnableImageWhitelist() && preg_match( self::EXT_IMAGE_REGEX, $url ) ) { - $whitelist = explode( "\n", wfMsgForContent( 'external_image_whitelist' ) ); + $whitelist = explode( "\n", wfMessage( 'external_image_whitelist' )->inContentLanguage()->text() ); foreach ( $whitelist as $entry ) { # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments if ( strpos( $entry, '#' ) === 0 || $entry === '' ) { @@ -1698,7 +1741,7 @@ class Parser { $holders = new LinkHolderArray( $this ); - # split the entire text string on occurences of [[ + # split the entire text string on occurrences of [[ $a = StringUtils::explode( '[[', ' ' . $s ); # get the first element (all text up to first [[), and remove the space we added $s = $a->current(); @@ -1711,7 +1754,7 @@ class Parser { if ( $useLinkPrefixExtension ) { # Match the end of a line for a word that's not followed by whitespace, # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched - $e2 = wfMsgForContent( 'linkprefix' ); + $e2 = wfMessage( 'linkprefix' )->inContentLanguage()->text(); } if ( is_null( $this->mTitle ) ) { @@ -1733,7 +1776,7 @@ class Parser { } if ( $this->getConverterLanguage()->hasVariants() ) { - $selflink = $this->getConverterLanguage()->autoConvertToAllVariants( + $selflink = $this->getConverterLanguage()->autoConvertToAllVariants( $this->mTitle->getPrefixedText() ); } else { $selflink = array( $this->mTitle->getPrefixedText() ); @@ -1812,7 +1855,7 @@ class Parser { # Don't allow internal links to pages containing # PROTO: where PROTO is a valid URL protocol; these # should be external links. - if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $m[1] ) ) { + if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $m[1] ) ) { $s .= $prefix . '[[' . $line ; wfProfileOut( __METHOD__."-misc" ); continue; @@ -1902,11 +1945,9 @@ class Parser { # Link not escaped by : , create the various objects if ( $noforce ) { - global $wgContLang; - # Interwikis wfProfileIn( __METHOD__."-interwiki" ); - if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) { + if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) { $this->mOutput->addLanguageLink( $nt->getFullText() ); $s = rtrim( $s . $prefix ); $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail; @@ -2051,7 +2092,7 @@ class Parser { * @return String: less-or-more HTML with NOPARSE bits */ function armorLinks( $text ) { - return preg_replace( '/\b(' . wfUrlProtocols() . ')/', + return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/', "{$this->mUniqPrefix}NOPARSE$1", $text ); } @@ -2122,7 +2163,7 @@ class Parser { * element appropriate to the prefix character passed into them. * @private * - * @param $char char + * @param $char string * * @return string */ @@ -2384,7 +2425,7 @@ class Parser { } /** - * Split up a string on ':', ignoring any occurences inside tags + * Split up a string on ':', ignoring any occurrences inside tags * to prevent illegal overlapping. * * @param $str String the string to split @@ -2698,6 +2739,18 @@ class Parser { $subjPage = $this->mTitle->getSubjectPage(); $value = wfEscapeWikiText( $subjPage->getPrefixedUrl() ); break; + case 'pageid': // requested in bug 23427 + $pageid = $this->getTitle()->getArticleId(); + if( $pageid == 0 ) { + # 0 means the page doesn't exist in the database, + # which means the user is previewing a new page. + # The vary-revision flag must be set, because the magic word + # will have a different value once the page is saved. + $this->mOutput->setFlag( 'vary-revision' ); + wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" ); + } + $value = $pageid ? $pageid : null; + break; case 'revisionid': # Let the edit saving system know we should parse the page # *after* a revision ID has been assigned. @@ -2760,6 +2813,9 @@ class Parser { case 'namespacee': $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) ); break; + case 'namespacenumber': + $value = $this->mTitle->getNamespace(); + break; case 'talkspace': $value = $this->mTitle->canTalk() ? str_replace( '_',' ',$this->mTitle->getTalkNsText() ) : ''; break; @@ -2834,7 +2890,8 @@ class Parser { $value = $pageLang->formatNum( SiteStats::edits() ); break; case 'numberofviews': - $value = $pageLang->formatNum( SiteStats::views() ); + global $wgDisableCounters; + $value = !$wgDisableCounters ? $pageLang->formatNum( SiteStats::views() ) : ''; break; case 'currenttimestamp': $value = wfTimestamp( TS_MW, $ts ); @@ -2900,7 +2957,7 @@ class Parser { * * @param $text String: The text to parse * @param $flags Integer: bitwise combination of: - * self::PTD_FOR_INCLUSION Handle / as if the text is being + * self::PTD_FOR_INCLUSION Handle "" and "" as if the text is being * included. Default is to assume a direct page view. * * The generated DOM tree must depend only on the input text and the flags. @@ -3027,13 +3084,14 @@ class Parser { * 'post-expand-template-inclusion' (corresponding messages: * 'post-expand-template-inclusion-warning', * 'post-expand-template-inclusion-category') - * @param $current Current value - * @param $max Maximum allowed, when an explicit limit has been + * @param $current int|null Current value + * @param $max int|null Maximum allowed, when an explicit limit has been * exceeded, provide the values (optional) */ - function limitationWarn( $limitationType, $current=null, $max=null) { + function limitationWarn( $limitationType, $current = '', $max = '' ) { # does no harm if $current and $max are present but are unnecessary for the message - $warning = wfMsgExt( "$limitationType-warning", array( 'parsemag', 'escape' ), $current, $max ); + $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max ) + ->inContentLanguage()->escaped(); $this->mOutput->addWarning( $warning ); $this->addTrackingCategory( "$limitationType-category" ); } @@ -3051,7 +3109,7 @@ class Parser { * @private */ function braceSubstitution( $piece, $frame ) { - global $wgNonincludableNamespaces, $wgContLang; + global $wgContLang; wfProfileIn( __METHOD__ ); wfProfileIn( __METHOD__.'-setup' ); @@ -3238,7 +3296,8 @@ class Parser { if ( $frame->depth >= $limit ) { $found = true; $text = '' - . wfMsgForContent( 'parser-template-recursion-depth-warning', $limit ) + . wfMessage( 'parser-template-recursion-depth-warning' ) + ->numParams( $limit )->inContentLanguage()->text() . ''; } } @@ -3246,8 +3305,11 @@ class Parser { # Load from database if ( !$found && $title ) { - $titleProfileIn = __METHOD__ . "-title-" . $title->getDBKey(); - wfProfileIn( $titleProfileIn ); // template in + if ( !Profiler::instance()->isPersistent() ) { + # Too many unique items can kill profiling DBs/collectors + $titleProfileIn = __METHOD__ . "-title-" . $title->getDBKey(); + wfProfileIn( $titleProfileIn ); // template in + } wfProfileIn( __METHOD__ . '-loadtpl' ); if ( !$title->isExternal() ) { if ( $title->isSpecialPage() @@ -3281,7 +3343,7 @@ class Parser { $isHTML = true; $this->disableCache(); } - } elseif ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) { + } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) { $found = false; # access denied wfDebug( __METHOD__.": template inclusion denied for " . $title->getPrefixedDBkey() ); } else { @@ -3315,7 +3377,9 @@ class Parser { # This has to be done after redirect resolution to avoid infinite loops via redirects if ( !$frame->loopCheck( $title ) ) { $found = true; - $text = '' . wfMsgForContent( 'parser-template-loop-warning', $titleText ) . ''; + $text = '' + . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text() + . ''; wfDebug( __METHOD__.": template loop broken at '$titleText'\n" ); } wfProfileOut( __METHOD__ . '-loadtpl' ); @@ -3362,10 +3426,8 @@ class Parser { } # Replace raw HTML by a placeholder - # Add a blank line preceding, to prevent it from mucking up - # immediately preceding headings if ( $isHTML ) { - $text = "\n\n" . $this->insertStripItem( $text ); + $text = $this->insertStripItem( $text ); } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) { # Escape nowiki-style return values $text = wfEscapeWikiText( $text ); @@ -3476,7 +3538,7 @@ class Parser { * Static function to get a template * Can be overridden via ParserOptions::setTemplateCallback(). * - * @parma $title Title + * @param $title Title * @param $parser Parser * * @return array @@ -3505,7 +3567,7 @@ class Parser { # Get the revision $rev = $id ? Revision::newFromId( $id ) - : Revision::newFromTitle( $title ); + : Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); $rev_id = $rev ? $rev->getId() : 0; # If there is no current revision, there is no page if ( $id === false && !$rev ) { @@ -3556,7 +3618,7 @@ class Parser { * If 'broken' is a key in $options then the file will appear as a broken thumbnail. * @param Title $title * @param Array $options Array of options to RepoGroup::findFile - * @return File|false + * @return File|bool */ function fetchFile( $title, $options = array() ) { $res = $this->fetchFileAndTitle( $title, $options ); @@ -3607,13 +3669,13 @@ class Parser { global $wgEnableScaryTranscluding; if ( !$wgEnableScaryTranscluding ) { - return wfMsgForContent('scarytranscludedisabled'); + return wfMessage('scarytranscludedisabled')->inContentLanguage()->text(); } $url = $title->getFullUrl( "action=$action" ); if ( strlen( $url ) > 255 ) { - return wfMsgForContent( 'scarytranscludetoolong' ); + return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text(); } return $this->fetchScaryTemplateMaybeFromCache( $url ); } @@ -3634,7 +3696,7 @@ class Parser { $text = Http::get( $url ); if ( !$text ) { - return wfMsgForContent( 'scarytranscludefailed', $url ); + return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text(); } $dbw = wfGetDB( DB_MASTER ); @@ -3650,7 +3712,7 @@ class Parser { * Triple brace replacement -- used for template arguments * @private * - * @param $peice array + * @param $piece array * @param $frame PPFrame * * @return array @@ -3700,7 +3762,7 @@ class Parser { * Return the text to be used for a given extension tag. * This is the ghost of strip(). * - * @param $params Associative array of parameters: + * @param $params array Associative array of parameters: * name PPNode for the tag name * attr PPNode for unparsed text where tag attributes are thought to be * attributes Optional associative array of parsed attributes @@ -3808,12 +3870,8 @@ class Parser { * @return Boolean: false if the limit has been exceeded */ function incrementExpensiveFunctionCount() { - global $wgExpensiveParserFunctionLimit; $this->mExpensiveFunctionCount++; - if ( $this->mExpensiveFunctionCount <= $wgExpensiveParserFunctionLimit ) { - return true; - } - return false; + return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit(); } /** @@ -3921,6 +3979,7 @@ class Parser { * @param $text String * @param $origText String: original, untouched wikitext * @param $isMain Boolean + * @return mixed|string * @private */ function formatHeadings( $text, $origText, $isMain=true ) { @@ -4147,7 +4206,7 @@ class Parser { # Don't number the heading if it is the only one (looks silly) if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) { # the two are different if the line contains a link - $headline = $numbering . ' ' . $headline; + $headline = Html::element( 'span', array( 'class' => 'mw-headline-number' ), $numbering ) . ' ' . $headline; } # Create the anchor for linking from the TOC to the section @@ -4286,7 +4345,7 @@ class Parser { } /** - * Transform wiki markup when saving a page by doing \r\n -> \n + * Transform wiki markup when saving a page by doing "\r\n" -> "\n" * conversion, substitting signatures, {{subst:}} templates, etc. * * @param $text String: the text to transform @@ -4362,7 +4421,7 @@ class Parser { $text = $this->replaceVariables( $text ); # This works almost by chance, as the replaceVariables are done before the getUserSig(), - # which may corrupt this parser instance via its wfMsgExt( parsemag ) call- + # which may corrupt this parser instance via its wfMessage()->text() call- # Signatures $sigText = $this->getUserSig( $user ); @@ -4373,13 +4432,12 @@ class Parser { ) ); # Context links: [[|name]] and [[name (context)|]] - global $wgLegalTitleChars; - $tc = "[$wgLegalTitleChars]"; + $tc = '[' . Title::legalChars() . ']'; $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii! $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]] $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]] - $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]] + $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]] $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" @@ -4583,7 +4641,7 @@ class Parser { } /** - * Create an HTML-style tag, e.g. special text + * Create an HTML-style tag, e.g. "special text" * The callback should have the following form: * function myParserHook( $text, $params, $parser, $frame ) { ... } * @@ -4601,13 +4659,15 @@ class Parser { * 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 $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 + * @return Mixed|null The old value of the mTagHooks array associated with the hook */ public function setHook( $tag, $callback ) { $tag = strtolower( $tag ); - if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" ); + if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) { + throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" ); + } $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null; $this->mTagHooks[$tag] = $callback; if ( !in_array( $tag, $this->mStripList ) ) { @@ -4629,13 +4689,15 @@ class Parser { * @since 1.10 * @todo better document or deprecate this * - * @param $tag Mixed: the tag to use, e.g. 'hook' for + * @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 + * @return Mixed|null 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" ); + if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) { + throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" ); + } $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null; $this->mTransparentTagHooks[$tag] = $callback; @@ -4691,7 +4753,7 @@ class Parser { * Please read the documentation in includes/parser/Preprocessor.php for more information * about the methods available in PPFrame and PPNode. * - * @return The old callback function for this name, if any + * @return string|callback The old callback function for this name, if any */ public function setFunctionHook( $id, $callback, $flags = 0 ) { global $wgContLang; @@ -4735,9 +4797,10 @@ class Parser { } /** - * Create a tag function, e.g. some stuff. + * Create a tag function, e.g. "some stuff". * Unlike tag hooks, tag functions are parsed at preprocessor level. * Unlike parser functions, their content is not preprocessed. + * @return null */ function setFunctionTagHook( $tag, $callback, $flags ) { $tag = strtolower( $tag ); @@ -4755,7 +4818,7 @@ class Parser { /** * @todo FIXME: Update documentation. makeLinkObj() is deprecated. - * Replace link placeholders with actual links, in the buffer + * Replace "" link placeholders with actual links, in the buffer * Placeholders created in Skin::makeLinkObj() * * @param $text string @@ -4768,7 +4831,7 @@ class Parser { } /** - * Replace link placeholders with plain text of links + * Replace "" link placeholders with plain text of links * (not HTML-formatted). * * @param $text String @@ -4845,30 +4908,41 @@ class Parser { $label = ''; $alt = ''; + $link = ''; if ( isset( $matches[3] ) ) { // 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]); + $parameterMatches = StringUtils::explode('|', $matches[3]); $magicWordAlt = MagicWord::get( 'img_alt' ); + $magicWordLink = MagicWord::get( 'img_link' ); - foreach ( $altmatches as $altmatch ) { - $match = $magicWordAlt->matchVariableStartToEnd( $altmatch ); - if ( $match ) { + foreach ( $parameterMatches as $parameterMatch ) { + if ( $match = $magicWordAlt->matchVariableStartToEnd( $parameterMatch ) ) { $alt = $this->stripAltText( $match, false ); } + elseif( $match = $magicWordLink->matchVariableStartToEnd( $parameterMatch ) ){ + $link = strip_tags($this->replaceLinkHoldersText($match)); + $chars = self::EXT_LINK_URL_CLASS; + $prots = $this->mUrlProtocols; + //check to see if link matches an absolute url, if not then it must be a wiki link. + if(!preg_match( "/^($prots)$chars+$/u", $link)){ + $localLinkTitle = Title::newFromText($link); + $link = $localLinkTitle->getLocalURL(); + } + } else { // concatenate all other pipes - $label .= '|' . $altmatch; + $label .= '|' . $parameterMatch; } } // remove the first pipe $label = substr( $label, 1 ); } - $ig->add( $title, $label, $alt ); + $ig->add( $title, $label, $alt ,$link); } return $ig->toHTML(); } @@ -4890,7 +4964,7 @@ class Parser { 'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom' ), 'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless', - 'upright', 'border', 'link', 'alt' ), + 'upright', 'border', 'link', 'alt', 'class' ), ); static $internalParamMap; if ( !$internalParamMap ) { @@ -4922,7 +4996,7 @@ class Parser { * * @param $title Title * @param $options String - * @param $holders LinkHolderArray|false + * @param $holders LinkHolderArray|bool * @return string HTML */ function makeImage( $title, $options, $holders = false ) { @@ -4940,6 +5014,7 @@ class Parser { # * upright reduce width for upright images, rounded to full __0 px # * border draw a 1px border around the image # * alt Text for HTML alt attribute (defaults to empty) + # * class Set a class for img node # * link Set the target of the image link. Can be external, interwiki, or local # vertical-align values (no % or length right now): # * baseline @@ -4983,27 +5058,22 @@ class Parser { # Special case; width and height come in one variable together if ( $type === 'handler' && $paramName === 'width' ) { - $m = array(); - # (bug 13500) In both cases (width/height and width only), - # permit trailing "px" for backward compatibility. - if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) { - $width = intval( $m[1] ); - $height = intval( $m[2] ); + $parsedWidthParam = $this->parseWidthParam( $value ); + if( isset( $parsedWidthParam['width'] ) ) { + $width = $parsedWidthParam['width']; if ( $handler->validateParam( 'width', $width ) ) { $params[$type]['width'] = $width; $validated = true; } + } + if( isset( $parsedWidthParam['height'] ) ) { + $height = $parsedWidthParam['height']; if ( $handler->validateParam( 'height', $height ) ) { $params[$type]['height'] = $height; $validated = true; } - } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) { - $width = intval( $value ); - if ( $handler->validateParam( 'width', $width ) ) { - $params[$type]['width'] = $width; - $validated = true; - } - } # else no validation -- bug 13436 + } + # else no validation -- bug 13436 } else { if ( $type === 'handler' ) { # Validate handler parameter @@ -5013,6 +5083,7 @@ class Parser { switch( $paramName ) { case 'manualthumb': case 'alt': + case 'class': # @todo FIXME: Possibly check validity here for # manualthumb? downstream behavior seems odd with # missing manual thumbs. @@ -5026,8 +5097,8 @@ class Parser { $paramName = 'no-link'; $value = true; $validated = true; - } elseif ( preg_match( "/^$prots/", $value ) ) { - if ( preg_match( "/^($prots)$chars+$/u", $value, $m ) ) { + } elseif ( preg_match( "/^(?i)$prots/", $value ) ) { + if ( preg_match( "/^((?i)$prots)$chars+$/u", $value, $m ) ) { $paramName = 'link-url'; $this->mOutput->addExternalLink( $value ); if ( $this->mOptions->getExternalLinkTarget() ) { @@ -5116,11 +5187,11 @@ class Parser { $params['frame']['title'] = $this->stripAltText( $caption, $holders ); } - wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params ) ); + wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) ); # Linker does the rest $time = isset( $options['time'] ) ? $options['time'] : false; - $ret = Linker::makeImageLink2( $title, $file, $params['frame'], $params['handler'], + $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'], $time, $descQuery, $this->mOptions->getThumbSize() ); # Give the handler a chance to modify the parser object @@ -5229,13 +5300,13 @@ class Parser { * * @param $text String: Page wikitext * @param $section String: a section identifier string of the form: - * - - ... -
+ * " - - ... -
" * * Currently the only recognised flag is "T", which means the target section number * was derived during a template inclusion parse, in other words this is a template * section edit link. If no flags are given, it was an ordinary section edit link. * This flag is required to avoid a section numbering mismatch when a section is - * enclosed by (bug 6563). + * enclosed by "" (bug 6563). * * The section number 0 pulls the text before the first heading; other numbers will * pull the given section along with its lower-level subsections. If the section is @@ -5381,7 +5452,7 @@ class Parser { * section does not exist, $oldtext is returned unchanged. * * @param $oldtext String: former text of the article - * @param $section Numeric: section identifier + * @param $section int section identifier * @param $text String: replacing text * @return String: modified text */ @@ -5464,7 +5535,7 @@ class Parser { /** * Mutator for $mDefaultSort * - * @param $sort New value + * @param $sort string New value */ public function setDefaultSort( $sort ) { $this->mDefaultSort = $sort; @@ -5542,7 +5613,7 @@ class Parser { * * @param $text String: text string to be stripped of wikitext * for use in a Section anchor - * @return Filtered text string + * @return string Filtered text string */ public function stripSectionName( $text ) { # Strip internal link markup @@ -5553,7 +5624,7 @@ class Parser { # @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 ); + $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text ); # Parse wikitext quotes (italics & bold) $text = $this->doQuotes( $text ); @@ -5691,7 +5762,7 @@ class Parser { * If the $data array has been stored persistently, the caller should first * check whether it is still valid, by calling isValidHalfParsedText(). * - * @param $data Serialized data + * @param $data array Serialized data * @return String */ function unserializeHalfParsedText( $data ) { @@ -5722,4 +5793,32 @@ class Parser { function isValidHalfParsedText( $data ) { return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION; } + + /** + * Parsed a width param of imagelink like 300px or 200x300px + * + * @param $value String + * + * @return array + * @since 1.20 + */ + public function parseWidthParam( $value ) { + $parsedWidthParam = array(); + if( $value === '' ) { + return $parsedWidthParam; + } + $m = array(); + # (bug 13500) In both cases (width/height and width only), + # permit trailing "px" for backward compatibility. + if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) { + $width = intval( $m[1] ); + $height = intval( $m[2] ); + $parsedWidthParam['width'] = $width; + $parsedWidthParam['height'] = $height; + } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) { + $width = intval( $value ); + $parsedWidthParam['width'] = $width; + } + return $parsedWidthParam; + } } diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php index 8b043290..6a4ef0c5 100644 --- a/includes/parser/ParserCache.php +++ b/includes/parser/ParserCache.php @@ -2,7 +2,23 @@ /** * Cache for outputs of the PHP parser * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Cache Parser */ /** @@ -77,6 +93,7 @@ class ParserCache { * * @param $article Article * @param $popts ParserOptions + * @return string */ function getETag( $article, $popts ) { return 'W/"' . $this->getParserOutputKey( $article, @@ -88,7 +105,7 @@ class ParserCache { * Retrieve the ParserOutput from ParserCache, even if it's outdated. * @param $article Article * @param $popts ParserOptions - * @return ParserOutput|false + * @return ParserOutput|bool False on failure */ public function getDirty( $article, $popts ) { $value = $this->get( $article, $popts, true ); @@ -102,8 +119,10 @@ class ParserCache { * * @todo Document parameter $useOutdated * - * @param $article Article - * @param $popts ParserOptions + * @param $article Article + * @param $popts ParserOptions + * @param $useOutdated Boolean (default true) + * @return bool|mixed|string */ public function getKey( $article, $popts, $useOutdated = true ) { global $wgCacheEpoch; @@ -139,11 +158,11 @@ class ParserCache { * Retrieve the ParserOutput from ParserCache. * false if not found or outdated. * - * @param $article Article - * @param $popts ParserOptions - * @param $useOutdated + * @param $article Article + * @param $popts ParserOptions + * @param $useOutdated Boolean (default false) * - * @return ParserOutput|false + * @return ParserOutput|bool False on failure */ public function get( $article, $popts, $useOutdated = false ) { global $wgCacheEpoch; diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php index 57d3a7eb..009b18a1 100644 --- a/includes/parser/ParserOptions.php +++ b/includes/parser/ParserOptions.php @@ -1,6 +1,21 @@ mTargetLanguage; } function getMaxIncludeSize() { return $this->mMaxIncludeSize; } function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; } + function getMaxGeneratedPPNodeCount() { return $this->mMaxGeneratedPPNodeCount; } function getMaxPPExpandDepth() { return $this->mMaxPPExpandDepth; } function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; } + /* @since 1.20 */ + function getExpensiveParserFunctionLimit() { return $this->mExpensiveParserFunctionLimit; } function getRemoveComments() { return $this->mRemoveComments; } function getTemplateCallback() { return $this->mTemplateCallback; } function getEnableLimitReport() { return $this->mEnableLimitReport; } function getCleanSignatures() { return $this->mCleanSignatures; } function getExternalLinkTarget() { return $this->mExternalLinkTarget; } + function getDisableContentConversion() { return $this->mDisableContentConversion; } + function getDisableTitleConversion() { return $this->mDisableTitleConversion; } function getMath() { $this->optionUsed( 'math' ); return $this->mMath; } function getThumbSize() { $this->optionUsed( 'thumbsize' ); @@ -285,13 +325,18 @@ class ParserOptions { function setTargetLanguage( $x ) { return wfSetVar( $this->mTargetLanguage, $x, true ); } function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); } function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); } + function setMaxGeneratedPPNodeCount( $x ) { return wfSetVar( $this->mMaxGeneratedPPNodeCount, $x ); } function setMaxTemplateDepth( $x ) { return wfSetVar( $this->mMaxTemplateDepth, $x ); } + /* @since 1.20 */ + function setExpensiveParserFunctionLimit( $x ) { return wfSetVar( $this->mExpensiveParserFunctionLimit, $x ); } function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); } function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); } function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); } function setCleanSignatures( $x ) { return wfSetVar( $this->mCleanSignatures, $x ); } function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); } + function disableContentConversion( $x = true ) { return wfSetVar( $this->mDisableContentConversion, $x ); } + function disableTitleConversion( $x = true ) { return wfSetVar( $this->mDisableTitleConversion, $x ); } function setMath( $x ) { return wfSetVar( $this->mMath, $x ); } function setUserLang( $x ) { if ( is_string( $x ) ) { @@ -380,7 +425,8 @@ class ParserOptions { global $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages, $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion, $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth, - $wgCleanSignatures, $wgExternalLinkTarget; + $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit, + $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion; wfProfileIn( __METHOD__ ); @@ -392,10 +438,14 @@ class ParserOptions { $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion; $this->mMaxIncludeSize = $wgMaxArticleSize * 1024; $this->mMaxPPNodeCount = $wgMaxPPNodeCount; + $this->mMaxGeneratedPPNodeCount = $wgMaxGeneratedPPNodeCount; $this->mMaxPPExpandDepth = $wgMaxPPExpandDepth; $this->mMaxTemplateDepth = $wgMaxTemplateDepth; + $this->mExpensiveParserFunctionLimit = $wgExpensiveParserFunctionLimit; $this->mCleanSignatures = $wgCleanSignatures; $this->mExternalLinkTarget = $wgExternalLinkTarget; + $this->mDisableContentConversion = $wgDisableLangConversion; + $this->mDisableTitleConversion = $wgDisableLangConversion || $wgDisableTitleConversion; $this->mUser = $user; $this->mNumberHeadings = $user->getOption( 'numberheadings' ); @@ -428,6 +478,7 @@ class ParserOptions { * Returns the full array of options that would have been used by * in 1.16. * Used to get the old parser cache entries when available. + * @return array */ public static function legacyOptions() { global $wgUseDynamicDates; diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php index 2d99a3b5..41b4a385 100644 --- a/includes/parser/ParserOutput.php +++ b/includes/parser/ParserOutput.php @@ -1,118 +1,26 @@ mCacheTime; } - - function containsOldMagic() { return $this->mContainsOldMagic; } - function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } - - /** - * setCacheTime() sets the timestamp expressing when the page has been rendered. - * This doesn not control expiry, see updateCacheExpiry() for that! - * @param $t string - * @return string - */ - function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } - - /** - * Sets the number of seconds after which this object should expire. - * This value is used with the ParserCache. - * If called with a value greater than the value provided at any previous call, - * the new call has no effect. The value returned by getCacheExpiry is smaller - * or equal to the smallest number that was provided as an argument to - * updateCacheExpiry(). - * - * @param $seconds number - */ - function updateCacheExpiry( $seconds ) { - $seconds = (int)$seconds; - - if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds ) { - $this->mCacheExpiry = $seconds; - } - - // hack: set old-style marker for uncacheable entries. - if ( $this->mCacheExpiry !== null && $this->mCacheExpiry <= 0 ) { - $this->mCacheTime = -1; - } - } - - /** - * Returns the number of seconds after which this object should expire. - * This method is used by ParserCache to determine how long the ParserOutput can be cached. - * The timestamp of expiry can be calculated by adding getCacheExpiry() to getCacheTime(). - * The value returned by getCacheExpiry is smaller or equal to the smallest number - * that was provided to a call of updateCacheExpiry(), and smaller or equal to the - * value of $wgParserCacheExpireTime. - */ - function getCacheExpiry() { - global $wgParserCacheExpireTime; - - if ( $this->mCacheTime < 0 ) { - return 0; - } // old-style marker for "not cachable" - - $expire = $this->mCacheExpiry; - - if ( $expire === null ) { - $expire = $wgParserCacheExpireTime; - } else { - $expire = min( $expire, $wgParserCacheExpireTime ); - } - - if( $this->containsOldMagic() ) { //compatibility hack - $expire = min( $expire, 3600 ); # 1 hour - } - - if ( $expire <= 0 ) { - return 0; // not cachable - } else { - return $expire; - } - } - - /** - * @return bool - */ - function isCacheable() { - return $this->getCacheExpiry() > 0; - } - - /** - * Return true if this cached output object predates the global or - * per-article cache invalidation timestamps, or if it comes from - * an incompatible older version. - * - * @param $touched String: the affected article's last touched timestamp - * @return Boolean - */ - public function expired( $touched ) { - global $wgCacheEpoch; - return !$this->isCacheable() || // parser says it's uncacheable - $this->getCacheTime() < $touched || - $this->getCacheTime() <= $wgCacheEpoch || - $this->getCacheTime() < wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) || // expiry period has passed - !isset( $this->mVersion ) || - version_compare( $this->mVersion, Parser::VERSION, "lt" ); - } -} - class ParserOutput extends CacheTime { var $mText, # The output text $mLanguageLinks, # List of the full text of language links, in the order they appear @@ -140,8 +48,9 @@ class ParserOutput extends CacheTime { $mProperties = array(), # Name/value pairs to be cached in the DB $mTOCHTML = '', # HTML of the TOC $mTimestamp; # Timestamp of the revision - private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change. - private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys) + private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change. + private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys) + private $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place. const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)())#'; @@ -166,6 +75,7 @@ class ParserOutput extends CacheTime { /** * callback used by getText to replace editsection tokens * @private + * @return mixed */ function replaceEditSectionLinksCallback( $m ) { global $wgOut, $wgLang; @@ -331,7 +241,7 @@ class ParserOutput extends CacheTime { } /** - * Add some text to the . + * Add some text to the "". * If $tag is set, the section with that tag will only be included once * in a given page. */ @@ -447,4 +357,45 @@ class ParserOutput extends CacheTime { function recordOption( $option ) { $this->mAccessedOptions[$option] = true; } + + /** + * Adds an update job to the output. Any update jobs added to the output will eventually bexecuted in order to + * store any secondary information extracted from the page's content. + * + * @since 1.20 + * + * @param DataUpdate $update + */ + public function addSecondaryDataUpdate( DataUpdate $update ) { + $this->mSecondaryDataUpdates[] = $update; + } + + /** + * Returns any DataUpdate jobs to be executed in order to store secondary information + * extracted from the page's content, including a LinksUpdate object for all links stored in + * this ParserOutput object. + * + * @since 1.20 + * + * @param $title Title of the page we're updating. If not given, a title object will be created based on $this->getTitleText() + * @param $recursive Boolean: queue jobs for recursive updates? + * + * @return Array. An array of instances of DataUpdate + */ + public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) { + if ( is_null( $title ) ) { + $title = Title::newFromText( $this->getTitleText() ); + } + + $linksUpdate = new LinksUpdate( $title, $this, $recursive ); + + if ( $this->mSecondaryDataUpdates === array() ) { + return array( $linksUpdate ); + } else { + $updates = array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) ); + } + + return $updates; + } + } diff --git a/includes/parser/Parser_DiffTest.php b/includes/parser/Parser_DiffTest.php index efad33f9..f25340fa 100644 --- a/includes/parser/Parser_DiffTest.php +++ b/includes/parser/Parser_DiffTest.php @@ -2,7 +2,23 @@ /** * Fake parser that output the difference of two different parsers * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** diff --git a/includes/parser/Parser_LinkHooks.php b/includes/parser/Parser_LinkHooks.php index 90e44943..6bcc324d 100644 --- a/includes/parser/Parser_LinkHooks.php +++ b/includes/parser/Parser_LinkHooks.php @@ -2,7 +2,23 @@ /** * Modified version of the PHP parser with hooks for wiki links; experimental * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** @@ -84,7 +100,7 @@ class Parser_LinkHooks extends Parser { * @param $flags Integer: a combination of the following flags: * SLH_PATTERN Use a regex link pattern rather than a namespace * - * @return The old callback function for this name, if any + * @return callback|null The old callback function for this name, if any */ public function setLinkHook( $ns, $callback, $flags = 0 ) { if( $flags & SLH_PATTERN && !is_string($ns) ) @@ -210,7 +226,7 @@ class Parser_LinkHooks extends Parser { # Don't allow internal links to pages containing # PROTO: where PROTO is a valid URL protocol; these # should be external links. - if( preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $titleText) ) { + if( preg_match('/^\b(?i:' . wfUrlProtocols() . ')/', $titleText) ) { wfProfileOut( __METHOD__ ); return $wt; } diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php index ae088fdb..bd13f9ae 100644 --- a/includes/parser/Preprocessor.php +++ b/includes/parser/Preprocessor.php @@ -2,7 +2,23 @@ /** * Interfaces for preprocessors * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file + * @ingroup Parser */ /** @@ -62,15 +78,19 @@ interface PPFrame { const RECOVER_ORIG = 27; // = 1|2|8|16 no constant expression support in PHP yet + /** This constant exists when $indexOffset is supported in newChild() */ + const SUPPORTS_INDEX_OFFSET = 1; + /** * Create a child frame * * @param $args array * @param $title Title + * @param $indexOffset A number subtracted from the index attributes of the arguments * * @return PPFrame */ - function newChild( $args = false, $title = false ); + function newChild( $args = false, $title = false, $indexOffset = 0 ); /** * Expand a document tree node @@ -211,7 +231,7 @@ interface PPNode { function getName(); /** - * Split a node into an associative array containing: + * Split a "" node into an associative array containing: * name PPNode name * index String index * value PPNode value @@ -219,13 +239,13 @@ interface PPNode { function splitArg(); /** - * Split an node into an associative array containing name, attr, inner and close + * Split an "" node into an associative array containing name, attr, inner and close * All values in the resulting array are PPNodes. Inner and close are optional. */ function splitExt(); /** - * Split an node + * Split an "" node */ function splitHeading(); } diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php index 066589f6..34de0ba5 100644 --- a/includes/parser/Preprocessor_DOM.php +++ b/includes/parser/Preprocessor_DOM.php @@ -2,6 +2,21 @@ /** * Preprocessor using PHP's dom extension * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup Parser */ @@ -41,7 +56,7 @@ class Preprocessor_DOM implements Preprocessor { } /** - * @param $args + * @param $args array * @return PPCustomFrame_DOM */ function newCustomFrame( $args ) { @@ -97,7 +112,7 @@ class Preprocessor_DOM implements Preprocessor { * * @param $text String: the text to parse * @param $flags Integer: bitwise combination of: - * Parser::PTD_FOR_INCLUSION Handle / as if the text is being + * Parser::PTD_FOR_INCLUSION Handle "" and "" as if the text is being * included. Default is to assume a direct page view. * * The generated DOM tree must depend only on the input text and the flags. @@ -147,6 +162,15 @@ class Preprocessor_DOM implements Preprocessor { } } + + // Fail if the number of elements exceeds acceptable limits + // Do not attempt to generate the DOM + $this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' ); + $max = $this->parser->mOptions->getMaxGeneratedPPNodeCount(); + if ( $this->parser->mGeneratedPPNodeCount > $max ) { + throw new MWException( __METHOD__.': generated node count limit exceeded' ); + } + wfProfileIn( __METHOD__.'-loadXML' ); $dom = new DOMDocument; wfSuppressWarnings(); @@ -220,6 +244,7 @@ class Preprocessor_DOM implements Preprocessor { $searchBase = "[{<\n"; #} $revText = strrev( $text ); // For fast reverse searches + $lengthText = strlen( $text ); $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start $accum =& $stack->getAccum(); # Current accumulator @@ -275,7 +300,7 @@ class Preprocessor_DOM implements Preprocessor { $accum .= htmlspecialchars( substr( $text, $i, $literalLength ) ); $i += $literalLength; } - if ( $i >= strlen( $text ) ) { + if ( $i >= $lengthText ) { if ( $currentClosing == "\n" ) { // Do a past-the-end run to finish off the heading $curChar = ''; @@ -339,10 +364,10 @@ class Preprocessor_DOM implements Preprocessor { // Unclosed comment in input, runs to end $inner = substr( $text, $i ); $accum .= '' . htmlspecialchars( $inner ) . ''; - $i = strlen( $text ); + $i = $lengthText; } else { // Search backwards for leading whitespace - $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0; + $wsStart = $i ? ( $i - strspn( $revText, ' ', $lengthText - $i ) ) : 0; // Search forwards for trailing whitespace // $wsEnd will be the position of the last space (or the '>' if there's none) $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 ); @@ -423,7 +448,7 @@ class Preprocessor_DOM implements Preprocessor { } else { // No end tag -- let it run out to the end of the text. $inner = substr( $text, $tagEndPos + 1 ); - $i = strlen( $text ); + $i = $lengthText; $close = ''; } } @@ -479,20 +504,20 @@ class Preprocessor_DOM implements Preprocessor { } elseif ( $found == 'line-end' ) { $piece = $stack->top; // A heading must be open, otherwise \n wouldn't have been in the search list - assert( $piece->open == "\n" ); + assert( '$piece->open == "\n"' ); $part = $piece->getCurrentPart(); // Search back through the input to see if it has a proper close // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient - $wsLength = strspn( $revText, " \t", strlen( $text ) - $i ); + $wsLength = strspn( $revText, " \t", $lengthText - $i ); $searchStart = $i - $wsLength; if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { // Comment found at line end // Search for equals signs before the comment $searchStart = $part->visualEnd; - $searchStart -= strspn( $revText, " \t", strlen( $text ) - $searchStart ); + $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart ); } $count = $piece->count; - $equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart ); + $equalsLength = strspn( $revText, '=', $lengthText - $searchStart ); if ( $equalsLength > 0 ) { if ( $searchStart - $equalsLength == $piece->startPos ) { // This is just a single string of equals signs on its own line @@ -911,7 +936,7 @@ class PPFrame_DOM implements PPFrame { * * @return PPTemplateFrame_DOM */ - function newChild( $args = false, $title = false ) { + function newChild( $args = false, $title = false, $indexOffset = 0 ) { $namedArgs = array(); $numberedArgs = array(); if ( $title === false ) { @@ -923,6 +948,9 @@ class PPFrame_DOM implements PPFrame { $args = $args->node; } foreach ( $args as $arg ) { + if ( $arg instanceof PPNode ) { + $arg = $arg->node; + } if ( !$xpath ) { $xpath = new DOMXPath( $arg->ownerDocument ); } @@ -932,6 +960,7 @@ class PPFrame_DOM implements PPFrame { if ( $nameNodes->item( 0 )->hasAttributes() ) { // Numbered parameter $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent; + $index = $index - $indexOffset; $numberedArgs[$index] = $value->item( 0 ); unset( $namedArgs[$index] ); } else { @@ -958,14 +987,25 @@ class PPFrame_DOM implements PPFrame { } if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) { + $this->parser->limitationWarn( 'node-count-exceeded', + $this->parser->mPPNodeCount, + $this->parser->mOptions->getMaxPPNodeCount() + ); return 'Node-count limit exceeded'; } if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) { + $this->parser->limitationWarn( 'expansion-depth-exceeded', + $expansionDepth, + $this->parser->mOptions->getMaxPPExpandDepth() + ); return 'Expansion depth limit exceeded'; } wfProfileIn( __METHOD__ ); ++$expansionDepth; + if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) { + $this->parser->mHighestExpansionDepth = $expansionDepth; + } if ( $root instanceof PPNode_DOM ) { $root = $root->node; @@ -1250,6 +1290,7 @@ class PPFrame_DOM implements PPFrame { /** * Virtual implode with brackets + * @return array */ function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { $args = array_slice( func_get_args(), 3 ); @@ -1522,6 +1563,10 @@ class PPCustomFrame_DOM extends PPFrame_DOM { } return $this->args[$index]; } + + function getArguments() { + return $this->args; + } } /** @@ -1623,10 +1668,10 @@ class PPNode_DOM implements PPNode { } /** - * Split a node into an associative array containing: - * name PPNode name - * index String index - * value PPNode value + * Split a "" node into an associative array containing: + * - name PPNode name + * - index String index + * - value PPNode value * * @return array */ @@ -1646,7 +1691,7 @@ class PPNode_DOM implements PPNode { } /** - * Split an node into an associative array containing name, attr, inner and close + * Split an "" node into an associative array containing name, attr, inner and close * All values in the resulting array are PPNodes. Inner and close are optional. * * @return array @@ -1673,7 +1718,8 @@ class PPNode_DOM implements PPNode { } /** - * Split a node + * Split a "" node + * @return array */ function splitHeading() { if ( $this->getName() !== 'h' ) { diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php index 2934181a..4f04c865 100644 --- a/includes/parser/Preprocessor_Hash.php +++ b/includes/parser/Preprocessor_Hash.php @@ -2,6 +2,21 @@ /** * Preprocessor using PHP arrays * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup Parser */ @@ -9,7 +24,7 @@ /** * Differences from DOM schema: * * attribute nodes are children - * * nodes that aren't at the top are replaced with + * * "" nodes that aren't at the top are replaced with * @ingroup Parser */ class Preprocessor_Hash implements Preprocessor { @@ -32,7 +47,7 @@ class Preprocessor_Hash implements Preprocessor { } /** - * @param $args + * @param $args array * @return PPCustomFrame_Hash */ function newCustomFrame( $args ) { @@ -76,7 +91,7 @@ class Preprocessor_Hash implements Preprocessor { * * @param $text String: the text to parse * @param $flags Integer: bitwise combination of: - * Parser::PTD_FOR_INCLUSION Handle / as if the text is being + * Parser::PTD_FOR_INCLUSION Handle "" and "" as if the text is being * included. Default is to assume a direct page view. * * The generated DOM tree must depend only on the input text and the flags. @@ -162,6 +177,7 @@ class Preprocessor_Hash implements Preprocessor { $searchBase = "[{<\n"; $revText = strrev( $text ); // For fast reverse searches + $lengthText = strlen( $text ); $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start $accum =& $stack->getAccum(); # Current accumulator @@ -216,7 +232,7 @@ class Preprocessor_Hash implements Preprocessor { $accum->addLiteral( substr( $text, $i, $literalLength ) ); $i += $literalLength; } - if ( $i >= strlen( $text ) ) { + if ( $i >= $lengthText ) { if ( $currentClosing == "\n" ) { // Do a past-the-end run to finish off the heading $curChar = ''; @@ -280,10 +296,10 @@ class Preprocessor_Hash implements Preprocessor { // Unclosed comment in input, runs to end $inner = substr( $text, $i ); $accum->addNodeWithText( 'comment', $inner ); - $i = strlen( $text ); + $i = $lengthText; } else { // Search backwards for leading whitespace - $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0; + $wsStart = $i ? ( $i - strspn( $revText, ' ', $lengthText - $i ) ) : 0; // Search forwards for trailing whitespace // $wsEnd will be the position of the last space (or the '>' if there's none) $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 ); @@ -368,7 +384,7 @@ class Preprocessor_Hash implements Preprocessor { } else { // No end tag -- let it run out to the end of the text. $inner = substr( $text, $tagEndPos + 1 ); - $i = strlen( $text ); + $i = $lengthText; $close = null; } } @@ -428,20 +444,20 @@ class Preprocessor_Hash implements Preprocessor { } elseif ( $found == 'line-end' ) { $piece = $stack->top; // A heading must be open, otherwise \n wouldn't have been in the search list - assert( $piece->open == "\n" ); + assert( '$piece->open == "\n"' ); $part = $piece->getCurrentPart(); // Search back through the input to see if it has a proper close // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient - $wsLength = strspn( $revText, " \t", strlen( $text ) - $i ); + $wsLength = strspn( $revText, " \t", $lengthText - $i ); $searchStart = $i - $wsLength; if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { // Comment found at line end // Search for equals signs before the comment $searchStart = $part->visualEnd; - $searchStart -= strspn( $revText, " \t", strlen( $text ) - $searchStart ); + $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart ); } $count = $piece->count; - $equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart ); + $equalsLength = strspn( $revText, '=', $lengthText - $searchStart ); if ( $equalsLength > 0 ) { if ( $searchStart - $equalsLength == $piece->startPos ) { // This is just a single string of equals signs on its own line @@ -869,11 +885,11 @@ class PPFrame_Hash implements PPFrame { * $args is optionally a multi-root PPNode or array containing the template arguments * * @param $args PPNode_Hash_Array|array - * @param $title Title|false + * @param $title Title|bool * * @return PPTemplateFrame_Hash */ - function newChild( $args = false, $title = false ) { + function newChild( $args = false, $title = false, $indexOffset = 0 ) { $namedArgs = array(); $numberedArgs = array(); if ( $title === false ) { @@ -889,8 +905,9 @@ class PPFrame_Hash implements PPFrame { $bits = $arg->splitArg(); if ( $bits['index'] !== '' ) { // Numbered parameter - $numberedArgs[$bits['index']] = $bits['value']; - unset( $namedArgs[$bits['index']] ); + $index = $bits['index'] - $indexOffset; + $numberedArgs[$index] = $bits['value']; + unset( $namedArgs[$index] ); } else { // Named parameter $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) ); @@ -915,12 +932,23 @@ class PPFrame_Hash implements PPFrame { } if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) { + $this->parser->limitationWarn( 'node-count-exceeded', + $this->parser->mPPNodeCount, + $this->parser->mOptions->getMaxPPNodeCount() + ); return 'Node-count limit exceeded'; } if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) { + $this->parser->limitationWarn( 'expansion-depth-exceeded', + $expansionDepth, + $this->parser->mOptions->getMaxPPExpandDepth() + ); return 'Expansion depth limit exceeded'; } ++$expansionDepth; + if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) { + $this->parser->mHighestExpansionDepth = $expansionDepth; + } $outStack = array( '', '' ); $iteratorStack = array( false, $root ); @@ -1470,6 +1498,10 @@ class PPCustomFrame_Hash extends PPFrame_Hash { } return $this->args[$index]; } + + function getArguments() { + return $this->args; + } } /** @@ -1543,7 +1575,7 @@ class PPNode_Hash_Tree implements PPNode { $children = array(); for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { if ( isset( $child->name ) && $child->name === $name ) { - $children[] = $name; + $children[] = $child; } } return $children; @@ -1572,10 +1604,10 @@ class PPNode_Hash_Tree implements PPNode { } /** - * Split a node into an associative array containing: - * name PPNode name - * index String index - * value PPNode value + * Split a "" node into an associative array containing: + * - name PPNode name + * - index String index + * - value PPNode value * * @return array */ @@ -1607,7 +1639,7 @@ class PPNode_Hash_Tree implements PPNode { } /** - * Split an node into an associative array containing name, attr, inner and close + * Split an "" node into an associative array containing name, attr, inner and close * All values in the resulting array are PPNodes. Inner and close are optional. * * @return array @@ -1635,7 +1667,7 @@ class PPNode_Hash_Tree implements PPNode { } /** - * Split an node + * Split an "" node * * @return array */ @@ -1661,7 +1693,7 @@ class PPNode_Hash_Tree implements PPNode { } /** - * Split a