From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- includes/parser/CacheTime.php | 98 ++- includes/parser/CoreParserFunctions.php | 710 +++++++++++----- includes/parser/CoreTagHooks.php | 26 +- includes/parser/DateFormatter.php | 51 +- includes/parser/LinkHolderArray.php | 243 +++--- includes/parser/MWTidy.php | 291 +++++++ includes/parser/Parser.php | 1364 ++++++++++++++++++------------- includes/parser/ParserCache.php | 108 ++- includes/parser/ParserDiffTest.php | 143 ++++ includes/parser/ParserOptions.php | 517 ++++++++---- includes/parser/ParserOutput.php | 449 ++++++---- includes/parser/Parser_DiffTest.php | 143 ---- includes/parser/Preprocessor.php | 176 +++- includes/parser/Preprocessor_DOM.php | 485 +++++++---- includes/parser/Preprocessor_Hash.php | 620 +++++++++----- includes/parser/StripState.php | 58 +- includes/parser/Tidy.php | 286 ------- 17 files changed, 3675 insertions(+), 2093 deletions(-) create mode 100644 includes/parser/MWTidy.php create mode 100644 includes/parser/ParserDiffTest.php delete mode 100644 includes/parser/Parser_DiffTest.php delete mode 100644 includes/parser/Tidy.php (limited to 'includes/parser') diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php index 8190a8a0..94abc266 100644 --- a/includes/parser/CacheTime.php +++ b/includes/parser/CacheTime.php @@ -27,24 +27,64 @@ * @ingroup Parser */ class CacheTime { + /** @var array|bool ParserOptions which have been taken into account to + * produce output or false if not available. + */ + public $mUsedOptions; - var $mVersion = Parser::VERSION, # Compatibility check + public $mVersion = Parser::VERSION, # Compatibility check $mCacheTime = '', # Time when this object was generated, or -1 for uncacheable. Used in ParserCache. $mCacheExpiry = null, # Seconds after which the object should expire, use 0 for uncachable. Used in ParserCache. - $mContainsOldMagic; # Boolean variable indicating if the input contained variables like {{CURRENTDAY}} + $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}} + $mCacheRevisionId = null; # Revision ID that was parsed - function getCacheTime() { return $this->mCacheTime; } + /** + * @return string TS_MW timestamp + */ + public function getCacheTime() { + return wfTimestamp( TS_MW, $this->mCacheTime ); + } - function containsOldMagic() { return $this->mContainsOldMagic; } - function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } + /** + * @return bool + */ + public function containsOldMagic() { + return $this->mContainsOldMagic; + } + + /** + * @param bool $com + * @return bool + */ + public 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 + * This does not control expiry, see updateCacheExpiry() for that! + * @param string $t * @return string */ - function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } + public function setCacheTime( $t ) { + return wfSetVar( $this->mCacheTime, $t ); + } + + /** + * @since 1.23 + * @return int|null Revision id, if any was set + */ + public function getCacheRevisionId() { + return $this->mCacheRevisionId; + } + + /** + * @since 1.23 + * @param int $id Revision id + */ + public function setCacheRevisionId( $id ) { + $this->mCacheRevisionId = $id; + } /** * Sets the number of seconds after which this object should expire. @@ -54,9 +94,9 @@ class CacheTime { * or equal to the smallest number that was provided as an argument to * updateCacheExpiry(). * - * @param $seconds number + * @param int $seconds */ - function updateCacheExpiry( $seconds ) { + public function updateCacheExpiry( $seconds ) { $seconds = (int)$seconds; if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds ) { @@ -78,7 +118,7 @@ class CacheTime { * value of $wgParserCacheExpireTime. * @return int|mixed|null */ - function getCacheExpiry() { + public function getCacheExpiry() { global $wgParserCacheExpireTime; if ( $this->mCacheTime < 0 ) { @@ -107,7 +147,7 @@ class CacheTime { /** * @return bool */ - function isCacheable() { + public function isCacheable() { return $this->getCacheExpiry() > 0; } @@ -116,17 +156,35 @@ class CacheTime { * per-article cache invalidation timestamps, or if it comes from * an incompatible older version. * - * @param string $touched the affected article's last touched timestamp - * @return Boolean + * @param string $touched The affected article's last touched timestamp + * @return bool */ 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" ); + + 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" ); } + /** + * Return true if this cached output object is for a different revision of + * the page. + * + * @todo We always return false if $this->getCacheRevisionId() is null; + * this prevents invalidating the whole parser cache when this change is + * deployed. Someday that should probably be changed. + * + * @since 1.23 + * @param int $id The affected article's current revision id + * @return bool + */ + public function isDifferentRevision( $id ) { + $cached = $this->getCacheRevisionId(); + return $cached !== null && $id !== $cached; + } } diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php index 4b6eeca2..eacbecd4 100644 --- a/includes/parser/CoreParserFunctions.php +++ b/includes/parser/CoreParserFunctions.php @@ -27,97 +27,69 @@ */ class CoreParserFunctions { /** - * @param $parser Parser + * @param Parser $parser * @return void */ - static function register( $parser ) { + public static function register( $parser ) { global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions; - # Syntax for arguments (see self::setFunctionHook): + # Syntax for arguments (see Parser::setFunctionHook): # "name for lookup in localized magic words array", # function callback, # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}} # instead of {{#int:...}}) + $noHashFunctions = array( + 'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc', + 'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl', + 'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural', + 'numberofpages', 'numberofusers', 'numberofactiveusers', + 'numberofarticles', 'numberoffiles', 'numberofadmins', + 'numberingroup', 'numberofedits', 'numberofviews', 'language', + 'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath', + 'pagesincategory', 'pagesize', 'protectionlevel', + 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee', + 'subjectspace', 'subjectspacee', 'pagename', 'pagenamee', + 'fullpagename', 'fullpagenamee', 'rootpagename', 'rootpagenamee', + 'basepagename', 'basepagenamee', 'subpagename', 'subpagenamee', + 'talkpagename', 'talkpagenamee', 'subjectpagename', + 'subjectpagenamee', 'pageid', 'revisionid', 'revisionday', + 'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear', + 'revisiontimestamp', 'revisionuser', 'cascadingsources', + ); + foreach ( $noHashFunctions as $func ) { + $parser->setFunctionHook( $func, array( __CLASS__, $func ), SFH_NO_HASH ); + } - $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'ns', array( __CLASS__, 'ns' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'nse', array( __CLASS__, 'nse' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'urlencode', array( __CLASS__, 'urlencode' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'lcfirst', array( __CLASS__, 'lcfirst' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'ucfirst', array( __CLASS__, 'ucfirst' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'lc', array( __CLASS__, 'lc' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'uc', array( __CLASS__, 'uc' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'localurl', array( __CLASS__, 'localurl' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'localurle', array( __CLASS__, 'localurle' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'fullurl', array( __CLASS__, 'fullurl' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'fullurle', array( __CLASS__, 'fullurle' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'canonicalurl', array( __CLASS__, 'canonicalurl' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'canonicalurle', array( __CLASS__, 'canonicalurle' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'formatnum', array( __CLASS__, 'formatnum' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'grammar', array( __CLASS__, 'grammar' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'gender', array( __CLASS__, 'gender' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'plural', array( __CLASS__, 'plural' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofpages', array( __CLASS__, 'numberofpages' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofusers', array( __CLASS__, 'numberofusers' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofactiveusers', array( __CLASS__, 'numberofactiveusers' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofarticles', array( __CLASS__, 'numberofarticles' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberoffiles', array( __CLASS__, 'numberoffiles' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofadmins', array( __CLASS__, 'numberofadmins' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberingroup', array( __CLASS__, 'numberingroup' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofedits', array( __CLASS__, 'numberofedits' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'numberofviews', array( __CLASS__, 'numberofviews' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'language', array( __CLASS__, 'language' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'padleft', array( __CLASS__, 'padleft' ), SFH_NO_HASH ); - $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 ); - $parser->setFunctionHook( 'pagesize', array( __CLASS__, 'pagesize' ), SFH_NO_HASH ); - $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 ); - $parser->setFunctionHook( 'subjectspacee', array( __CLASS__, 'subjectspacee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'pagename', array( __CLASS__, 'pagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'pagenamee', array( __CLASS__, 'pagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'fullpagename', array( __CLASS__, 'fullpagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'fullpagenamee', array( __CLASS__, 'fullpagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'rootpagename', array( __CLASS__, 'rootpagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'rootpagenamee', array( __CLASS__, 'rootpagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'basepagename', array( __CLASS__, 'basepagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'basepagenamee', array( __CLASS__, 'basepagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'subpagename', array( __CLASS__, 'subpagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'subpagenamee', array( __CLASS__, 'subpagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'talkpagename', array( __CLASS__, 'talkpagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'talkpagenamee', array( __CLASS__, 'talkpagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'subjectpagename', array( __CLASS__, 'subjectpagename' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'subjectpagenamee', array( __CLASS__, 'subjectpagenamee' ), SFH_NO_HASH ); - $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS ); - $parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) ); + $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) ); + $parser->setFunctionHook( 'speciale', array( __CLASS__, 'speciale' ) ); + $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) ); if ( $wgAllowDisplayTitle ) { $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH ); } if ( $wgAllowSlowParserFunctions ) { - $parser->setFunctionHook( 'pagesinnamespace', array( __CLASS__, 'pagesinnamespace' ), SFH_NO_HASH ); + $parser->setFunctionHook( + 'pagesinnamespace', + array( __CLASS__, 'pagesinnamespace' ), + SFH_NO_HASH + ); } } /** - * @param $parser Parser + * @param Parser $parser * @param string $part1 * @return array */ - static function intFunction( $parser, $part1 = '' /*, ... */ ) { + public static function intFunction( $parser, $part1 = '' /*, ... */ ) { if ( strval( $part1 ) !== '' ) { $args = array_slice( func_get_args(), 2 ); - $message = wfMessage( $part1, $args )->inLanguage( $parser->getOptions()->getUserLangObj() )->plain(); + $message = wfMessage( $part1, $args ) + ->inLanguage( $parser->getOptions()->getUserLangObj() )->plain(); + return array( $message, 'noparse' => false ); } else { return array( 'found' => false ); @@ -125,12 +97,13 @@ class CoreParserFunctions { } /** - * @param $parser Parser - * @param $date - * @param null $defaultPref - * @return mixed|string + * @param Parser $parser + * @param string $date + * @param string $defaultPref + * + * @return string */ - static function formatDate( $parser, $date, $defaultPref = null ) { + public static function formatDate( $parser, $date, $defaultPref = null ) { $lang = $parser->getFunctionLang(); $df = DateFormatter::getInstance( $lang ); @@ -148,7 +121,7 @@ class CoreParserFunctions { return $date; } - static function ns( $parser, $part1 = '' ) { + public static function ns( $parser, $part1 = '' ) { global $wgContLang; if ( intval( $part1 ) || $part1 == "0" ) { $index = intval( $part1 ); @@ -162,7 +135,7 @@ class CoreParserFunctions { } } - static function nse( $parser, $part1 = '' ) { + public static function nse( $parser, $part1 = '' ) { $ret = self::ns( $parser, $part1 ); if ( is_string( $ret ) ) { $ret = wfUrlencode( str_replace( ' ', '_', $ret ) ); @@ -177,12 +150,12 @@ class CoreParserFunctions { * Or to encode a value for the HTTP "path", spaces are encoded as '%20'. * For links to "wiki"s, or similar software, spaces are encoded as '_', * - * @param $parser Parser object + * @param Parser $parser * @param string $s The text to encode. * @param string $arg (optional): The type of encoding. * @return string */ - static function urlencode( $parser, $s = '', $arg = null ) { + public static function urlencode( $parser, $s = '', $arg = null ) { static $magicWords = null; if ( is_null( $magicWords ) ) { $magicWords = new MagicWordArray( array( 'url_path', 'url_query', 'url_wiki' ) ); @@ -208,44 +181,76 @@ class CoreParserFunctions { return $parser->markerSkipCallback( $s, $func ); } - static function lcfirst( $parser, $s = '' ) { + public static function lcfirst( $parser, $s = '' ) { global $wgContLang; return $wgContLang->lcfirst( $s ); } - static function ucfirst( $parser, $s = '' ) { + public static function ucfirst( $parser, $s = '' ) { global $wgContLang; return $wgContLang->ucfirst( $s ); } /** - * @param $parser Parser + * @param Parser $parser * @param string $s - * @return + * @return string */ - static function lc( $parser, $s = '' ) { + public static function lc( $parser, $s = '' ) { global $wgContLang; return $parser->markerSkipCallback( $s, array( $wgContLang, 'lc' ) ); } /** - * @param $parser Parser + * @param Parser $parser * @param string $s - * @return + * @return string */ - static function uc( $parser, $s = '' ) { + public static function uc( $parser, $s = '' ) { global $wgContLang; return $parser->markerSkipCallback( $s, array( $wgContLang, 'uc' ) ); } - static function localurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getLocalURL', $s, $arg ); } - static function localurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeLocalURL', $s, $arg ); } - static function fullurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getFullURL', $s, $arg ); } - static function fullurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeFullURL', $s, $arg ); } - static function canonicalurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getCanonicalURL', $s, $arg ); } - static function canonicalurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeCanonicalURL', $s, $arg ); } + public static function localurl( $parser, $s = '', $arg = null ) { + return self::urlFunction( 'getLocalURL', $s, $arg ); + } + + public static function localurle( $parser, $s = '', $arg = null ) { + $temp = self::urlFunction( 'getLocalURL', $s, $arg ); + if ( !is_string( $temp ) ) { + return $temp; + } else { + return htmlspecialchars( $temp ); + } + } + + public static function fullurl( $parser, $s = '', $arg = null ) { + return self::urlFunction( 'getFullURL', $s, $arg ); + } + + public static function fullurle( $parser, $s = '', $arg = null ) { + $temp = self::urlFunction( 'getFullURL', $s, $arg ); + if ( !is_string( $temp ) ) { + return $temp; + } else { + return htmlspecialchars( $temp ); + } + } + + public static function canonicalurl( $parser, $s = '', $arg = null ) { + return self::urlFunction( 'getCanonicalURL', $s, $arg ); + } - static function urlFunction( $func, $s = '', $arg = null ) { + public static function canonicalurle( $parser, $s = '', $arg = null ) { + $temp = self::urlFunction( 'getCanonicalURL', $s, $arg ); + if ( !is_string( $temp ) ) { + return $temp; + } else { + return htmlspecialchars( $temp ); + } + } + + public static function urlFunction( $func, $s = '', $arg = null ) { $title = Title::newFromText( $s ); # Due to order of execution of a lot of bits, the values might be encoded # before arriving here; if that's true, then the title can't be created @@ -271,12 +276,12 @@ class CoreParserFunctions { } /** - * @param $parser Parser + * @param Parser $parser * @param string $num * @param string $arg * @return string */ - static function formatnum( $parser, $num = '', $arg = null ) { + public static function formatnum( $parser, $num = '', $arg = null ) { if ( self::matchAgainstMagicword( 'rawsuffix', $arg ) ) { $func = array( $parser->getFunctionLang(), 'parseFormattedNumber' ); } elseif ( self::matchAgainstMagicword( 'nocommafysuffix', $arg ) ) { @@ -288,22 +293,22 @@ class CoreParserFunctions { } /** - * @param $parser Parser + * @param Parser $parser * @param string $case * @param string $word - * @return + * @return string */ - static function grammar( $parser, $case = '', $word = '' ) { + public static function grammar( $parser, $case = '', $word = '' ) { $word = $parser->killMarkers( $word ); return $parser->getFunctionLang()->convertGrammar( $word, $case ); } /** - * @param $parser Parser - * @param $username string - * @return + * @param Parser $parser + * @param string $username + * @return string */ - static function gender( $parser, $username ) { + public static function gender( $parser, $username ) { wfProfileIn( __METHOD__ ); $forms = array_slice( func_get_args(), 2 ); @@ -341,11 +346,11 @@ class CoreParserFunctions { } /** - * @param $parser Parser + * @param Parser $parser * @param string $text - * @return + * @return string */ - static function plural( $parser, $text = '' ) { + public static function plural( $parser, $text = '' ) { $forms = array_slice( func_get_args(), 2 ); $text = $parser->getFunctionLang()->parseFormattedNumber( $text ); settype( $text, ctype_digit( $text ) ? 'int' : 'float' ); @@ -356,13 +361,20 @@ class CoreParserFunctions { * Override the title of the page when viewed, provided we've been given a * title which will normalise to the canonical title * - * @param $parser Parser: parent parser - * @param string $text desired title text - * @return String + * @param Parser $parser Parent parser + * @param string $text Desired title text + * @param string $uarg + * @return string */ - static function displaytitle( $parser, $text = '' ) { + public static function displaytitle( $parser, $text = '', $uarg = '' ) { global $wgRestrictDisplayTitle; + static $magicWords = null; + if ( is_null( $magicWords ) ) { + $magicWords = new MagicWordArray( array( 'displaytitle_noerror', 'displaytitle_noreplace' ) ); + } + $arg = $magicWords->matchStartToEnd( $uarg ); + // parse a limited subset of wiki markup (just the single quote items) $text = $parser->doQuotes( $text ); @@ -373,7 +385,7 @@ class CoreParserFunctions { // list of disallowed tags for DISPLAYTITLE // these will be escaped even though they are allowed in normal wiki text $bad = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr', - 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rp', 'br' ); + 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' ); // disallow some styles that could be used to bypass $wgRestrictDisplayTitle if ( $wgRestrictDisplayTitle ) { @@ -399,13 +411,34 @@ class CoreParserFunctions { // only requested titles that normalize to the actual title are allowed through // if $wgRestrictDisplayTitle is true (it is by default) // mimic the escaping process that occurs in OutputPage::setPageTitle - $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $text, $htmlTagsCallback, array(), array(), $bad ) ); + $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( + $text, + $htmlTagsCallback, + array(), + array(), + $bad + ) ); $title = Title::newFromText( Sanitizer::stripAllTags( $text ) ); - if ( !$wgRestrictDisplayTitle ) { - $parser->mOutput->setDisplayTitle( $text ); - } elseif ( $title instanceof Title && $title->getFragment() == '' && $title->equals( $parser->mTitle ) ) { - $parser->mOutput->setDisplayTitle( $text ); + if ( !$wgRestrictDisplayTitle || + ( $title instanceof Title + && !$title->hasFragment() + && $title->equals( $parser->mTitle ) ) + ) { + $old = $parser->mOutput->getProperty( 'displaytitle' ); + if ( $old === false || $arg !== 'displaytitle_noreplace' ) { + $parser->mOutput->setDisplayTitle( $text ); + } + if ( $old !== false && $old !== $text && !$arg ) { + $converter = $parser->getConverterLanguage()->getConverter(); + return '' . + wfMessage( 'duplicate-displaytitle', + // Message should be parsed, but these params should only be escaped. + $converter->markNoConversion( wfEscapeWikiText( $old ) ), + $converter->markNoConversion( wfEscapeWikiText( $text ) ) + )->inContentLanguage()->text() . + ''; + } } return ''; @@ -414,19 +447,20 @@ class CoreParserFunctions { /** * Matches the given value against the value of given magic word * - * @param string $magicword magic word key - * @param mixed $value value to match - * @return boolean true on successful match + * @param string $magicword Magic word key + * @param string $value Value to match + * @return bool True on successful match */ - static private function matchAgainstMagicword( $magicword, $value ) { - if ( strval( $value ) === '' ) { + private static function matchAgainstMagicword( $magicword, $value ) { + $value = trim( strval( $value ) ); + if ( $value === '' ) { return false; } $mwObject = MagicWord::get( $magicword ); - return $mwObject->match( $value ); + return $mwObject->matchStartToEnd( $value ); } - static function formatRaw( $num, $raw ) { + public static function formatRaw( $num, $raw ) { if ( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) { return $num; } else { @@ -434,35 +468,35 @@ class CoreParserFunctions { return $wgContLang->formatNum( $num ); } } - static function numberofpages( $parser, $raw = null ) { + public static function numberofpages( $parser, $raw = null ) { return self::formatRaw( SiteStats::pages(), $raw ); } - static function numberofusers( $parser, $raw = null ) { + public static function numberofusers( $parser, $raw = null ) { return self::formatRaw( SiteStats::users(), $raw ); } - static function numberofactiveusers( $parser, $raw = null ) { + public static function numberofactiveusers( $parser, $raw = null ) { return self::formatRaw( SiteStats::activeUsers(), $raw ); } - static function numberofarticles( $parser, $raw = null ) { + public static function numberofarticles( $parser, $raw = null ) { return self::formatRaw( SiteStats::articles(), $raw ); } - static function numberoffiles( $parser, $raw = null ) { + public static function numberoffiles( $parser, $raw = null ) { return self::formatRaw( SiteStats::images(), $raw ); } - static function numberofadmins( $parser, $raw = null ) { + public static function numberofadmins( $parser, $raw = null ) { return self::formatRaw( SiteStats::numberingroup( 'sysop' ), $raw ); } - static function numberofedits( $parser, $raw = null ) { + public static function numberofedits( $parser, $raw = null ) { return self::formatRaw( SiteStats::edits(), $raw ); } - static function numberofviews( $parser, $raw = null ) { + public static function numberofviews( $parser, $raw = null ) { global $wgDisableCounters; return !$wgDisableCounters ? self::formatRaw( SiteStats::views(), $raw ) : ''; } - static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) { + public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) { return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw ); } - static function numberingroup( $parser, $name = '', $raw = null ) { + public static function numberingroup( $parser, $name = '', $raw = null ) { return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw ); } @@ -471,51 +505,53 @@ class CoreParserFunctions { * corresponding magic word * Note: function name changed to "mwnamespace" rather than "namespace" * to not break PHP 5.3 + * @param Parser $parser + * @param string $title * @return mixed|string */ - static function mwnamespace( $parser, $title = null ) { + public static function mwnamespace( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return str_replace( '_', ' ', $t->getNsText() ); } - static function namespacee( $parser, $title = null ) { + public static function namespacee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfUrlencode( $t->getNsText() ); } - static function namespacenumber( $parser, $title = null ) { + public static function namespacenumber( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return $t->getNamespace(); } - static function talkspace( $parser, $title = null ) { + public static function talkspace( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) { return ''; } return str_replace( '_', ' ', $t->getTalkNsText() ); } - static function talkspacee( $parser, $title = null ) { + public static function talkspacee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) { return ''; } return wfUrlencode( $t->getTalkNsText() ); } - static function subjectspace( $parser, $title = null ) { + public static function subjectspace( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return str_replace( '_', ' ', $t->getSubjectNsText() ); } - static function subjectspacee( $parser, $title = null ) { + public static function subjectspacee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; @@ -526,100 +562,102 @@ class CoreParserFunctions { /** * Functions to get and normalize pagenames, corresponding to the magic words * of the same names - * @return String + * @param Parser $parser + * @param string $title + * @return string */ - static function pagename( $parser, $title = null ) { + public static function pagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getText() ); } - static function pagenamee( $parser, $title = null ) { + public static function pagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getPartialURL() ); } - static function fullpagename( $parser, $title = null ) { + public static function fullpagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) { return ''; } return wfEscapeWikiText( $t->getPrefixedText() ); } - static function fullpagenamee( $parser, $title = null ) { + public static function fullpagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) { return ''; } return wfEscapeWikiText( $t->getPrefixedURL() ); } - static function subpagename( $parser, $title = null ) { + public static function subpagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getSubpageText() ); } - static function subpagenamee( $parser, $title = null ) { + public static function subpagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getSubpageUrlForm() ); } - static function rootpagename( $parser, $title = null ) { + public static function rootpagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getRootText() ); } - static function rootpagenamee( $parser, $title = null ) { + public static function rootpagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getRootText() ) ) ); } - static function basepagename( $parser, $title = null ) { + public static function basepagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getBaseText() ); } - static function basepagenamee( $parser, $title = null ) { + public static function basepagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) ) ); } - static function talkpagename( $parser, $title = null ) { + public static function talkpagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) { return ''; } return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() ); } - static function talkpagenamee( $parser, $title = null ) { + public static function talkpagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) || !$t->canTalk() ) { return ''; } return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() ); } - static function subjectpagename( $parser, $title = null ) { + public static function subjectpagename( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; } return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() ); } - static function subjectpagenamee( $parser, $title = null ) { + public static function subjectpagenamee( $parser, $title = null ) { $t = Title::newFromText( $title ); if ( is_null( $t ) ) { return ''; @@ -631,9 +669,13 @@ class CoreParserFunctions { * 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. + * @param Parser $parser + * @param string $name + * @param string $arg1 + * @param string $arg2 * @return string */ - static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) { + public static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) { global $wgContLang; static $magicWords = null; if ( is_null( $magicWords ) ) { @@ -695,46 +737,29 @@ class CoreParserFunctions { * Return the size of the given page, or 0 if it's nonexistent. This is an * expensive parser function and can't be called too many times per page. * - * @todo FIXME: Title::getLength() documentation claims that it adds things - * to the link cache, so the local cache here should be unnecessary, but - * in fact calling getLength() repeatedly for the same $page does seem to - * run one query for each call? - * @todo Document parameters - * - * @param $parser Parser - * @param $page String Name of page to check (Default: empty string) - * @param $raw String Should number be human readable with commas or just number + * @param Parser $parser + * @param string $page Name of page to check (Default: empty string) + * @param string $raw Should number be human readable with commas or just number * @return string */ - static function pagesize( $parser, $page = '', $raw = null ) { - static $cache = array(); + public static function pagesize( $parser, $page = '', $raw = null ) { $title = Title::newFromText( $page ); if ( !is_object( $title ) ) { - $cache[$page] = 0; return self::formatRaw( 0, $raw ); } - # Normalize name for cache - $page = $title->getPrefixedText(); - - $length = 0; - if ( isset( $cache[$page] ) ) { - $length = $cache[$page]; - } elseif ( $parser->incrementExpensiveFunctionCount() ) { - $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); - $pageID = $rev ? $rev->getPage() : 0; - $revID = $rev ? $rev->getId() : 0; - $length = $cache[$page] = $rev ? $rev->getSize() : 0; - - // Register dependency in templatelinks - $parser->mOutput->addTemplate( $title, $pageID, $revID ); - } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $title ); + $length = $rev ? $rev->getSize() : 0; return self::formatRaw( $length, $raw ); } /** - * Returns the requested protection level for the current page + * Returns the requested protection level for the current page. This + * is an expensive parser function and can't be called too many times + * per page, unless the protection levels for the given title have + * already been retrieved * * @param Parser $parser * @param string $type @@ -742,25 +767,28 @@ class CoreParserFunctions { * * @return string */ - static function protectionlevel( $parser, $type = '', $title = '' ) { + public static function protectionlevel( $parser, $type = '', $title = '' ) { $titleObject = Title::newFromText( $title ); if ( !( $titleObject instanceof Title ) ) { $titleObject = $parser->mTitle; } - $restrictions = $titleObject->getRestrictions( strtolower( $type ) ); - # Title::getRestrictions returns an array, its possible it may have - # multiple values in the future - return implode( $restrictions, ',' ); + if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) { + $restrictions = $titleObject->getRestrictions( strtolower( $type ) ); + # Title::getRestrictions returns an array, its possible it may have + # multiple values in the future + return implode( $restrictions, ',' ); + } + return ''; } /** * Gives language names. - * @param $parser Parser - * @param string $code Language code (of which to get name) - * @param string $inLanguage Language code (in which to get name) - * @return String + * @param Parser $parser + * @param string $code Language code (of which to get name) + * @param string $inLanguage Language code (in which to get name) + * @return string */ - static function language( $parser, $code = '', $inLanguage = '' ) { + public static function language( $parser, $code = '', $inLanguage = '' ) { $code = strtolower( $code ); $inLanguage = strtolower( $inLanguage ); $lang = Language::fetchLanguageName( $code, $inLanguage ); @@ -769,9 +797,14 @@ class CoreParserFunctions { /** * Unicode-safe str_pad with the restriction that $length is forced to be <= 500 + * @param Parser $parser + * @param string $string + * @param int $length + * @param string $padding + * @param int $direction * @return string */ - static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) { + public static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) { $padding = $parser->killMarkers( $padding ); $lengthOfPadding = mb_strlen( $padding ); if ( $lengthOfPadding == 0 ) { @@ -797,25 +830,25 @@ class CoreParserFunctions { } } - static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) { + public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) { return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT ); } - static function padright( $parser, $string = '', $length = 0, $padding = '0' ) { + public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) { return self::pad( $parser, $string, $length, $padding ); } /** - * @param $parser Parser - * @param $text + * @param Parser $parser + * @param string $text * @return string */ - static function anchorencode( $parser, $text ) { + public static function anchorencode( $parser, $text ) { $text = $parser->killMarkers( $text ); return (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 ); } - static function special( $parser, $text ) { + public static function special( $parser, $text ) { list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text ); if ( $page ) { $title = SpecialPage::getTitleFor( $page, $subpage ); @@ -827,12 +860,12 @@ class CoreParserFunctions { } } - static function speciale( $parser, $text ) { + public static function speciale( $parser, $text ) { return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) ); } /** - * @param $parser Parser + * @param Parser $parser * @param string $text The sortkey to use * @param string $uarg Either "noreplace" or "noerror" (in en) * both suppress errors, and noreplace does nothing if @@ -869,8 +902,9 @@ class CoreParserFunctions { } } - // Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}} - // or {{filepath|300px}}, {{filepath|200x300px}}, {{filepath|nowiki|200x300px}}, {{filepath|200x300px|nowiki}} + // 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 ); @@ -907,6 +941,9 @@ class CoreParserFunctions { /** * Parser function to extension tag adaptor + * @param Parser $parser + * @param PPFrame $frame + * @param array $args * @return string */ public static function tagObj( $parser, $frame, $args ) { @@ -949,4 +986,271 @@ class CoreParserFunctions { ); return $parser->extensionSubstitution( $params, $frame ); } + + /** + * Fetched the current revision of the given title and return this. + * Will increment the expensive function count and + * add a template link to get the value refreshed on changes. + * For a given title, which is equal to the current parser title, + * the revision object from the parser is used, when that is the current one + * + * @param Parser $parser + * @param Title $title + * @return Revision + * @since 1.23 + */ + private static function getCachedRevisionObject( $parser, $title = null ) { + static $cache = null; + if ( $cache == null ) { + $cache = new MapCacheLRU( 50 ); + } + + if ( is_null( $title ) ) { + return null; + } + + // Use the revision from the parser itself, when param is the current page + // and the revision is the current one + if ( $title->equals( $parser->getTitle() ) ) { + $parserRev = $parser->getRevisionObject(); + if ( $parserRev && $parserRev->isCurrent() ) { + // force reparse after edit with vary-revision flag + $parser->getOutput()->setFlag( 'vary-revision' ); + wfDebug( __METHOD__ . ": use current revision from parser, setting vary-revision...\n" ); + return $parserRev; + } + } + + // Normalize name for cache + $page = $title->getPrefixedDBkey(); + + if ( $cache->has( $page ) ) { // cache contains null values + return $cache->get( $page ); + } + if ( $parser->incrementExpensiveFunctionCount() ) { + $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); + $pageID = $rev ? $rev->getPage() : 0; + $revID = $rev ? $rev->getId() : 0; + $cache->set( $page, $rev ); // maybe null + + // Register dependency in templatelinks + $parser->getOutput()->addTemplate( $title, $pageID, $revID ); + + return $rev; + } + $cache->set( $page, null ); + return null; + } + + /** + * Get the pageid of a specified page + * @param Parser $parser + * @param string $title Title to get the pageid from + * @return int|null|string + * @since 1.23 + */ + public static function pageid( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // Use title from parser to have correct pageid after edit + if ( $t->equals( $parser->getTitle() ) ) { + $t = $parser->getTitle(); + return $t->getArticleID(); + } + + // These can't have ids + if ( !$t->canExist() || $t->isExternal() ) { + return 0; + } + + // Check the link cache, maybe something already looked it up. + $linkCache = LinkCache::singleton(); + $pdbk = $t->getPrefixedDBkey(); + $id = $linkCache->getGoodLinkID( $pdbk ); + if ( $id != 0 ) { + $parser->mOutput->addLink( $t, $id ); + return $id; + } + if ( $linkCache->isBadLink( $pdbk ) ) { + $parser->mOutput->addLink( $t, 0 ); + return $id; + } + + // We need to load it from the DB, so mark expensive + if ( $parser->incrementExpensiveFunctionCount() ) { + $id = $t->getArticleID(); + $parser->mOutput->addLink( $t, $id ); + return $id; + } + return null; + } + + /** + * Get the id from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the id from + * @return int|null|string + * @since 1.23 + */ + public static function revisionid( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? $rev->getId() : ''; + } + + /** + * Get the day from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the day from + * @return string + * @since 1.23 + */ + public static function revisionday( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'j' ) : ''; + } + + /** + * Get the day with leading zeros from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the day from + * @return string + * @since 1.23 + */ + public static function revisionday2( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'd' ) : ''; + } + + /** + * Get the month with leading zeros from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the month from + * @return string + * @since 1.23 + */ + public static function revisionmonth( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'm' ) : ''; + } + + /** + * Get the month from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the month from + * @return string + * @since 1.23 + */ + public static function revisionmonth1( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'n' ) : ''; + } + + /** + * Get the year from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the year from + * @return string + * @since 1.23 + */ + public static function revisionyear( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'Y' ) : ''; + } + + /** + * Get the timestamp from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the timestamp from + * @return string + * @since 1.23 + */ + public static function revisiontimestamp( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'YmdHis' ) : ''; + } + + /** + * Get the user from the last revision of a specified page. + * @param Parser $parser + * @param string $title Title to get the user from + * @return string + * @since 1.23 + */ + public static function revisionuser( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null( $t ) ) { + return ''; + } + // fetch revision from cache/database and return the value + $rev = self::getCachedRevisionObject( $parser, $t ); + return $rev ? $rev->getUserText() : ''; + } + + /** + * Returns the sources of any cascading protection acting on a specified page. + * Pages will not return their own title unless they transclude themselves. + * This is an expensive parser function and can't be called too many times per page, + * unless cascading protection sources for the page have already been loaded. + * + * @param Parser $parser + * @param string $title + * + * @return string + * @since 1.23 + */ + public static function cascadingsources( $parser, $title = '' ) { + $titleObject = Title::newFromText( $title ); + if ( !( $titleObject instanceof Title ) ) { + $titleObject = $parser->mTitle; + } + if ( $titleObject->areCascadeProtectionSourcesLoaded() + || $parser->incrementExpensiveFunctionCount() + ) { + $names = array(); + $sources = $titleObject->getCascadeProtectionSources(); + foreach ( $sources[0] as $sourceTitle ) { + $names[] = $sourceTitle->getPrefixedText(); + } + return implode( $names, '|' ); + } + return ''; + } + } diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php index a2eb6987..85920cc1 100644 --- a/includes/parser/CoreTagHooks.php +++ b/includes/parser/CoreTagHooks.php @@ -27,10 +27,10 @@ */ class CoreTagHooks { /** - * @param $parser Parser + * @param Parser $parser * @return void */ - static function register( $parser ) { + public static function register( $parser ) { global $wgRawHtml; $parser->setHook( 'pre', array( __CLASS__, 'pre' ) ); $parser->setHook( 'nowiki', array( __CLASS__, 'nowiki' ) ); @@ -50,7 +50,7 @@ class CoreTagHooks { * @param Parser $parser * @return string HTML */ - static function pre( $text, $attribs, $parser ) { + public static function pre( $text, $attribs, $parser ) { // Backwards-compatibility hack $content = StringUtils::delimiterReplace( '', '', '$1', $text, 'i' ); @@ -69,13 +69,13 @@ class CoreTagHooks { * * Uses undocumented extended tag hook return values, introduced in r61913. * - * @param $content string - * @param $attributes array - * @param $parser Parser + * @param string $content + * @param array $attributes + * @param Parser $parser * @throws MWException * @return array */ - static function html( $content, $attributes, $parser ) { + public static function html( $content, $attributes, $parser ) { global $wgRawHtml; if ( $wgRawHtml ) { return array( $content, 'markerType' => 'nowiki' ); @@ -91,12 +91,12 @@ class CoreTagHooks { * * Uses undocumented extended tag hook return values, introduced in r61913. * - * @param $content string - * @param $attributes array - * @param $parser Parser + * @param string $content + * @param array $attributes + * @param Parser $parser * @return array */ - static function nowiki( $content, $attributes, $parser ) { + public static function nowiki( $content, $attributes, $parser ) { $content = strtr( $content, array( '-{' => '-{', '}-' => '}-' ) ); return array( Xml::escapeTagsOnly( $content ), 'markerType' => 'nowiki' ); } @@ -107,7 +107,7 @@ class CoreTagHooks { * Renders a thumbnail list of the given images, with optional captions. * Full syntax documented on the wiki: * - * http://www.mediawiki.org/wiki/Help:Images#Gallery_syntax + * https://www.mediawiki.org/wiki/Help:Images#Gallery_syntax * * @todo break Parser::renderImageGallery out here too. * @@ -116,7 +116,7 @@ class CoreTagHooks { * @param Parser $parser * @return string HTML */ - static function gallery( $content, $attributes, $parser ) { + public static function gallery( $content, $attributes, $parser ) { return $parser->renderImageGallery( $content, $attributes ); } } diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php index 0a69b045..82f0e9d4 100644 --- a/includes/parser/DateFormatter.php +++ b/includes/parser/DateFormatter.php @@ -27,11 +27,11 @@ * @ingroup Parser */ class DateFormatter { - var $mSource, $mTarget; - var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD; + public $mSource, $mTarget; + public $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD; - var $regexes, $pDays, $pMonths, $pYears; - var $rules, $xMonths, $preferences; + public $regexes, $pDays, $pMonths, $pYears; + public $rules, $xMonths, $preferences; protected $lang; @@ -49,9 +49,9 @@ class DateFormatter { const LAST = 8; /** - * @param $lang Language In which language to format the date + * @param Language $lang In which language to format the date */ - function __construct( Language $lang ) { + public function __construct( Language $lang ) { $this->lang = $lang; $this->monthNames = $this->getMonthRegex(); @@ -120,9 +120,9 @@ class DateFormatter { /** * Get a DateFormatter object * - * @param $lang Language|string|null In which language to format the date + * @param Language|string|null $lang In which language to format the date * Defaults to the site content language - * @return DateFormatter object + * @return DateFormatter */ public static function &getInstance( $lang = null ) { global $wgMemc, $wgContLang; @@ -142,10 +142,11 @@ class DateFormatter { /** * @param string $preference User preference * @param string $text Text to reformat - * @param array $options can contain 'linked' and/or 'match-whole' - * @return mixed|String + * @param array $options Array can contain 'linked' and/or 'match-whole' + * + * @return string */ - function reformat( $preference, $text, $options = array( 'linked' ) ) { + public function reformat( $preference, $text, $options = array( 'linked' ) ) { $linked = in_array( 'linked', $options ); $match_whole = in_array( 'match-whole', $options ); @@ -192,10 +193,10 @@ class DateFormatter { } /** - * @param $matches + * @param array $matches * @return string */ - function replace( $matches ) { + public function replace( $matches ) { # Extract information from $matches $linked = true; if ( isset( $this->mLinked ) ) { @@ -204,7 +205,8 @@ class DateFormatter { $bits = array(); $key = $this->keys[$this->mSource]; - for ( $p = 0; $p < strlen( $key ); $p++ ) { + $keyLength = strlen( $key ); + for ( $p = 0; $p < $keyLength; $p++ ) { if ( $key[$p] != ' ' ) { $bits[$key[$p]] = $matches[$p + 1]; } @@ -214,11 +216,11 @@ class DateFormatter { } /** - * @param $bits array - * @param $link bool + * @param array $bits + * @param bool $link * @return string */ - function formatDate( $bits, $link = true ) { + public function formatDate( $bits, $link = true ) { $format = $this->targets[$this->mTarget]; if ( !$link ) { @@ -253,7 +255,8 @@ class DateFormatter { $bits['d'] = sprintf( '%02d', $bits['j'] ); } - for ( $p = 0; $p < strlen( $format ); $p++ ) { + $formatLength = strlen( $format ); + for ( $p = 0; $p < $formatLength; $p++ ) { $char = $format[$p]; switch ( $char ) { case 'd': # ISO day of month @@ -292,6 +295,7 @@ class DateFormatter { } } if ( $fail ) { + /** @todo FIXME: $matches doesn't exist here, what's expected? */ $text = $matches[0]; } @@ -314,7 +318,7 @@ class DateFormatter { * @todo document * @return string */ - function getMonthRegex() { + public function getMonthRegex() { $names = array(); for ( $i = 1; $i <= 12; $i++ ) { $names[] = $this->lang->getMonthName( $i ); @@ -325,10 +329,10 @@ class DateFormatter { /** * Makes an ISO month, e.g. 02, from a month name - * @param string $monthName month name + * @param string $monthName Month name * @return string ISO month name */ - function makeIsoMonth( $monthName ) { + public function makeIsoMonth( $monthName ) { $n = $this->xMonths[$this->lang->lc( $monthName )]; return sprintf( '%02d', $n ); } @@ -338,7 +342,7 @@ class DateFormatter { * @param string $year Year name * @return string ISO year name */ - function makeIsoYear( $year ) { + public function makeIsoYear( $year ) { # Assumes the year is in a nice format, as enforced by the regex if ( substr( $year, -2 ) == 'BC' ) { $num = intval( substr( $year, 0, -3 ) ) - 1; @@ -353,9 +357,10 @@ class DateFormatter { /** * @todo document + * @param string $iso * @return int|string */ - function makeNormalYear( $iso ) { + public function makeNormalYear( $iso ) { if ( $iso[0] == '-' ) { $text = ( intval( substr( $iso, 1 ) ) + 1 ) . ' BC'; } else { diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php index f1a0b258..7794fae4 100644 --- a/includes/parser/LinkHolderArray.php +++ b/includes/parser/LinkHolderArray.php @@ -25,19 +25,27 @@ * @ingroup Parser */ class LinkHolderArray { - var $internals = array(), $interwikis = array(); - var $size = 0; - var $parent; + public $internals = array(); + public $interwikis = array(); + public $size = 0; + + /** + * @var Parser + */ + public $parent; protected $tempIdOffset; - function __construct( $parent ) { + /** + * @param Parser $parent + */ + public function __construct( $parent ) { $this->parent = $parent; } /** * Reduce memory usage to reduce the impact of circular references */ - function __destruct() { + public function __destruct() { foreach ( $this as $name => $value ) { unset( $this->$name ); } @@ -49,9 +57,9 @@ class LinkHolderArray { * serializing at present. * * Compact the titles, only serialize the text form. - * @return array - */ - function __sleep() { + * @return array + */ + public function __sleep() { foreach ( $this->internals as &$nsLinks ) { foreach ( $nsLinks as &$entry ) { unset( $entry['title'] ); @@ -71,7 +79,7 @@ class LinkHolderArray { /** * Recreate the Title objects */ - function __wakeup() { + public function __wakeup() { foreach ( $this->internals as &$nsLinks ) { foreach ( $nsLinks as &$entry ) { $entry['title'] = Title::newFromText( $entry['pdbk'] ); @@ -88,9 +96,9 @@ class LinkHolderArray { /** * Merge another LinkHolderArray into this one - * @param $other LinkHolderArray + * @param LinkHolderArray $other */ - function merge( $other ) { + public function merge( $other ) { foreach ( $other->internals as $ns => $entries ) { $this->size += count( $entries ); if ( !isset( $this->internals[$ns] ) ) { @@ -110,11 +118,11 @@ class LinkHolderArray { * converted for use in the destination link holder. The resulting array of * strings will be returned. * - * @param $other LinkHolderArray - * @param array $texts of strings - * @return Array + * @param LinkHolderArray $other + * @param array $texts Array of strings + * @return array */ - function mergeForeign( $other, $texts ) { + public function mergeForeign( $other, $texts ) { $this->tempIdOffset = $idOffset = $this->parent->nextLinkID(); $maxId = 0; @@ -144,6 +152,10 @@ class LinkHolderArray { return $texts; } + /** + * @param array $m + * @return string + */ protected function mergeForeignCallback( $m ) { return $m[1] . ( $m[2] + $this->tempIdOffset ) . $m[3]; } @@ -151,17 +163,18 @@ class LinkHolderArray { /** * Get a subset of the current LinkHolderArray which is sufficient to * interpret the given text. + * @param string $text * @return LinkHolderArray */ - function getSubArray( $text ) { + public function getSubArray( $text ) { $sub = new LinkHolderArray( $this->parent ); # Internal links $pos = 0; while ( $pos < strlen( $text ) ) { if ( !preg_match( '//', - $text, $m, PREG_OFFSET_CAPTURE, $pos ) ) - { + $text, $m, PREG_OFFSET_CAPTURE, $pos ) + ) { break; } $ns = $m[1][0]; @@ -187,7 +200,7 @@ class LinkHolderArray { * Returns true if the memory requirements of this object are getting large * @return bool */ - function isBig() { + public function isBig() { global $wgLinkHolderBatchSize; return $this->size > $wgLinkHolderBatchSize; } @@ -196,7 +209,7 @@ class LinkHolderArray { * Clear all stored link holders. * Make sure you don't have any text left using these link holders, before you call this */ - function clear() { + public function clear() { $this->internals = array(); $this->interwikis = array(); $this->size = 0; @@ -208,14 +221,14 @@ class LinkHolderArray { * parsing of interwiki links, and secondly to allow all existence checks and * article length checks (for stub links) to be bundled into a single query. * - * @param $nt Title - * @param $text String + * @param Title $nt + * @param string $text * @param array $query [optional] * @param string $trail [optional] * @param string $prefix [optional] * @return string */ - function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) { + public function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) { wfProfileIn( __METHOD__ ); if ( !is_object( $nt ) ) { # Fail gracefully @@ -251,15 +264,16 @@ class LinkHolderArray { } /** - * @todo FIXME: Update documentation. makeLinkObj() is deprecated. * Replace link placeholders with actual links, in the buffer - * Placeholders created in Skin::makeLinkObj() - * @return array of link CSS classes, indexed by PDBK. + * + * @param string $text + * @return array Array of link CSS classes, indexed by PDBK. */ - function replace( &$text ) { + public function replace( &$text ) { wfProfileIn( __METHOD__ ); - $colours = $this->replaceInternal( $text ); // FIXME: replaceInternal doesn't return a value + /** @todo FIXME: replaceInternal doesn't return a value */ + $colours = $this->replaceInternal( $text ); $this->replaceInterwiki( $text ); wfProfileOut( __METHOD__ ); @@ -268,6 +282,7 @@ class LinkHolderArray { /** * Replace internal links + * @param string $text */ protected function replaceInternal( &$text ) { if ( !$this->internals ) { @@ -275,93 +290,99 @@ class LinkHolderArray { } wfProfileIn( __METHOD__ ); - global $wgContLang; + global $wgContLang, $wgContentHandlerUseDB; $colours = array(); $linkCache = LinkCache::singleton(); $output = $this->parent->getOutput(); - if ( $linkCache->useDatabase() ) { - wfProfileIn( __METHOD__ . '-check' ); - $dbr = wfGetDB( DB_SLAVE ); - $threshold = $this->parent->getOptions()->getStubThreshold(); + wfProfileIn( __METHOD__ . '-check' ); + $dbr = wfGetDB( DB_SLAVE ); + $threshold = $this->parent->getOptions()->getStubThreshold(); - # Sort by namespace - ksort( $this->internals ); + # Sort by namespace + ksort( $this->internals ); - $linkcolour_ids = array(); + $linkcolour_ids = array(); - # Generate query - $queries = array(); - foreach ( $this->internals as $ns => $entries ) { - foreach ( $entries as $entry ) { - $title = $entry['title']; - $pdbk = $entry['pdbk']; + # Generate query + $queries = array(); + foreach ( $this->internals as $ns => $entries ) { + foreach ( $entries as $entry ) { + /** @var Title $title */ + $title = $entry['title']; + $pdbk = $entry['pdbk']; - # Skip invalid entries. - # Result will be ugly, but prevents crash. - if ( is_null( $title ) ) { - continue; - } + # Skip invalid entries. + # Result will be ugly, but prevents crash. + if ( is_null( $title ) ) { + continue; + } - # Check if it's a static known link, e.g. interwiki - if ( $title->isAlwaysKnown() ) { - $colours[$pdbk] = ''; - } elseif ( $ns == NS_SPECIAL ) { - $colours[$pdbk] = 'new'; - } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { - $colours[$pdbk] = Linker::getLinkColour( $title, $threshold ); - $output->addLink( $title, $id ); - $linkcolour_ids[$id] = $pdbk; - } elseif ( $linkCache->isBadLink( $pdbk ) ) { - $colours[$pdbk] = 'new'; - } else { - # Not in the link cache, add it to the query - $queries[$ns][] = $title->getDBkey(); - } + # Check if it's a static known link, e.g. interwiki + if ( $title->isAlwaysKnown() ) { + $colours[$pdbk] = ''; + } elseif ( $ns == NS_SPECIAL ) { + $colours[$pdbk] = 'new'; + } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { + $colours[$pdbk] = Linker::getLinkColour( $title, $threshold ); + $output->addLink( $title, $id ); + $linkcolour_ids[$id] = $pdbk; + } elseif ( $linkCache->isBadLink( $pdbk ) ) { + $colours[$pdbk] = 'new'; + } else { + # Not in the link cache, add it to the query + $queries[$ns][] = $title->getDBkey(); } } - if ( $queries ) { - $where = array(); - foreach ( $queries as $ns => $pages ) { - $where[] = $dbr->makeList( - array( - 'page_namespace' => $ns, - 'page_title' => $pages, - ), - LIST_AND - ); - } - - $res = $dbr->select( - 'page', - array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ), - $dbr->makeList( $where, LIST_OR ), - __METHOD__ + } + if ( $queries ) { + $where = array(); + foreach ( $queries as $ns => $pages ) { + $where[] = $dbr->makeList( + array( + 'page_namespace' => $ns, + 'page_title' => array_unique( $pages ), + ), + LIST_AND ); + } - # Fetch data and form into an associative array - # non-existent = broken - foreach ( $res as $s ) { - $title = Title::makeTitle( $s->page_namespace, $s->page_title ); - $pdbk = $title->getPrefixedDBkey(); - $linkCache->addGoodLinkObjFromRow( $title, $s ); - $output->addLink( $title, $s->page_id ); - # @todo FIXME: Convoluted data flow - # The redirect status and length is passed to getLinkColour via the LinkCache - # Use formal parameters instead - $colours[$pdbk] = Linker::getLinkColour( $title, $threshold ); - //add id to the extension todolist - $linkcolour_ids[$s->page_id] = $pdbk; - } - unset( $res ); + $fields = array( 'page_id', 'page_namespace', 'page_title', + 'page_is_redirect', 'page_len', 'page_latest' ); + + if ( $wgContentHandlerUseDB ) { + $fields[] = 'page_content_model'; } - if ( count( $linkcolour_ids ) ) { - //pass an array of page_ids to an extension - wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); + + $res = $dbr->select( + 'page', + $fields, + $dbr->makeList( $where, LIST_OR ), + __METHOD__ + ); + + # Fetch data and form into an associative array + # non-existent = broken + foreach ( $res as $s ) { + $title = Title::makeTitle( $s->page_namespace, $s->page_title ); + $pdbk = $title->getPrefixedDBkey(); + $linkCache->addGoodLinkObjFromRow( $title, $s ); + $output->addLink( $title, $s->page_id ); + # @todo FIXME: Convoluted data flow + # The redirect status and length is passed to getLinkColour via the LinkCache + # Use formal parameters instead + $colours[$pdbk] = Linker::getLinkColour( $title, $threshold ); + //add id to the extension todolist + $linkcolour_ids[$s->page_id] = $pdbk; } - wfProfileOut( __METHOD__ . '-check' ); + unset( $res ); } + if ( count( $linkcolour_ids ) ) { + //pass an array of page_ids to an extension + wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); + } + wfProfileOut( __METHOD__ . '-check' ); # Do a second query for different language variants of links and categories if ( $wgContLang->hasVariants() ) { @@ -421,6 +442,7 @@ class LinkHolderArray { /** * Replace interwiki links + * @param string $text */ protected function replaceInterwiki( &$text ) { if ( empty( $this->interwikis ) ) { @@ -446,9 +468,10 @@ class LinkHolderArray { /** * Modify $this->internals and $colours according to language variant linking rules + * @param array $colours */ protected function doVariants( &$colours ) { - global $wgContLang; + global $wgContLang, $wgContentHandlerUseDB; $linkBatch = new LinkBatch(); $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) $output = $this->parent->getOutput(); @@ -486,6 +509,7 @@ class LinkHolderArray { // Then add variants of links to link batch $parentTitle = $this->parent->getTitle(); foreach ( $titlesAttrs as $i => $attrs ) { + /** @var Title $title */ list( $index, $title ) = $attrs; $ns = $title->getNamespace(); $text = $title->getText(); @@ -504,7 +528,7 @@ class LinkHolderArray { // Self-link checking for mixed/different variant titles. At this point, we // already know the exact title does not exist, so the link cannot be to a // variant of the current title that exists as a separate page. - if ( $variantTitle->equals( $parentTitle ) && $title->getFragment() === '' ) { + if ( $variantTitle->equals( $parentTitle ) && !$title->hasFragment() ) { $this->internals[$ns][$index]['selflink'] = true; continue 2; } @@ -536,8 +560,15 @@ class LinkHolderArray { if ( !$linkBatch->isEmpty() ) { // construct query $dbr = wfGetDB( DB_SLAVE ); + $fields = array( 'page_id', 'page_namespace', 'page_title', + 'page_is_redirect', 'page_len', 'page_latest' ); + + if ( $wgContentHandlerUseDB ) { + $fields[] = 'page_content_model'; + } + $varRes = $dbr->select( 'page', - array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ), + $fields, $linkBatch->constructSet( 'page', $dbr ), __METHOD__ ); @@ -609,10 +640,10 @@ class LinkHolderArray { * Replace link placeholders with plain text of links * (not HTML-formatted). * - * @param $text String - * @return String + * @param string $text + * @return string */ - function replaceText( $text ) { + public function replaceText( $text ) { wfProfileIn( __METHOD__ ); $text = preg_replace_callback( @@ -627,11 +658,11 @@ class LinkHolderArray { /** * Callback for replaceText() * - * @param $matches Array + * @param array $matches * @return string * @private */ - function replaceTextCallback( $matches ) { + public function replaceTextCallback( $matches ) { $type = $matches[1]; $key = $matches[2]; if ( $type == 'LINK' ) { diff --git a/includes/parser/MWTidy.php b/includes/parser/MWTidy.php new file mode 100644 index 00000000..b310862f --- /dev/null +++ b/includes/parser/MWTidy.php @@ -0,0 +1,291 @@ +mTokens = null; + $this->mUniqPrefix = null; + } + + /** + * @param string $text + * @return string + */ + public function getWrapped( $text ) { + $this->mTokens = new ReplacementArray; + $this->mUniqPrefix = "\x7fUNIQ" . + dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) ); + $this->mMarkerIndex = 0; + + // Replace elements with placeholders + $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX, + array( &$this, 'replaceCallback' ), $text ); + // ...and markers + $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/', + array( &$this, 'replaceCallback' ), $wrappedtext ); + // ... and tags + $wrappedtext = preg_replace_callback( '/\/s', + array( &$this, 'replaceCallback' ), $wrappedtext ); + // Modify inline Microdata and elements so they say and so + // we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config + $wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '' . + 'test' . $wrappedtext . ''; + + return $wrappedtext; + } + + /** + * @param array $m + * + * @return string + */ + public function replaceCallback( $m ) { + $marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX; + $this->mMarkerIndex++; + $this->mTokens->setPair( $marker, $m[0] ); + return $marker; + } + + /** + * @param string $text + * @return string + */ + public function postprocess( $text ) { + // Revert back to <{link,meta}> + $text = preg_replace( '!]*?)(/{0,1}>)!', '<$1$2$3', $text ); + + // Restore the contents of placeholder tokens + $text = $this->mTokens->replace( $text ); + + return $text; + } + +} + +/** + * Class to interact with HTML tidy + * + * Either the external tidy program or the in-process tidy extension + * will be used depending on availability. Override the default + * $wgTidyInternal setting to disable the internal if it's not working. + * + * @ingroup Parser + */ +class MWTidy { + /** + * Interface with html tidy, used if $wgUseTidy = true. + * If tidy isn't able to correct the markup, the original will be + * returned in all its glory with a warning comment appended. + * + * @param string $text Hideous HTML input + * @return string Corrected HTML output + */ + public static function tidy( $text ) { + global $wgTidyInternal; + + $wrapper = new MWTidyWrapper; + $wrappedtext = $wrapper->getWrapped( $text ); + + $retVal = null; + if ( $wgTidyInternal ) { + $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal ); + } else { + $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal ); + } + + if ( $retVal < 0 ) { + wfDebug( "Possible tidy configuration error!\n" ); + return $text . "\n\n"; + } elseif ( is_null( $correctedtext ) ) { + wfDebug( "Tidy error detected!\n" ); + return $text . "\n\n"; + } + + $correctedtext = $wrapper->postprocess( $correctedtext ); // restore any hidden tokens + + return $correctedtext; + } + + /** + * Check HTML for errors, used if $wgValidateAllHtml = true. + * + * @param string $text + * @param string &$errorStr Return the error string + * @return bool Whether the HTML is valid + */ + public static function checkErrors( $text, &$errorStr = null ) { + global $wgTidyInternal; + + $retval = 0; + if ( $wgTidyInternal ) { + $errorStr = self::execInternalTidy( $text, true, $retval ); + } else { + $errorStr = self::execExternalTidy( $text, true, $retval ); + } + + return ( $retval < 0 && $errorStr == '' ) || $retval == 0; + } + + /** + * Spawn an external HTML tidy process and get corrected markup back from it. + * Also called in OutputHandler.php for full page validation + * + * @param string $text HTML to check + * @param bool $stderr Whether to read result from STDERR rather than STDOUT + * @param int &$retval Exit code (-1 on internal error) + * @return string|null + */ + private static function execExternalTidy( $text, $stderr = false, &$retval = null ) { + global $wgTidyConf, $wgTidyBin, $wgTidyOpts; + wfProfileIn( __METHOD__ ); + + $cleansource = ''; + $opts = ' -utf8'; + + if ( $stderr ) { + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'file', wfGetNull(), 'a' ), + 2 => array( 'pipe', 'w' ) + ); + } else { + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'pipe', 'w' ), + 2 => array( 'file', wfGetNull(), 'a' ) + ); + } + + $readpipe = $stderr ? 2 : 1; + $pipes = array(); + + $process = proc_open( + "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes ); + + //NOTE: At least on linux, the process will be created even if tidy is not installed. + // This means that missing tidy will be treated as a validation failure. + + if ( is_resource( $process ) ) { + // Theoretically, this style of communication could cause a deadlock + // here. If the stdout buffer fills up, then writes to stdin could + // block. This doesn't appear to happen with tidy, because tidy only + // writes to stdout after it's finished reading from stdin. Search + // for tidyParseStdin and tidySaveStdout in console/tidy.c + fwrite( $pipes[0], $text ); + fclose( $pipes[0] ); + while ( !feof( $pipes[$readpipe] ) ) { + $cleansource .= fgets( $pipes[$readpipe], 1024 ); + } + fclose( $pipes[$readpipe] ); + $retval = proc_close( $process ); + } else { + wfWarn( "Unable to start external tidy process" ); + $retval = -1; + } + + if ( !$stderr && $cleansource == '' && $text != '' ) { + // Some kind of error happened, so we couldn't get the corrected text. + // Just give up; we'll use the source text and append a warning. + $cleansource = null; + } + + wfProfileOut( __METHOD__ ); + return $cleansource; + } + + /** + * Use the HTML tidy extension to use the tidy library in-process, + * saving the overhead of spawning a new process. + * + * @param string $text HTML to check + * @param bool $stderr Whether to read result from error status instead of output + * @param int &$retval Exit code (-1 on internal error) + * @return string|null + */ + private static function execInternalTidy( $text, $stderr = false, &$retval = null ) { + global $wgTidyConf, $wgDebugTidy; + wfProfileIn( __METHOD__ ); + + if ( !class_exists( 'tidy' ) ) { + wfWarn( "Unable to load internal tidy class." ); + $retval = -1; + + wfProfileOut( __METHOD__ ); + return null; + } + + $tidy = new tidy; + $tidy->parseString( $text, $wgTidyConf, 'utf8' ); + + if ( $stderr ) { + $retval = $tidy->getStatus(); + + wfProfileOut( __METHOD__ ); + return $tidy->errorBuffer; + } + + $tidy->cleanRepair(); + $retval = $tidy->getStatus(); + if ( $retval == 2 ) { + // 2 is magic number for fatal error + // http://www.php.net/manual/en/function.tidy-get-status.php + $cleansource = null; + } else { + $cleansource = tidy_get_output( $tidy ); + if ( $wgDebugTidy && $retval > 0 ) { + $cleansource .= "', '-->', $tidy->errorBuffer ) . + "\n-->"; + } + } + + wfProfileOut( __METHOD__ ); + return $cleansource; + } +} diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 1f14223d..84bb2243 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -120,62 +120,61 @@ class Parser { const TOC_END = ''; # Persistent: - var $mTagHooks = array(); - var $mTransparentTagHooks = array(); - var $mFunctionHooks = array(); - var $mFunctionSynonyms = array( 0 => array(), 1 => array() ); - var $mFunctionTagHooks = array(); - var $mStripList = array(); - var $mDefaultStripList = array(); - var $mVarCache = array(); - var $mImageParams = array(); - var $mImageParamsMagicArray = array(); - var $mMarkerIndex = 0; - var $mFirstCall = true; + public $mTagHooks = array(); + public $mTransparentTagHooks = array(); + public $mFunctionHooks = array(); + public $mFunctionSynonyms = array( 0 => array(), 1 => array() ); + public $mFunctionTagHooks = array(); + public $mStripList = array(); + public $mDefaultStripList = array(); + public $mVarCache = array(); + public $mImageParams = array(); + public $mImageParamsMagicArray = array(); + public $mMarkerIndex = 0; + public $mFirstCall = true; # Initialised by initialiseVariables() /** * @var MagicWordArray */ - var $mVariables; + public $mVariables; /** * @var MagicWordArray */ - var $mSubstWords; - var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor + public $mSubstWords; + public $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor # Cleared with clearState(): /** * @var ParserOutput */ - var $mOutput; - var $mAutonumber, $mDTopen; + public $mOutput; + public $mAutonumber, $mDTopen; /** * @var StripState */ - var $mStripState; + public $mStripState; - var $mIncludeCount, $mArgStack, $mLastSection, $mInPre; + public $mIncludeCount, $mArgStack, $mLastSection, $mInPre; /** * @var LinkHolderArray */ - var $mLinkHolders; + public $mLinkHolders; - var $mLinkID; - 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 - var $mShowToc, $mForceTocPosition; + public $mLinkID; + public $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth; + public $mDefaultSort; + public $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores; + public $mExpensiveFunctionCount; # number of expensive parser function calls + public $mShowToc, $mForceTocPosition; /** * @var User */ - var $mUser; # User object; only used when doing pre-save transform + public $mUser; # User object; only used when doing pre-save transform # Temporary # These are variables reset at least once per parse regardless of $clearState @@ -183,38 +182,42 @@ class Parser { /** * @var ParserOptions */ - var $mOptions; + public $mOptions; /** * @var Title */ - var $mTitle; # Title context, used for self-link rendering and similar things - var $mOutputType; # Output type, one of the OT_xxx constants - var $ot; # Shortcut alias, see setOutputType() - var $mRevisionObject; # The revision object of the specified revision ID - var $mRevisionId; # ID to display in {{REVISIONID}} tags - var $mRevisionTimestamp; # The timestamp of the specified revision ID - var $mRevisionUser; # User to display in {{REVISIONUSER}} tag - var $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable - var $mRevIdForTs; # The revision ID which was used to fetch the timestamp - var $mInputSize = false; # For {{PAGESIZE}} on current page. + public $mTitle; # Title context, used for self-link rendering and similar things + public $mOutputType; # Output type, one of the OT_xxx constants + public $ot; # Shortcut alias, see setOutputType() + public $mRevisionObject; # The revision object of the specified revision ID + public $mRevisionId; # ID to display in {{REVISIONID}} tags + public $mRevisionTimestamp; # The timestamp of the specified revision ID + public $mRevisionUser; # User to display in {{REVISIONUSER}} tag + public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable + public $mRevIdForTs; # The revision ID which was used to fetch the timestamp + public $mInputSize = false; # For {{PAGESIZE}} on current page. /** * @var string */ - var $mUniqPrefix; + public $mUniqPrefix; /** - * @var Array with the language name of each language link (i.e. the + * @var array Array with the language name of each language link (i.e. the * interwiki prefix) in the key, value arbitrary. Used to avoid sending * duplicate language links to the ParserOutput. */ - var $mLangLinkLanguages; + public $mLangLinkLanguages; /** - * Constructor - * - * @param $conf array + * @var bool Recursive call protection. + * This variable should be treated as if it were private. + */ + public $mInParse = false; + + /** + * @param array $conf */ public function __construct( $conf = array() ) { $this->mConf = $conf; @@ -241,7 +244,7 @@ class Parser { /** * Reduce memory usage to reduce the impact of circular references */ - function __destruct() { + public function __destruct() { if ( isset( $this->mLinkHolders ) ) { unset( $this->mLinkHolders ); } @@ -253,14 +256,15 @@ class Parser { /** * Allow extensions to clean up when the parser is cloned */ - function __clone() { + public function __clone() { + $this->mInParse = false; wfRunHooks( 'ParserCloned', array( $this ) ); } /** * Do various kinds of initialisation on the first call of the parser */ - function firstCallInit() { + public function firstCallInit() { if ( !$this->mFirstCall ) { return; } @@ -281,7 +285,7 @@ class Parser { * * @private */ - function clearState() { + public function clearState() { wfProfileIn( __METHOD__ ); if ( $this->mFirstCall ) { $this->firstCallInit(); @@ -316,7 +320,7 @@ class Parser { $this->mStripState = new StripState( $this->mUniqPrefix ); # Clear these on every parse, bug 4549 - $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array(); + $this->mTplRedirCache = $this->mTplDomCache = array(); $this->mShowToc = true; $this->mForceTocPosition = false; @@ -345,15 +349,17 @@ class Parser { * Convert wikitext to HTML * Do not call this function recursively. * - * @param string $text text we want to parse - * @param $title Title object - * @param $options ParserOptions - * @param $linestart boolean - * @param $clearState boolean - * @param int $revid number to pass in {{REVISIONID}} - * @return ParserOutput a ParserOutput - */ - public function parse( $text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null ) { + * @param string $text Text we want to parse + * @param Title $title + * @param ParserOptions $options + * @param bool $linestart + * @param bool $clearState + * @param int $revid Number to pass in {{REVISIONID}} + * @return ParserOutput A ParserOutput + */ + public function parse( $text, Title $title, ParserOptions $options, + $linestart = true, $clearState = true, $revid = null + ) { /** * First pass--just handle sections, pass the rest off * to internalParse() which does all the real work. @@ -364,6 +370,10 @@ class Parser { wfProfileIn( __METHOD__ ); wfProfileIn( $fname ); + if ( $clearState ) { + $magicScopeVariable = $this->lock(); + } + $this->startParse( $title, $options, self::OT_HTML, $clearState ); $this->mInputSize = strlen( $text ); @@ -420,8 +430,8 @@ class Parser { * d) it is an interface message (which is in the user language) */ if ( !( $options->getDisableContentConversion() - || isset( $this->mDoubleUnderscores['nocontentconvert'] ) ) ) - { + || isset( $this->mDoubleUnderscores['nocontentconvert'] ) ) + ) { if ( !$this->mOptions->getInterfaceMessage() ) { # The position of the convert() call should not be changed. it # assumes that the links are all replaced and the only thing left @@ -438,10 +448,10 @@ class Parser { * automatic link conversion. */ if ( !( $options->getDisableTitleConversion() - || isset( $this->mDoubleUnderscores['nocontentconvert'] ) - || isset( $this->mDoubleUnderscores['notitleconvert'] ) - || $this->mOutput->getDisplayTitle() !== false ) ) - { + || isset( $this->mDoubleUnderscores['nocontentconvert'] ) + || isset( $this->mDoubleUnderscores['notitleconvert'] ) + || $this->mOutput->getDisplayTitle() !== false ) + ) { $convruletitle = $this->getConverterLanguage()->getConvRuleTitle(); if ( $convruletitle ) { $this->mOutput->setTitleText( $convruletitle ); @@ -541,7 +551,7 @@ class Parser { } foreach ( $this->mOutput->getLimitReportData() as $key => $value ) { if ( wfRunHooks( 'ParserLimitReportFormat', - array( $key, $value, &$limitReport, false, false ) + array( $key, &$value, &$limitReport, false, false ) ) ) { $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false ); $valueMsg = wfMessage( array( "$key-value-text", "$key-value" ) ) @@ -590,12 +600,12 @@ class Parser { * * If $frame is not provided, then template variables (e.g., {{{1}}}) within $text are not expanded * - * @param string $text text extension wants to have parsed - * @param $frame PPFrame: The frame to use for expanding any template variables + * @param string $text Text extension wants to have parsed + * @param bool|PPFrame $frame The frame to use for expanding any template variables * * @return string */ - function recursiveTagParse( $text, $frame = false ) { + public function recursiveTagParse( $text, $frame = false ) { wfProfileIn( __METHOD__ ); wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); @@ -607,17 +617,24 @@ class Parser { /** * Expand templates and variables in the text, producing valid, static wikitext. * Also removes comments. + * Do not call this function recursively. + * @param string $text + * @param Title $title + * @param ParserOptions $options + * @param int|null $revid + * @param bool|PPFrame $frame * @return mixed|string */ - function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null ) { + public function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null, $frame = false ) { wfProfileIn( __METHOD__ ); + $magicScopeVariable = $this->lock(); $this->startParse( $title, $options, self::OT_PREPROCESS, true ); if ( $revid !== null ) { $this->mRevisionId = $revid; } wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->replaceVariables( $text ); + $text = $this->replaceVariables( $text, $frame ); $text = $this->mStripState->unstripBoth( $text ); wfProfileOut( __METHOD__ ); return $text; @@ -627,9 +644,9 @@ class Parser { * Recursive parser entry point that can be called from an extension tag * hook. * - * @param string $text text to be expanded - * @param $frame PPFrame: The frame to use for expanding any template variables - * @return String + * @param string $text Text to be expanded + * @param bool|PPFrame $frame The frame to use for expanding any template variables + * @return string * @since 1.19 */ public function recursivePreprocess( $text, $frame = false ) { @@ -647,13 +664,18 @@ class Parser { * transclusion, comments, templates, arguments, tags hooks and parser * functions are untouched. * - * @param $text String - * @param $title Title - * @param $options ParserOptions - * @return String + * @param string $text + * @param Title $title + * @param ParserOptions $options + * @param array $params + * @return string */ - public function getPreloadText( $text, Title $title, ParserOptions $options ) { + public function getPreloadText( $text, Title $title, ParserOptions $options, $params = array() ) { + $msg = new RawMessage( $text ); + $text = $msg->params( $params )->plain(); + # Parser (re)initialisation + $magicScopeVariable = $this->lock(); $this->startParse( $title, $options, self::OT_PLAIN, true ); $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES; @@ -676,16 +698,16 @@ class Parser { * Set the current user. * Should only be used when doing pre-save transform. * - * @param $user Mixed: User object or null (to reset) + * @param User|null $user User object or null (to reset) */ - function setUser( $user ) { + public function setUser( $user ) { $this->mUser = $user; } /** * Accessor for mUniqPrefix. * - * @return String + * @return string */ public function uniqPrefix() { if ( !isset( $this->mUniqPrefix ) ) { @@ -703,14 +725,14 @@ class Parser { /** * Set the context title * - * @param $t Title + * @param Title $t */ - function setTitle( $t ) { - if ( !$t || $t instanceof FakeTitle ) { + public function setTitle( $t ) { + if ( !$t ) { $t = Title::newFromText( 'NO TITLE' ); } - if ( strval( $t->getFragment() ) !== '' ) { + if ( $t->hasFragment() ) { # Strip the fragment to avoid various odd effects $this->mTitle = clone $t; $this->mTitle->setFragment( '' ); @@ -722,28 +744,28 @@ class Parser { /** * Accessor for the Title object * - * @return Title object + * @return Title */ - function getTitle() { + public function getTitle() { return $this->mTitle; } /** * Accessor/mutator for the Title object * - * @param $x Title object or null to just get the current one - * @return Title object + * @param Title $x Title object or null to just get the current one + * @return Title */ - function Title( $x = null ) { + public function Title( $x = null ) { return wfSetVar( $this->mTitle, $x ); } /** * Set the output type * - * @param $ot Integer: new value + * @param int $ot New value */ - function setOutputType( $ot ) { + public function setOutputType( $ot ) { $this->mOutputType = $ot; # Shortcut alias $this->ot = array( @@ -758,51 +780,51 @@ class Parser { * Accessor/mutator for the output type * * @param int|null $x New value or null to just get the current one - * @return Integer + * @return int */ - function OutputType( $x = null ) { + public function OutputType( $x = null ) { return wfSetVar( $this->mOutputType, $x ); } /** * Get the ParserOutput object * - * @return ParserOutput object + * @return ParserOutput */ - function getOutput() { + public function getOutput() { return $this->mOutput; } /** * Get the ParserOptions object * - * @return ParserOptions object + * @return ParserOptions */ - function getOptions() { + public function getOptions() { return $this->mOptions; } /** * Accessor/mutator for the ParserOptions object * - * @param $x ParserOptions New value or null to just get the current one + * @param ParserOptions $x New value or null to just get the current one * @return ParserOptions Current ParserOptions object */ - function Options( $x = null ) { + public function Options( $x = null ) { return wfSetVar( $this->mOptions, $x ); } /** * @return int */ - function nextLinkID() { + public function nextLinkID() { return $this->mLinkID++; } /** - * @param $id int + * @param int $id */ - function setLinkID( $id ) { + public function setLinkID( $id ) { $this->mLinkID = $id; } @@ -810,7 +832,7 @@ class Parser { * Get a language object for use in parser functions such as {{FORMATNUM:}} * @return Language */ - function getFunctionLang() { + public function getFunctionLang() { return $this->getTargetLanguage(); } @@ -821,7 +843,7 @@ class Parser { * @since 1.19 * * @throws MWException - * @return Language|null + * @return Language */ public function getTargetLanguage() { $target = $this->mOptions->getTargetLanguage(); @@ -839,8 +861,9 @@ class Parser { /** * Get the language object for language conversion + * @return Language|null */ - function getConverterLanguage() { + public function getConverterLanguage() { return $this->getTargetLanguage(); } @@ -848,9 +871,9 @@ class Parser { * Get a User object either from $this->mUser, if set, or from the * ParserOptions object otherwise * - * @return User object + * @return User */ - function getUser() { + public function getUser() { if ( !is_null( $this->mUser ) ) { return $this->mUser; } @@ -860,9 +883,9 @@ class Parser { /** * Get a preprocessor object * - * @return Preprocessor instance + * @return Preprocessor */ - function getPreprocessor() { + public function getPreprocessor() { if ( !isset( $this->mPreprocessor ) ) { $class = $this->mPreprocessorClass; $this->mPreprocessor = new $class( $this ); @@ -884,11 +907,11 @@ class Parser { * 'tag content' ) ) * @endcode * - * @param array $elements list of element names. Comments are always extracted. + * @param array $elements List of element names. Comments are always extracted. * @param string $text Source text string. * @param array $matches Out parameter, Array: extracted tags - * @param $uniq_prefix string - * @return String: stripped text + * @param string $uniq_prefix + * @return string Stripped text */ public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) { static $n = 1; @@ -957,7 +980,7 @@ class Parser { * * @return array */ - function getStripList() { + public function getStripList() { return $this->mStripList; } @@ -966,11 +989,11 @@ class Parser { * Returns the unique tag which must be inserted into the stripped text * The tag will be replaced with the original text in unstrip() * - * @param $text string + * @param string $text * * @return string */ - function insertStripItem( $text ) { + public function insertStripItem( $text ) { $rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX; $this->mMarkerIndex++; $this->mStripState->addGeneral( $rnd, $text ); @@ -981,9 +1004,10 @@ class Parser { * parse the wiki syntax used to render tables * * @private + * @param string $text * @return string */ - function doTableStuff( $text ) { + public function doTableStuff( $text ) { wfProfileIn( __METHOD__ ); $lines = StringUtils::explode( "\n", $text ); @@ -1068,7 +1092,10 @@ class Parser { array_push( $tr_history, false ); array_push( $td_history, false ); array_push( $last_tag_history, '' ); - } elseif ( $first_character === '|' || $first_character === '!' || substr( $line, 0, 2 ) === '|+' ) { + } elseif ( $first_character === '|' + || $first_character === '!' + || substr( $line, 0, 2 ) === '|+' + ) { # This might be cell elements, td, th or captions if ( substr( $line, 0, 2 ) === '|+' ) { $first_character = '+'; @@ -1179,13 +1206,13 @@ class Parser { * * @private * - * @param $text string - * @param $isMain bool - * @param $frame bool + * @param string $text + * @param bool $isMain + * @param bool $frame * * @return string */ - function internalParse( $text, $isMain = true, $frame = false ) { + public function internalParse( $text, $isMain = true, $frame = false ) { wfProfileIn( __METHOD__ ); $origText = $text; @@ -1213,7 +1240,12 @@ class Parser { } wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) ); - $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) ); + $text = Sanitizer::removeHTMLtags( + $text, + array( &$this, 'attributeStripCallback' ), + false, + array_keys( $this->mTransparentTagHooks ) + ); wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) ); # Tables need to come after variable replacement for things to work @@ -1249,11 +1281,11 @@ class Parser { * DML * @private * - * @param $text string + * @param string $text * * @return string */ - function doMagicLinks( $text ) { + public function doMagicLinks( $text ) { wfProfileIn( __METHOD__ ); $prots = wfUrlProtocolsWithoutProtRel(); $urlChar = self::EXT_LINK_URL_CLASS; @@ -1275,10 +1307,10 @@ class Parser { /** * @throws MWException - * @param $m array + * @param array $m * @return HTML|string */ - function magicLinkCallback( $m ) { + public function magicLinkCallback( $m ) { if ( isset( $m[1] ) && $m[1] !== '' ) { # Skip anchor return $m[0]; @@ -1293,19 +1325,19 @@ class Parser { if ( substr( $m[0], 0, 3 ) === 'RFC' ) { $keyword = 'RFC'; $urlmsg = 'rfcurl'; - $CssClass = 'mw-magiclink-rfc'; + $cssClass = 'mw-magiclink-rfc'; $id = $m[4]; } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) { $keyword = 'PMID'; $urlmsg = 'pubmedurl'; - $CssClass = 'mw-magiclink-pmid'; + $cssClass = 'mw-magiclink-pmid'; $id = $m[4]; } else { throw new MWException( __METHOD__ . ': unrecognised match type "' . substr( $m[0], 0, 20 ) . '"' ); } $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text(); - return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $CssClass ); + return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass ); } elseif ( isset( $m[5] ) && $m[5] !== '' ) { # ISBN $isbn = $m[5]; @@ -1326,12 +1358,12 @@ class Parser { /** * Make a free external link, given a user-supplied URL * - * @param $url string + * @param string $url * * @return string HTML * @private */ - function makeFreeExternalLink( $url ) { + public function makeFreeExternalLink( $url ) { wfProfileIn( __METHOD__ ); $trail = ''; @@ -1370,7 +1402,7 @@ class Parser { $this->getExternalLinkAttribs( $url ) ); # Register it in the output object... # Replace unnecessary URL escape codes with their equivalent characters - $pasteurized = self::replaceUnusualEscapes( $url ); + $pasteurized = self::normalizeLinkUrl( $url ); $this->mOutput->addExternalLink( $pasteurized ); } wfProfileOut( __METHOD__ ); @@ -1382,11 +1414,11 @@ class Parser { * * @private * - * @param $text string + * @param string $text * * @return string */ - function doHeadings( $text ) { + public function doHeadings( $text ) { wfProfileIn( __METHOD__ ); for ( $i = 6; $i >= 1; --$i ) { $h = str_repeat( '=', $i ); @@ -1400,11 +1432,11 @@ class Parser { * Replace single quotes with HTML markup * @private * - * @param $text string + * @param string $text * - * @return string the altered text + * @return string The altered text */ - function doAllQuotes( $text ) { + public function doAllQuotes( $text ) { wfProfileIn( __METHOD__ ); $outtext = ''; $lines = StringUtils::explode( "\n", $text ); @@ -1419,7 +1451,7 @@ class Parser { /** * Helper function for doAllQuotes() * - * @param $text string + * @param string $text * * @return string */ @@ -1608,18 +1640,19 @@ class Parser { * * @private * - * @param $text string + * @param string $text * * @throws MWException * @return string */ - function replaceExternalLinks( $text ) { + public function replaceExternalLinks( $text ) { wfProfileIn( __METHOD__ ); $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE ); if ( $bits === false ) { wfProfileOut( __METHOD__ ); - throw new MWException( "PCRE needs to be compiled with --enable-unicode-properties in order for MediaWiki to function" ); + throw new MWException( "PCRE needs to be compiled with " + . "--enable-unicode-properties in order for MediaWiki to function" ); } $s = array_shift( $bits ); @@ -1677,43 +1710,45 @@ class Parser { # Register link in the output object. # Replace unnecessary URL escape codes with the referenced character # This prevents spammers from hiding links from the filters - $pasteurized = self::replaceUnusualEscapes( $url ); + $pasteurized = self::normalizeLinkUrl( $url ); $this->mOutput->addExternalLink( $pasteurized ); } wfProfileOut( __METHOD__ ); return $s; } + /** * Get the rel attribute for a particular external link. * * @since 1.21 - * @param string|bool $url optional URL, to extract the domain from for rel => + * @param string|bool $url Optional URL, to extract the domain from for rel => * nofollow if appropriate - * @param $title Title optional Title, for wgNoFollowNsExceptions lookups - * @return string|null rel attribute for $url + * @param Title $title Optional Title, for wgNoFollowNsExceptions lookups + * @return string|null Rel attribute for $url */ public static function getExternalLinkRel( $url = false, $title = null ) { global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions; $ns = $title ? $title->getNamespace() : false; - if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) && - !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions ) ) - { + if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) + && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions ) + ) { return 'nofollow'; } return null; } + /** * Get an associative array of additional HTML attributes appropriate for a * particular external link. This currently may include rel => nofollow * (depending on configuration, namespace, and the URL's domain) and/or a * target attribute (depending on configuration). * - * @param string|bool $url optional URL, to extract the domain from for rel => + * @param string|bool $url Optional URL, to extract the domain from for rel => * nofollow if appropriate - * @return Array associative array of HTML attributes + * @return array Associative array of HTML attributes */ - function getExternalLinkAttribs( $url = false ) { + public function getExternalLinkAttribs( $url = false ) { $attribs = array(); $attribs['rel'] = self::getExternalLinkRel( $url, $this->mTitle ); @@ -1724,52 +1759,86 @@ class Parser { } /** - * Replace unusual URL escape codes with their equivalent characters - * - * @param $url String - * @return String + * Replace unusual escape codes in a URL with their equivalent characters * - * @todo This can merge genuinely required bits in the path or query string, - * breaking legit URLs. A proper fix would treat the various parts of - * the URL differently; as a workaround, just use the output for - * statistical records, not for actual linking/output. + * @deprecated since 1.24, use normalizeLinkUrl + * @param string $url + * @return string */ - static function replaceUnusualEscapes( $url ) { - return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', - array( __CLASS__, 'replaceUnusualEscapesCallback' ), $url ); + public static function replaceUnusualEscapes( $url ) { + wfDeprecated( __METHOD__, '1.24' ); + return self::normalizeLinkUrl( $url ); } /** - * Callback function used in replaceUnusualEscapes(). - * Replaces unusual URL escape codes with their equivalent character + * Replace unusual escape codes in a URL with their equivalent characters * - * @param $matches array + * This generally follows the syntax defined in RFC 3986, with special + * consideration for HTTP query strings. * + * @param string $url * @return string */ - private static function replaceUnusualEscapesCallback( $matches ) { - $char = urldecode( $matches[0] ); - $ord = ord( $char ); - # Is it an unsafe or HTTP reserved character according to RFC 1738? - if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) { - # No, shouldn't be escaped - return $char; - } else { - # Yes, leave it escaped - return $matches[0]; + public static function normalizeLinkUrl( $url ) { + # First, make sure unsafe characters are encoded + $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/', + function ( $m ) { + return rawurlencode( $m[0] ); + }, + $url + ); + + $ret = ''; + $end = strlen( $url ); + + # Fragment part - 'fragment' + $start = strpos( $url, '#' ); + if ( $start !== false && $start < $end ) { + $ret = self::normalizeUrlComponent( + substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret; + $end = $start; } + + # Query part - 'query' minus &=+; + $start = strpos( $url, '?' ); + if ( $start !== false && $start < $end ) { + $ret = self::normalizeUrlComponent( + substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret; + $end = $start; + } + + # Scheme and path part - 'pchar' + # (we assume no userinfo or encoded colons in the host) + $ret = self::normalizeUrlComponent( + substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret; + + return $ret; + } + + private static function normalizeUrlComponent( $component, $unsafe ) { + $callback = function ( $matches ) use ( $unsafe ) { + $char = urldecode( $matches[0] ); + $ord = ord( $char ); + if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) { + # Unescape it + return $char; + } else { + # Leave it escaped, but use uppercase for a-f + return strtoupper( $matches[0] ); + } + }; + return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component ); } /** * make an image if it's allowed, either through the global * option, through the exception, or through the on-wiki whitelist - * @private * - * $param $url string + * @param string $url * * @return string */ - function maybeMakeExternalImage( $url ) { + private function maybeMakeExternalImage( $url ) { $imagesfrom = $this->mOptions->getAllowExternalImagesFrom(); $imagesexception = !empty( $imagesfrom ); $text = false; @@ -1787,16 +1856,23 @@ class Parser { } else { $imagematch = false; } + if ( $this->mOptions->getAllowExternalImages() - || ( $imagesexception && $imagematch ) ) { + || ( $imagesexception && $imagematch ) + ) { if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) { # Image found $text = Linker::makeExternalImage( $url ); } } if ( !$text && $this->mOptions->getEnableImageWhitelist() - && preg_match( self::EXT_IMAGE_REGEX, $url ) ) { - $whitelist = explode( "\n", wfMessage( 'external_image_whitelist' )->inContentLanguage()->text() ); + && preg_match( self::EXT_IMAGE_REGEX, $url ) + ) { + $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 === '' ) { @@ -1815,26 +1891,27 @@ class Parser { /** * Process [[ ]] wikilinks * - * @param $s string + * @param string $s * - * @return String: processed text + * @return string Processed text * * @private */ - function replaceInternalLinks( $s ) { + public function replaceInternalLinks( $s ) { $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) ); return $s; } /** * Process [[ ]] wikilinks (RIL) - * @param $s + * @param string $s * @throws MWException * @return LinkHolderArray * * @private */ - function replaceInternalLinks2( &$s ) { + public function replaceInternalLinks2( &$s ) { + global $wgExtraInterlanguageLinkPrefixes; wfProfileIn( __METHOD__ ); wfProfileIn( __METHOD__ . '-setup' ); @@ -1863,7 +1940,9 @@ 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 = wfMessage( 'linkprefix' )->inContentLanguage()->text(); + global $wgContLang; + $charset = $wgContLang->linkPrefixCharset(); + $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu"; } if ( is_null( $this->mTitle ) ) { @@ -1887,8 +1966,11 @@ class Parser { $useSubpages = $this->areSubpagesAllowed(); wfProfileOut( __METHOD__ . '-setup' ); + // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect # Loop for each link for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) { + // @codingStandardsIgnoreStart + # Check for excessive memory usage if ( $holders->isBig() ) { # Too big @@ -1926,11 +2008,10 @@ class Parser { # Still some problems for cases where the ] is meant to be outside punctuation, # and no image is in sight. See bug 2095. # - if ( $text !== '' && - substr( $m[3], 0, 1 ) === ']' && - strpos( $text, '[' ) !== false - ) - { + if ( $text !== '' + && substr( $m[3], 0, 1 ) === ']' + && strpos( $text, '[' ) !== false + ) { $text .= ']'; # so that replaceExternalLinks($text) works later $m[3] = substr( $m[3], 1 ); } @@ -1940,7 +2021,8 @@ class Parser { $m[1] = str_replace( array( '<', '>' ), array( '<', '>' ), rawurldecode( $m[1] ) ); } $trail = $m[3]; - } elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption + } elseif ( preg_match( $e1_img, $line, $m ) ) { + # Invalid, but might be an image with a link in its caption $might_be_img = true; $text = $m[2]; if ( strpos( $m[1], '%' ) !== false ) { @@ -1955,10 +2037,12 @@ class Parser { wfProfileOut( __METHOD__ . "-e1" ); wfProfileIn( __METHOD__ . "-misc" ); + $origLink = $m[1]; + # Don't allow internal links to pages containing # PROTO: where PROTO is a valid URL protocol; these # should be external links. - if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $m[1] ) ) { + if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) { $s .= $prefix . '[[' . $line; wfProfileOut( __METHOD__ . "-misc" ); continue; @@ -1966,12 +2050,12 @@ class Parser { # Make subpage if necessary if ( $useSubpages ) { - $link = $this->maybeDoSubpageLink( $m[1], $text ); + $link = $this->maybeDoSubpageLink( $origLink, $text ); } else { - $link = $m[1]; + $link = $origLink; } - $noforce = ( substr( $m[1], 0, 1 ) !== ':' ); + $noforce = ( substr( $origLink, 0, 1 ) !== ':' ); if ( !$noforce ) { # Strip off leading ':' $link = substr( $link, 1 ); @@ -1987,7 +2071,7 @@ class Parser { } $ns = $nt->getNamespace(); - $iw = $nt->getInterWiki(); + $iw = $nt->getInterwiki(); wfProfileOut( __METHOD__ . "-title" ); if ( $might_be_img ) { # if this is actually an invalid link @@ -2047,12 +2131,15 @@ class Parser { } # Link not escaped by : , create the various objects - if ( $noforce ) { + if ( $noforce && !$nt->wasLocalInterwiki() ) { # Interwikis wfProfileIn( __METHOD__ . "-interwiki" ); - if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) { - // XXX: the above check prevents links to sites with identifiers that are not language codes - + if ( + $iw && $this->mOptions->getInterwikiMagic() && $nottalk && ( + Language::fetchLanguageName( $iw, null, 'mw' ) || + in_array( $iw, $wgExtraInterlanguageLinkPrefixes ) + ) + ) { # Bug 24502: filter duplicates if ( !isset( $this->mLangLinkLanguages[$iw] ) ) { $this->mLangLinkLanguages[$iw] = true; @@ -2108,7 +2195,6 @@ class Parser { /** * Strip the whitespace Category links produce, see bug 87 - * @todo We might want to use trim($tmp, "\n") here. */ $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail; @@ -2120,7 +2206,7 @@ class Parser { # Self-link checking. For some languages, variants of the title are checked in # LinkHolderArray::doVariants() to allow batching the existence checks necessary # for linking to a different variant. - if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && $nt->getFragment() === '' ) { + if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) { $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail ); continue; } @@ -2169,14 +2255,14 @@ class Parser { * breaking URLs in the following text without breaking trails on the * wiki links, it's been made into a horrible function. * - * @param $nt Title - * @param $text String - * @param array $query or String - * @param $trail String - * @param $prefix String - * @return String: HTML-wikitext mix oh yuck + * @param Title $nt + * @param string $text + * @param array|string $query + * @param string $trail + * @param string $prefix + * @return string HTML-wikitext mix oh yuck */ - function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) { + public function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) { list( $inside, $trail ) = Linker::splitTrail( $trail ); if ( is_string( $query ) ) { @@ -2198,19 +2284,19 @@ class Parser { * Not needed quite as much as it used to be since free links are a bit * more sensible these days. But bracketed links are still an issue. * - * @param string $text more-or-less HTML - * @return String: less-or-more HTML with NOPARSE bits + * @param string $text More-or-less HTML + * @return string Less-or-more HTML with NOPARSE bits */ - function armorLinks( $text ) { + public function armorLinks( $text ) { return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/', "{$this->mUniqPrefix}NOPARSE$1", $text ); } /** * Return true if subpage links should be expanded on this page. - * @return Boolean + * @return bool */ - function areSubpagesAllowed() { + public function areSubpagesAllowed() { # Some namespaces don't allow subpages return MWNamespace::hasSubpages( $this->mTitle->getNamespace() ); } @@ -2218,12 +2304,12 @@ class Parser { /** * Handle link to subpage if necessary * - * @param string $target the source of the link - * @param &$text String: the link text, modified as necessary - * @return string the full name of the link + * @param string $target The source of the link + * @param string &$text The link text, modified as necessary + * @return string The full name of the link * @private */ - function maybeDoSubpageLink( $target, &$text ) { + public function maybeDoSubpageLink( $target, &$text ) { return Linker::normalizeSubpageLink( $this->mTitle, $target, $text ); } @@ -2233,7 +2319,7 @@ class Parser { * * @return string */ - function closeParagraph() { + public function closeParagraph() { $result = ''; if ( $this->mLastSection != '' ) { $result = 'mLastSection . ">\n"; @@ -2248,12 +2334,12 @@ class Parser { * of both arguments, starting at the beginning of both. * @private * - * @param $st1 string - * @param $st2 string + * @param string $st1 + * @param string $st2 * * @return int */ - function getCommon( $st1, $st2 ) { + public function getCommon( $st1, $st2 ) { $fl = strlen( $st1 ); $shorter = strlen( $st2 ); if ( $fl < $shorter ) { @@ -2273,21 +2359,21 @@ class Parser { * element appropriate to the prefix character passed into them. * @private * - * @param $char string + * @param string $char * * @return string */ - function openList( $char ) { + public function openList( $char ) { $result = $this->closeParagraph(); if ( '*' === $char ) { - $result .= "
    \n
  • "; + $result .= "
    • "; } elseif ( '#' === $char ) { - $result .= "
        \n
      1. "; + $result .= "
        1. "; } elseif ( ':' === $char ) { - $result .= "
          \n
          "; + $result .= "
          "; } elseif ( ';' === $char ) { - $result .= "
          \n
          "; + $result .= "
          "; $this->mDTopen = true; } else { $result = ''; @@ -2298,12 +2384,12 @@ class Parser { /** * TODO: document - * @param $char String + * @param string $char * @private * * @return string */ - function nextItem( $char ) { + public function nextItem( $char ) { if ( '*' === $char || '#' === $char ) { return "
        2. \n
        3. "; } elseif ( ':' === $char || ';' === $char ) { @@ -2323,40 +2409,40 @@ class Parser { } /** - * TODO: document - * @param $char String + * @todo Document + * @param string $char * @private * * @return string */ - function closeList( $char ) { + public function closeList( $char ) { if ( '*' === $char ) { - $text = "
        4. \n
    "; + $text = "
"; } elseif ( '#' === $char ) { - $text = "\n"; + $text = ""; } elseif ( ':' === $char ) { if ( $this->mDTopen ) { $this->mDTopen = false; - $text = "\n"; + $text = ""; } else { - $text = "\n"; + $text = ""; } } else { return ''; } - return $text . "\n"; + return $text; } /**#@-*/ /** * Make lists from lines starting with ':', '*', '#', etc. (DBL) * - * @param $text String - * @param $linestart Boolean: whether or not this is at the start of a line. + * @param string $text + * @param bool $linestart Whether or not this is at the start of a line. * @private - * @return string the lists rendered as HTML + * @return string The lists rendered as HTML */ - function doBlockLevels( $text, $linestart ) { + public function doBlockLevels( $text, $linestart ) { wfProfileIn( __METHOD__ ); # Parsing through the text line by line. The main thing @@ -2442,6 +2528,9 @@ class Parser { } # Open prefixes where appropriate. + if ( $lastPrefix && $prefixLength > $commonPrefixLength ) { + $output .= "\n"; + } while ( $prefixLength > $commonPrefixLength ) { $char = substr( $prefix, $commonPrefixLength, 1 ); $output .= $this->openList( $char ); @@ -2455,6 +2544,9 @@ class Parser { } ++$commonPrefixLength; } + if ( !$prefixLength && $lastPrefix ) { + $output .= "\n"; + } $lastPrefix = $prefix2; } @@ -2463,13 +2555,22 @@ class Parser { wfProfileIn( __METHOD__ . "-paragraph" ); # No prefix (not in list)--go to paragraph mode # XXX: use a stack for nestable elements like span, table and div - $openmatch = preg_match( '/(?:mUniqPrefix . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', $t ); + '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' + . 'mUniqPrefix + . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', + $t + ); + if ( $openmatch or $closematch ) { $paragraphStack = false; - # TODO bug 5718: paragraph closed + # @todo bug 5718: paragraph closed $output .= $this->closeParagraph(); if ( $preOpenMatch and !$preCloseMatch ) { $this->mInPre = true; @@ -2481,7 +2582,10 @@ class Parser { } $inBlockElem = !$closematch; } elseif ( !$inBlockElem && !$this->mInPre ) { - if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) and !$inBlockquote ) { + if ( ' ' == substr( $t, 0, 1 ) + && ( $this->mLastSection === 'pre' || trim( $t ) != '' ) + && !$inBlockquote + ) { # pre if ( $this->mLastSection !== 'pre' ) { $paragraphStack = false; @@ -2524,12 +2628,18 @@ class Parser { $this->mInPre = false; } if ( $paragraphStack === false ) { - $output .= $t . "\n"; + $output .= $t; + if ( $prefixLength === 0 ) { + $output .= "\n"; + } } } while ( $prefixLength ) { $output .= $this->closeList( $prefix2[$prefixLength - 1] ); --$prefixLength; + if ( !$prefixLength ) { + $output .= "\n"; + } } if ( $this->mLastSection != '' ) { $output .= 'mLastSection . '>'; @@ -2544,13 +2654,13 @@ class Parser { * Split up a string on ':', ignoring any occurrences inside tags * to prevent illegal overlapping. * - * @param string $str the string to split - * @param &$before String set to everything before the ':' - * @param &$after String set to everything after the ':' + * @param string $str The string to split + * @param string &$before Set to everything before the ':' + * @param string &$after Set to everything after the ':' * @throws MWException - * @return String the position of the ':', or false if none found + * @return string The position of the ':', or false if none found */ - function findColonNoLinks( $str, &$before, &$after ) { + public function findColonNoLinks( $str, &$before, &$after ) { wfProfileIn( __METHOD__ ); $pos = strpos( $str, ':' ); @@ -2712,14 +2822,14 @@ class Parser { * * @private * - * @param $index integer - * @param bool|\PPFrame $frame + * @param int $index + * @param bool|PPFrame $frame * * @throws MWException * @return string */ - function getVariableValue( $index, $frame = false ) { - global $wgContLang, $wgSitename, $wgServer; + public function getVariableValue( $index, $frame = false ) { + global $wgContLang, $wgSitename, $wgServer, $wgServerName; global $wgArticlePath, $wgScriptPath, $wgStylePath; if ( is_null( $this->mTitle ) ) { @@ -2747,6 +2857,9 @@ class Parser { $pageLang = $this->getFunctionLang(); switch ( $index ) { + case '!': + $value = '|'; + break; case 'currentmonth': $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) ); break; @@ -2811,13 +2924,21 @@ class Parser { $value = wfEscapeWikiText( $this->mTitle->getRootText() ); break; case 'rootpagenamee': - $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getRootText() ) ) ); + $value = wfEscapeWikiText( wfUrlEncode( str_replace( + ' ', + '_', + $this->mTitle->getRootText() + ) ) ); break; case 'basepagename': $value = wfEscapeWikiText( $this->mTitle->getBaseText() ); break; case 'basepagenamee': - $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ) ); + $value = wfEscapeWikiText( wfUrlEncode( str_replace( + ' ', + '_', + $this->mTitle->getBaseText() + ) ) ); break; case 'talkpagename': if ( $this->mTitle->canTalk() ) { @@ -2928,7 +3049,9 @@ class Parser { $value = $this->mTitle->getNamespace(); break; case 'talkspace': - $value = $this->mTitle->canTalk() ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() ) : ''; + $value = $this->mTitle->canTalk() + ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() ) + : ''; break; case 'talkspacee': $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : ''; @@ -2940,7 +3063,7 @@ class Parser { $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) ); break; case 'currentdayname': - $value = $pageLang->getWeekdayName( MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 ); + $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 ); break; case 'currentyear': $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true ); @@ -2960,13 +3083,19 @@ class Parser { $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) ); break; case 'localdayname': - $value = $pageLang->getWeekdayName( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1 ); + $value = $pageLang->getWeekdayName( + (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1 + ); break; case 'localyear': $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true ); break; case 'localtime': - $value = $pageLang->time( MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ), false, false ); + $value = $pageLang->time( + MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ), + false, + false + ); break; case 'localhour': $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true ); @@ -3020,8 +3149,7 @@ class Parser { case 'server': return $wgServer; case 'servername': - $serverParts = wfParseUrl( $wgServer ); - return $serverParts && isset( $serverParts['host'] ) ? $serverParts['host'] : $wgServer; + return $wgServerName; case 'scriptpath': return $wgScriptPath; case 'stylepath': @@ -3031,13 +3159,17 @@ class Parser { case 'contentlanguage': global $wgLanguageCode; return $wgLanguageCode; + case 'cascadingsources': + $value = CoreParserFunctions::cascadingsources( $this ); + break; default: $ret = null; - if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) ) ) { - return $ret; - } else { - return null; - } + wfRunHooks( + 'ParserGetVariableValueSwitch', + array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) + ); + + return $ret; } if ( $index ) { @@ -3052,7 +3184,7 @@ class Parser { * * @private */ - function initialiseVariables() { + public function initialiseVariables() { wfProfileIn( __METHOD__ ); $variableIDs = MagicWord::getVariableIDs(); $substIDs = MagicWord::getSubstIDs(); @@ -3067,9 +3199,9 @@ class Parser { * This is the ghost of replace_variables(). * * @param string $text The text to parse - * @param $flags Integer: bitwise combination of: - * self::PTD_FOR_INCLUSION Handle "" and "" as if the text is being - * included. Default is to assume a direct page view. + * @param int $flags Bitwise combination of: + * - 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. * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. @@ -3082,11 +3214,9 @@ class Parser { * cache may be implemented at a later date which takes further advantage of these strict * dependency requirements. * - * @private - * * @return PPNode */ - function preprocessToDom( $text, $flags = 0 ) { + public function preprocessToDom( $text, $flags = 0 ) { $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags ); return $dom; } @@ -3094,7 +3224,7 @@ class Parser { /** * Return a three-element array: leading whitespace, string contents, trailing whitespace * - * @param $s string + * @param string $s * * @return array */ @@ -3121,16 +3251,17 @@ class Parser { * self::OT_PREPROCESS: templates but not extension tags * self::OT_HTML: all templates and extension tags * - * @param string $text the text to transform - * @param $frame PPFrame Object describing the arguments passed to the template. - * Arguments may also be provided as an associative array, as was the usual case before MW1.12. - * Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly. - * @param $argsOnly Boolean only do argument (triple-brace) expansion, not double-brace expansion - * @private - * + * @param string $text The text to transform + * @param bool|PPFrame $frame Object describing the arguments passed to the + * template. Arguments may also be provided as an associative array, as + * was the usual case before MW1.12. Providing arguments this way may be + * useful for extensions wishing to perform variable replacement + * explicitly. + * @param bool $argsOnly Only do argument (triple-brace) expansion, not + * double-brace expansion. * @return string */ - function replaceVariables( $text, $frame = false, $argsOnly = false ) { + public function replaceVariables( $text, $frame = false, $argsOnly = false ) { # Is there any text? Also, Prevent too big inclusions! if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) { return $text; @@ -3140,7 +3271,8 @@ class Parser { if ( $frame === false ) { $frame = $this->getPreprocessor()->newFrame(); } elseif ( !( $frame instanceof PPFrame ) ) { - wfDebug( __METHOD__ . " called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" ); + wfDebug( __METHOD__ . " called using plain parameters instead of " + . "a PPFrame instance. Creating custom frame.\n" ); $frame = $this->getPreprocessor()->newCustomFrame( $frame ); } @@ -3155,11 +3287,11 @@ class Parser { /** * Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. * - * @param $args array + * @param array $args * * @return array */ - static function createAssocArgs( $args ) { + public static function createAssocArgs( $args ) { $assocArgs = array(); $index = 1; foreach ( $args as $arg ) { @@ -3185,7 +3317,7 @@ class Parser { * Warn the user when a parser limitation is reached * Will warn at most once the user per limitation type * - * @param string $limitationType should be one of: + * @param string $limitationType Should be one of: * 'expensive-parserfunction' (corresponding messages: * 'expensive-parserfunction-warning', * 'expensive-parserfunction-category') @@ -3201,11 +3333,11 @@ class Parser { * 'expansion-depth-exceeded' (corresponding messages: * 'expansion-depth-exceeded-warning', * 'expansion-depth-exceeded-category') - * @param int|null $current Current value - * @param int|null $max Maximum allowed, when an explicit limit has been + * @param string|int|null $current Current value + * @param string|int|null $max Maximum allowed, when an explicit limit has been * exceeded, provide the values (optional) */ - function limitationWarn( $limitationType, $current = '', $max = '' ) { + public function limitationWarn( $limitationType, $current = '', $max = '' ) { # does no harm if $current and $max are present but are unnecessary for the message $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max ) ->inLanguage( $this->mOptions->getUserLangObj() )->text(); @@ -3217,26 +3349,32 @@ class Parser { * Return the text of a template, after recursively * replacing any variables or templates within the template. * - * @param array $piece the parts of the template - * $piece['title']: the title, i.e. the part before the | - * $piece['parts']: the parameter array - * $piece['lineStart']: whether the brace was at the start of a line - * @param $frame PPFrame The current frame, contains template arguments - * @throws MWException - * @return String: the text of the template - * @private + * @param array $piece The parts of the template + * $piece['title']: the title, i.e. the part before the | + * $piece['parts']: the parameter array + * $piece['lineStart']: whether the brace was at the start of a line + * @param PPFrame $frame The current frame, contains template arguments + * @throws Exception + * @return string The text of the template */ - function braceSubstitution( $piece, $frame ) { + public function braceSubstitution( $piece, $frame ) { wfProfileIn( __METHOD__ ); wfProfileIn( __METHOD__ . '-setup' ); - # Flags - $found = false; # $text has been filled - $nowiki = false; # wiki markup in $text should be escaped - $isHTML = false; # $text is HTML, armour it against wikitext transformation - $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered - $isChildObj = false; # $text is a DOM node needing expansion in a child frame - $isLocalObj = false; # $text is a DOM node needing expansion in the current frame + // Flags + + // $text has been filled + $found = false; + // wiki markup in $text should be escaped + $nowiki = false; + // $text is HTML, armour it against wikitext transformation + $isHTML = false; + // Force interwiki transclusion to be done in raw mode not rendered + $forceRawInterwiki = false; + // $text is a DOM node needing expansion in a child frame + $isChildObj = false; + // $text is a DOM node needing expansion in the current frame + $isLocalObj = false; # Title object, where $text came from $title = false; @@ -3251,7 +3389,8 @@ class Parser { $originalTitle = $part1; # $args is a list of argument nodes, starting from index 0, not including $part1 - # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object + # @todo FIXME: If piece['parts'] is null then the call to getLength() + # below won't work b/c this $args isn't an object $args = ( null == $piece['parts'] ) ? array() : $piece['parts']; wfProfileOut( __METHOD__ . '-setup' ); @@ -3385,13 +3524,14 @@ class Parser { if ( !$title->isExternal() ) { if ( $title->isSpecialPage() && $this->mOptions->getAllowSpecialInclusion() - && $this->ot['html'] ) - { + && $this->ot['html'] + ) { // Pass the template arguments as URL parameters. // "uselang" will have no effect since the Language object // is forced to the one defined in ParserOptions. $pageArgs = array(); - for ( $i = 0; $i < $args->getLength(); $i++ ) { + $argsLength = $args->getLength(); + for ( $i = 0; $i < $argsLength; $i++ ) { $bits = $args->item( $i )->splitArg(); if ( strval( $bits['index'] ) === '' ) { $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) ); @@ -3416,7 +3556,8 @@ class Parser { } } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) { $found = false; # access denied - wfDebug( __METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey() ); + wfDebug( __METHOD__ . ": template inclusion denied for " . + $title->getPrefixedDBkey() . "\n" ); } else { list( $text, $title ) = $this->getTemplateDom( $title ); if ( $text !== false ) { @@ -3476,12 +3617,7 @@ class Parser { $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG ); } elseif ( $titleText !== false && $newFrame->isEmpty() ) { # Expansion is eligible for the empty-frame cache - if ( isset( $this->mTplExpandCache[$titleText] ) ) { - $text = $this->mTplExpandCache[$titleText]; - } else { - $text = $newFrame->expand( $text ); - $this->mTplExpandCache[$titleText] = $text; - } + $text = $newFrame->cachedExpand( $titleText, $text ); } else { # Uncached expansion $text = $newFrame->expand( $text ); @@ -3504,8 +3640,8 @@ class Parser { $text = wfEscapeWikiText( $text ); } elseif ( is_string( $text ) && !$piece['lineStart'] - && preg_match( '/^(?:{\\||:|;|#|\*)/', $text ) ) - { + && preg_match( '/^(?:{\\||:|;|#|\*)/', $text ) + ) { # Bug 529: if the template begins with a table or block-level # element, it should be treated as beginning a new line. # This behavior is somewhat controversial. @@ -3523,7 +3659,8 @@ class Parser { preg_replace( '/^:/', '', $originalTitle ); $text = "[[:$originalTitle]]"; } - $text .= $this->insertStripItem( '' ); + $text .= $this->insertStripItem( '' ); $this->limitationWarn( 'post-expand-template-inclusion' ); } @@ -3550,9 +3687,10 @@ class Parser { * nowiki: bool, wiki markup in $text should be escaped * * @since 1.21 - * @param $frame PPFrame The current frame, contains template arguments - * @param $function string Function name - * @param $args array Arguments to the function + * @param PPFrame $frame The current frame, contains template arguments + * @param string $function Function name + * @param array $args Arguments to the function + * @throws MWException * @return array */ public function callParserFunction( $frame, $function, array $args = array() ) { @@ -3655,11 +3793,11 @@ class Parser { * Get the semi-parsed DOM representation of a template with a given title, * and its redirect destination title. Cached. * - * @param $title Title + * @param Title $title * * @return array */ - function getTemplateDom( $title ) { + public function getTemplateDom( $title ) { $cacheTitle = $title; $titleText = $title->getPrefixedDBkey(); @@ -3694,10 +3832,11 @@ class Parser { /** * Fetch the unparsed text of a template and register a reference to it. * @param Title $title - * @return Array ( string or false, Title ) + * @return array ( string or false, Title ) */ - function fetchTemplateAndTitle( $title ) { - $templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate() + public function fetchTemplateAndTitle( $title ) { + // Defaults to Parser::statelessFetchTemplate() + $templateCb = $this->mOptions->getTemplateCallback(); $stuff = call_user_func( $templateCb, $title, $this ); $text = $stuff['text']; $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title; @@ -3717,9 +3856,9 @@ class Parser { /** * Fetch the unparsed text of a template and register a reference to it. * @param Title $title - * @return mixed string or false + * @return string|bool */ - function fetchTemplate( $title ) { + public function fetchTemplate( $title ) { $rv = $this->fetchTemplateAndTitle( $title ); return $rv[0]; } @@ -3728,12 +3867,12 @@ class Parser { * Static function to get a template * Can be overridden via ParserOptions::setTemplateCallback(). * - * @param $title Title - * @param $parser Parser + * @param Title $title + * @param bool|Parser $parser * * @return array */ - static function statelessFetchTemplate( $title, $parser = false ) { + public static function statelessFetchTemplate( $title, $parser = false ) { $text = $skip = false; $finalTitle = $title; $deps = array(); @@ -3817,7 +3956,7 @@ class Parser { * @param array $options Array of options to RepoGroup::findFile * @return File|bool */ - function fetchFile( $title, $options = array() ) { + public function fetchFile( $title, $options = array() ) { $res = $this->fetchFileAndTitle( $title, $options ); return $res[0]; } @@ -3827,9 +3966,9 @@ 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 Array ( File or false, Title of file ) + * @return array ( File or false, Title of file ) */ - function fetchFileAndTitle( $title, $options = array() ) { + public function fetchFileAndTitle( $title, $options = array() ) { $file = $this->fetchFileNoRegister( $title, $options ); $time = $file ? $file->getTimestamp() : false; @@ -3839,12 +3978,7 @@ class Parser { if ( $file && !$title->equals( $file->getTitle() ) ) { # Update fetched file title $title = $file->getTitle(); - if ( is_null( $file->getRedirectedTitle() ) ) { - # This file was not a redirect, but the title does not match. - # Register under the new name because otherwise the link will - # get lost. - $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 ); - } + $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 ); } return array( $file, $title ); } @@ -3857,7 +3991,7 @@ class Parser { * * @param Title $title * @param array $options Array of options to RepoGroup::findFile - * @return File or false + * @return File|bool */ protected function fetchFileNoRegister( $title, $options = array() ) { if ( isset( $options['broken'] ) ) { @@ -3873,12 +4007,12 @@ class Parser { /** * Transclude an interwiki link. * - * @param $title Title - * @param $action + * @param Title $title + * @param string $action * * @return string */ - function interwikiTransclude( $title, $action ) { + public function interwikiTransclude( $title, $action ) { global $wgEnableScaryTranscluding; if ( !$wgEnableScaryTranscluding ) { @@ -3894,10 +4028,10 @@ class Parser { } /** - * @param $url string - * @return Mixed|String + * @param string $url + * @return mixed|string */ - function fetchScaryTemplateMaybeFromCache( $url ) { + public function fetchScaryTemplateMaybeFromCache( $url ) { global $wgTranscludeCacheExpiry; $dbr = wfGetDB( DB_SLAVE ); $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry ); @@ -3911,8 +4045,10 @@ class Parser { $status = $req->execute(); // Status object if ( $status->isOK() ) { $text = $req->getContent(); - } elseif ( $req->getStatus() != 200 ) { // Though we failed to fetch the content, this status is useless. - return wfMessage( 'scarytranscludefailed-httpstatus', $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text(); + } elseif ( $req->getStatus() != 200 ) { + // Though we failed to fetch the content, this status is useless. + return wfMessage( 'scarytranscludefailed-httpstatus' ) + ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text(); } else { return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text(); } @@ -3930,12 +4066,12 @@ class Parser { * Triple brace replacement -- used for template arguments * @private * - * @param $piece array - * @param $frame PPFrame + * @param array $piece + * @param PPFrame $frame * * @return array */ - function argSubstitution( $piece, $frame ) { + public function argSubstitution( $piece, $frame ) { wfProfileIn( __METHOD__ ); $error = false; @@ -3945,11 +4081,10 @@ class Parser { $object = false; $text = $frame->getArgument( $argName ); if ( $text === false && $parts->getLength() > 0 - && ( - $this->ot['html'] - || $this->ot['pre'] - || ( $this->ot['wiki'] && $frame->isTemplate() ) - ) + && ( $this->ot['html'] + || $this->ot['pre'] + || ( $this->ot['wiki'] && $frame->isTemplate() ) + ) ) { # No match in frame, use the supplied default $object = $parts->item( 0 )->getChildren(); @@ -3986,16 +4121,17 @@ class Parser { * attributes Optional associative array of parsed attributes * inner Contents of extension element * noClose Original text did not have a close tag - * @param $frame PPFrame + * @param PPFrame $frame * * @throws MWException * @return string */ - function extensionSubstitution( $params, $frame ) { + public function extensionSubstitution( $params, $frame ) { $name = $frame->expand( $params['name'] ); $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] ); $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] ); - $marker = "{$this->mUniqPrefix}-$name-" . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX; + $marker = "{$this->mUniqPrefix}-$name-" + . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX; $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) && ( $this->ot['html'] || $this->ot['pre'] ); @@ -4070,11 +4206,11 @@ class Parser { /** * Increment an include size counter * - * @param string $type the type of expansion - * @param $size Integer: the size of the text - * @return Boolean: false if this inclusion would take it over the maximum, true otherwise + * @param string $type The type of expansion + * @param int $size The size of the text + * @return bool False if this inclusion would take it over the maximum, true otherwise */ - function incrementIncludeSize( $type, $size ) { + public function incrementIncludeSize( $type, $size ) { if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) { return false; } else { @@ -4086,9 +4222,9 @@ class Parser { /** * Increment the expensive function count * - * @return Boolean: false if the limit has been exceeded + * @return bool False if the limit has been exceeded */ - function incrementExpensiveFunctionCount() { + public function incrementExpensiveFunctionCount() { $this->mExpensiveFunctionCount++; return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit(); } @@ -4097,11 +4233,11 @@ class Parser { * Strip double-underscore items like __NOGALLERY__ and __NOTOC__ * Fills $this->mDoubleUnderscores, returns the modified text * - * @param $text string + * @param string $text * * @return string */ - function doDoubleUnderscore( $text ) { + public function doDoubleUnderscore( $text ) { wfProfileIn( __METHOD__ ); # The position of __TOC__ needs to be recorded @@ -4127,7 +4263,9 @@ class Parser { if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) { $this->mShowToc = false; } - if ( isset( $this->mDoubleUnderscores['hiddencat'] ) && $this->mTitle->getNamespace() == NS_CATEGORY ) { + if ( isset( $this->mDoubleUnderscores['hiddencat'] ) + && $this->mTitle->getNamespace() == NS_CATEGORY + ) { $this->addTrackingCategory( 'hidden-category-category' ); } # (bug 8068) Allow control over whether robots index a page. @@ -4156,8 +4294,12 @@ class Parser { * Add a tracking category, getting the title from a system message, * or print a debug message if the title is invalid. * - * @param string $msg message key - * @return Boolean: whether the addition was successful + * Please add any message that you use with this function to + * $wgTrackingCategories. That way they will be listed on + * Special:TrackingCategories. + * + * @param string $msg Message key + * @return bool Whether the addition was successful */ public function addTrackingCategory( $msg ) { if ( $this->mTitle->getNamespace() === NS_SPECIAL ) { @@ -4195,13 +4337,13 @@ class Parser { * It loops through all headlines, collects the necessary data, then splits up the * string and re-inserts the newly formatted headlines. * - * @param $text String - * @param string $origText original, untouched wikitext - * @param $isMain Boolean + * @param string $text + * @param string $origText Original, untouched wikitext + * @param bool $isMain * @return mixed|string * @private */ - function formatHeadings( $text, $origText, $isMain = true ) { + public function formatHeadings( $text, $origText, $isMain = true ) { global $wgMaxTocLevel, $wgExperimentalHtmlIds; # Inhibit editsection links if requested in the page @@ -4218,7 +4360,11 @@ class Parser { # Get all headlines for numbering them and adding funky stuff like [edit] # links - this is for later, but we need the number of headlines right now $matches = array(); - $numMatches = preg_match_all( '/[1-6])(?P.*?' . '>)\s*(?P
[\s\S]*?)\s*<\/H[1-6] *>/i', $text, $matches ); + $numMatches = preg_match_all( + '/[1-6])(?P.*?' . '>)\s*(?P
[\s\S]*?)\s*<\/H[1-6] *>/i', + $text, + $matches + ); # if there are fewer than 4 headlines in the article, do not show TOC # unless it's been explicitly enabled. @@ -4367,7 +4513,10 @@ class Parser { # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from , # to allow setting directionality in toc items. $tocline = preg_replace( - array( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#' ), + array( + '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', + '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#' + ), array( '', '<$1>' ), $safeHeadline ); @@ -4431,7 +4580,11 @@ 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 = Html::element( 'span', array( 'class' => 'mw-headline-number' ), $numbering ) . ' ' . $headline; + $headline = Html::element( + 'span', + array( 'class' => 'mw-headline-number' ), + $numbering + ) . ' ' . $headline; } # Create the anchor for linking from the TOC to the section @@ -4479,21 +4632,30 @@ class Parser { if ( $isTemplate ) { # Put a T flag in the section identifier, to indicate to extractSections() # that sections inside should be counted. - $editlinkArgs = array( $titleText, "T-$sectionIndex"/*, null */ ); + $editsectionPage = $titleText; + $editsectionSection = "T-$sectionIndex"; + $editsectionContent = null; } else { - $editlinkArgs = array( $this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint ); + $editsectionPage = $this->mTitle->getPrefixedText(); + $editsectionSection = $sectionIndex; + $editsectionContent = $headlineHint; } - // We use a bit of pesudo-xml for editsection markers. The language converter is run later on - // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff - // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped - // so we don't have to worry about a user trying to input one of these markers directly. - // We use a page and section attribute to stop the language converter from converting these important bits - // of data, but put the headline hint inside a content block because the language converter is supposed to + // We use a bit of pesudo-xml for editsection markers. The + // language converter is run later on. Using a UNIQ style marker + // leads to the converter screwing up the tokens when it + // converts stuff. And trying to insert strip tags fails too. At + // this point all real inputted tags have already been escaped, + // so we don't have to worry about a user trying to input one of + // these markers directly. We use a page and section attribute + // to stop the language converter from converting these + // important bits of data, but put the headline hint inside a + // content block because the language converter is supposed to // be able to convert that piece of data. - $editlink = ''; + // Gets replaced with html in ParserOutput::getText + $editlink = ''; } else { $editlink .= '/>'; } @@ -4521,6 +4683,7 @@ class Parser { $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() ); $this->mOutput->setTOCHTML( $toc ); $toc = self::TOC_START . $toc . self::TOC_END; + $this->mOutput->addModules( 'mediawiki.toc' ); } if ( $isMain ) { @@ -4575,14 +4738,19 @@ class Parser { * Transform wiki markup when saving a page by doing "\r\n" -> "\n" * conversion, substitting signatures, {{subst:}} templates, etc. * - * @param string $text the text to transform - * @param $title Title: the Title object for the current article - * @param $user User: the User object describing the current user - * @param $options ParserOptions: parsing options - * @param $clearState Boolean: whether to clear the parser state first - * @return String: the altered wiki markup + * @param string $text The text to transform + * @param Title $title The Title object for the current article + * @param User $user The User object describing the current user + * @param ParserOptions $options Parsing options + * @param bool $clearState Whether to clear the parser state first + * @return string The altered wiki markup */ - public function preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState = true ) { + public function preSaveTransform( $text, Title $title, User $user, + ParserOptions $options, $clearState = true + ) { + if ( $clearState ) { + $magicScopeVariable = $this->lock(); + } $this->startParse( $title, $options, self::OT_WIKI, $clearState ); $this->setUser( $user ); @@ -4602,14 +4770,13 @@ class Parser { /** * Pre-save transform helper function - * @private * - * @param $text string - * @param $user User + * @param string $text + * @param User $user * * @return string */ - function pstPass2( $text, $user ) { + private function pstPass2( $text, $user ) { global $wgContLang; # Note: This is the timestamp saved as hardcoded wikitext to @@ -4652,10 +4819,14 @@ class Parser { $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)|]] (double-width brackets, added in r40257) - $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]] (using either single or double-width comma) - $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] (reverse pipe trick: add context from page title) + // [[ns:page (context)|]] + $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; + // [[ns:page(context)|]] (double-width brackets, added in r40257) + $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; + // [[ns:page (context), context|]] (using either single or double-width comma) + $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; + // [[|page]] (reverse pipe trick: add context from page title) + $p2 = "/\[\[\\|($tc+)]]/"; # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text ); @@ -4687,13 +4858,13 @@ class Parser { * Do not reuse this parser instance after calling getUserSig(), * as it may have changed if it's the $wgParser. * - * @param $user User - * @param string|bool $nickname nickname to use or false to use user's default nickname - * @param $fancySig Boolean|null whether the nicknname is the complete signature - * or null to use default value + * @param User $user + * @param string|bool $nickname Nickname to use or false to use user's default nickname + * @param bool|null $fancySig whether the nicknname is the complete signature + * or null to use default value * @return string */ - function getUserSig( &$user, $nickname = false, $fancySig = null ) { + public function getUserSig( &$user, $nickname = false, $fancySig = null ) { global $wgMaxSigChars; $username = $user->getName(); @@ -4732,16 +4903,17 @@ class Parser { $nickText = wfEscapeWikiText( $nickname ); $msgName = $user->isAnon() ? 'signature-anon' : 'signature'; - return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()->title( $this->getTitle() )->text(); + return wfMessage( $msgName, $userText, $nickText )->inContentLanguage() + ->title( $this->getTitle() )->text(); } /** * Check that the user's signature contains no bad XML * - * @param $text String - * @return mixed An expanded string, or false if invalid. + * @param string $text + * @return string|bool An expanded string, or false if invalid. */ - function validateSig( $text ) { + public function validateSig( $text ) { return Xml::isWellFormedXmlFragment( $text ) ? $text : false; } @@ -4751,13 +4923,14 @@ class Parser { * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig * 2) Substitute all transclusions * - * @param $text String + * @param string $text * @param bool $parsing Whether we're cleaning (preferences save) or parsing - * @return String: signature text + * @return string Signature text */ public function cleanSig( $text, $parsing = false ) { if ( !$parsing ) { global $wgTitle; + $magicScopeVariable = $this->lock(); $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true ); } @@ -4788,8 +4961,8 @@ class Parser { /** * Strip ~~~, ~~~~ and ~~~~~ out of signatures * - * @param $text String - * @return String: signature text with /~{3,5}/ removed + * @param string $text + * @return string Signature text with /~{3,5}/ removed */ public static function cleanSigInSig( $text ) { $text = preg_replace( '/~{3,5}/', '', $text ); @@ -4800,22 +4973,26 @@ class Parser { * Set up some variables which are usually set up in parse() * so that an external function can call some class members with confidence * - * @param $title Title|null - * @param $options ParserOptions - * @param $outputType - * @param $clearState bool + * @param Title|null $title + * @param ParserOptions $options + * @param int $outputType + * @param bool $clearState */ - public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) { + public function startExternalParse( Title $title = null, ParserOptions $options, + $outputType, $clearState = true + ) { $this->startParse( $title, $options, $outputType, $clearState ); } /** - * @param $title Title|null - * @param $options ParserOptions - * @param $outputType - * @param $clearState bool + * @param Title|null $title + * @param ParserOptions $options + * @param int $outputType + * @param bool $clearState */ - private function startParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) { + private function startParse( Title $title = null, ParserOptions $options, + $outputType, $clearState = true + ) { $this->setTitle( $title ); $this->mOptions = $options; $this->setOutputType( $outputType ); @@ -4827,10 +5004,10 @@ class Parser { /** * Wrapper for preprocess() * - * @param string $text the text to preprocess - * @param $options ParserOptions: options - * @param $title Title object or null to use $wgTitle - * @return String + * @param string $text The text to preprocess + * @param ParserOptions $options Options + * @param Title|null $title Title object or null to use $wgTitle + * @return string */ public function transformMsg( $text, $options, $title = null ) { static $executing = false; @@ -4873,10 +5050,10 @@ 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 $callback Mixed: the callback function (and object) to use for the tag + * @param string $tag The tag to use, e.g. 'hook' for "" + * @param callable $callback The callback function (and object) to use for the tag * @throws MWException - * @return Mixed|null The old value of the mTagHooks array associated with the hook + * @return callable|null The old value of the mTagHooks array associated with the hook */ public function setHook( $tag, $callback ) { $tag = strtolower( $tag ); @@ -4904,12 +5081,12 @@ class Parser { * @since 1.10 * @todo better document or deprecate this * - * @param $tag Mixed: the tag to use, e.g. 'hook' for "" - * @param $callback Mixed: the callback function (and object) to use for the tag + * @param string $tag The tag to use, e.g. 'hook' for "" + * @param callable $callback The callback function (and object) to use for the tag * @throws MWException - * @return Mixed|null The old value of the mTagHooks array associated with the hook + * @return callable|null The old value of the mTagHooks array associated with the hook */ - function setTransparentTagHook( $tag, $callback ) { + public 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" ); @@ -4923,7 +5100,7 @@ class Parser { /** * Remove all tag hooks */ - function clearTagHooks() { + public function clearTagHooks() { $this->mTagHooks = array(); $this->mFunctionTagHooks = array(); $this->mStripList = $this->mDefaultStripList; @@ -4946,8 +5123,8 @@ class Parser { * isHTML The returned text is HTML, armour it against wikitext transformation * * @param string $id The magic word ID - * @param $callback Mixed: the callback function (and object) to use - * @param $flags Integer: a combination of the following flags: + * @param callable $callback The callback function (and object) to use + * @param int $flags A combination of the following flags: * SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}} * * SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text. This @@ -4970,7 +5147,7 @@ class Parser { * about the methods available in PPFrame and PPNode. * * @throws MWException - * @return string|callback The old callback function for this name, if any + * @return string|callable The old callback function for this name, if any */ public function setFunctionHook( $id, $callback, $flags = 0 ) { global $wgContLang; @@ -5008,9 +5185,9 @@ class Parser { /** * Get all registered function hook identifiers * - * @return Array + * @return array */ - function getFunctionHooks() { + public function getFunctionHooks() { return array_keys( $this->mFunctionHooks ); } @@ -5018,13 +5195,13 @@ class Parser { * 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. - * @param $tag - * @param $callback - * @param $flags + * @param string $tag + * @param callable $callback + * @param int $flags * @throws MWException * @return null */ - function setFunctionTagHook( $tag, $callback, $flags ) { + public function setFunctionTagHook( $tag, $callback, $flags ) { $tag = strtolower( $tag ); if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) { throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" ); @@ -5045,12 +5222,12 @@ class Parser { * Replace "" link placeholders with actual links, in the buffer * Placeholders created in Skin::makeLinkObj() * - * @param $text string - * @param $options int + * @param string $text + * @param int $options * - * @return array of link CSS classes, indexed by PDBK. + * @return array Array of link CSS classes, indexed by PDBK. */ - function replaceLinkHolders( &$text, $options = 0 ) { + public function replaceLinkHolders( &$text, $options = 0 ) { return $this->mLinkHolders->replace( $text ); } @@ -5058,10 +5235,10 @@ class Parser { * Replace "" link placeholders with plain text of links * (not HTML-formatted). * - * @param $text String - * @return String + * @param string $text + * @return string */ - function replaceLinkHoldersText( $text ) { + public function replaceLinkHoldersText( $text ) { return $this->mLinkHolders->replaceText( $text ); } @@ -5078,7 +5255,7 @@ class Parser { * @param array $params * @return string HTML */ - function renderImageGallery( $text, $params ) { + public function renderImageGallery( $text, $params ) { wfProfileIn( __METHOD__ ); $mode = false; @@ -5203,7 +5380,7 @@ class Parser { } else { $localLinkTitle = Title::newFromText( $linkValue ); if ( $localLinkTitle !== null ) { - $link = $localLinkTitle->getLocalURL(); + $link = $localLinkTitle->getLinkURL(); } } break; @@ -5213,7 +5390,7 @@ class Parser { $handlerOptions[$paramName] = $match; } else { // Guess not. Append it to the caption. - wfDebug( "$parameterMatch failed parameter validation" ); + wfDebug( "$parameterMatch failed parameter validation\n" ); $label .= '|' . $parameterMatch; } } @@ -5230,15 +5407,16 @@ class Parser { $ig->add( $title, $label, $alt, $link, $handlerOptions ); } $html = $ig->toHTML(); + wfRunHooks( 'AfterParserFetchFileAndTitle', array( $this, $ig, &$html ) ); wfProfileOut( __METHOD__ ); return $html; } /** - * @param $handler + * @param string $handler * @return array */ - function getImageParams( $handler ) { + public function getImageParams( $handler ) { if ( $handler ) { $handlerClass = get_class( $handler ); } else { @@ -5281,12 +5459,12 @@ class Parser { /** * Parse image options text and use it to make an image * - * @param $title Title - * @param $options String - * @param $holders LinkHolderArray|bool + * @param Title $title + * @param string $options + * @param LinkHolderArray|bool $holders * @return string HTML */ - function makeImage( $title, $options, $holders = false ) { + public function makeImage( $title, $options, $holders = false ) { # Check if the options text is of the form "options|alt text" # Options are: # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang @@ -5336,6 +5514,7 @@ class Parser { $caption = ''; $params = array( 'frame' => array(), 'handler' => array(), 'horizAlign' => array(), 'vertAlign' => array() ); + $seenformat = false; foreach ( $parts as $part ) { $part = trim( $part ); list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part ); @@ -5384,7 +5563,7 @@ class Parser { $paramName = 'no-link'; $value = true; $validated = true; - } elseif ( preg_match( "/^(?i)$prots/", $value ) ) { + } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) { if ( preg_match( "/^((?i)$prots)$chars+$/u", $value, $m ) ) { $paramName = 'link-url'; $this->mOutput->addExternalLink( $value ); @@ -5403,6 +5582,13 @@ class Parser { } } break; + case 'frameless': + case 'framed': + case 'thumbnail': + // use first appearing option, discard others. + $validated = ! $seenformat; + $seenformat = true; + break; default: # Most other things appear to be empty or numeric... $validated = ( $value === false || is_numeric( trim( $value ) ) ); @@ -5430,10 +5616,10 @@ class Parser { $params['frame']['caption'] = $caption; # Will the image be presented in a frame, with the caption below? - $imageIsFramed = isset( $params['frame']['frame'] ) || - isset( $params['frame']['framed'] ) || - isset( $params['frame']['thumbnail'] ) || - isset( $params['frame']['manualthumb'] ); + $imageIsFramed = isset( $params['frame']['frame'] ) + || isset( $params['frame']['framed'] ) + || isset( $params['frame']['thumbnail'] ) + || isset( $params['frame']['manualthumb'] ); # In the old days, [[Image:Foo|text...]] would set alt text. Later it # came to also set the caption, ordinary text after the image -- which @@ -5490,9 +5676,9 @@ class Parser { } /** - * @param $caption - * @param $holders LinkHolderArray - * @return mixed|String + * @param string $caption + * @param LinkHolderArray|bool $holders + * @return mixed|string */ protected function stripAltText( $caption, $holders ) { # Strip bad stuff out of the title (tooltip). We can't just use @@ -5517,7 +5703,7 @@ class Parser { * Set a flag in the output object indicating that the content is dynamic and * shouldn't be cached. */ - function disableCache() { + public function disableCache() { wfDebug( "Parser output marked as uncacheable.\n" ); if ( !$this->mOutput ) { throw new MWException( __METHOD__ . @@ -5531,11 +5717,11 @@ class Parser { * Callback from the Sanitizer for expanding items found in HTML attribute * values, so they can be safely tested and escaped. * - * @param $text String - * @param $frame PPFrame - * @return String + * @param string $text + * @param bool|PPFrame $frame + * @return string */ - function attributeStripCallback( &$text, $frame = false ) { + public function attributeStripCallback( &$text, $frame = false ) { $text = $this->replaceVariables( $text, $frame ); $text = $this->mStripState->unstripBoth( $text ); return $text; @@ -5546,8 +5732,12 @@ class Parser { * * @return array */ - function getTags() { - return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ), array_keys( $this->mFunctionTagHooks ) ); + public function getTags() { + return array_merge( + array_keys( $this->mTransparentTagHooks ), + array_keys( $this->mTagHooks ), + array_keys( $this->mFunctionTagHooks ) + ); } /** @@ -5556,11 +5746,11 @@ class Parser { * Transparent tag hooks are like regular XML-style tag hooks, except they * operate late in the transformation sequence, on HTML instead of wikitext. * - * @param $text string + * @param string $text * * @return string */ - function replaceTransparentTags( $text ) { + public function replaceTransparentTags( $text ) { $matches = array(); $elements = array_keys( $this->mTransparentTagHooks ); $text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix ); @@ -5570,7 +5760,10 @@ class Parser { list( $element, $content, $params, $tag ) = $data; $tagName = strtolower( $element ); if ( isset( $this->mTransparentTagHooks[$tagName] ) ) { - $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], array( $content, $params, $this ) ); + $output = call_user_func_array( + $this->mTransparentTagHooks[$tagName], + array( $content, $params, $this ) + ); } else { $output = $tag; } @@ -5586,7 +5779,7 @@ class Parser { * External callers should use the getSection and replaceSection methods. * * @param string $text Page wikitext - * @param string $section a section identifier string of the form: + * @param string|number $sectionId A section identifier string of the form: * " - - ... -
" * * Currently the only recognised flag is "T", which means the target section number @@ -5603,20 +5796,22 @@ class Parser { * string. If $text is the empty string and section 0 is replaced, $newText is * returned. * - * @param string $mode one of "get" or "replace" - * @param string $newText replacement text for section data. - * @return String: for "get", the extracted section text. - * for "replace", the whole page with the section replaced. + * @param string $mode One of "get" or "replace" + * @param string $newText Replacement text for section data. + * @return string For "get", the extracted section text. + * for "replace", the whole page with the section replaced. */ - private function extractSections( $text, $section, $mode, $newText = '' ) { + private function extractSections( $text, $sectionId, $mode, $newText = '' ) { global $wgTitle; # not generally used but removes an ugly failure mode + + $magicScopeVariable = $this->lock(); $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true ); $outText = ''; $frame = $this->getPreprocessor()->newFrame(); # Process section extraction flags $flags = 0; - $sectionParts = explode( '-', $section ); + $sectionParts = explode( '-', $sectionId ); $sectionIndex = array_pop( $sectionParts ); foreach ( $sectionParts as $part ) { if ( $part === 'T' ) { @@ -5724,13 +5919,15 @@ class Parser { * * If a section contains subsections, these are also returned. * - * @param string $text text to look in - * @param string $section section identifier - * @param string $deftext default to return if section is not found - * @return string text of the requested section + * @param string $text Text to look in + * @param string|number $sectionId Section identifier as a number or string + * (e.g. 0, 1 or 'T-1'). + * @param string $defaultText Default to return if section is not found + * + * @return string Text of the requested section */ - public function getSection( $text, $section, $deftext = '' ) { - return $this->extractSections( $text, $section, "get", $deftext ); + public function getSection( $text, $sectionId, $defaultText = '' ) { + return $this->extractSections( $text, $sectionId, 'get', $defaultText ); } /** @@ -5738,30 +5935,33 @@ class Parser { * specified by $section has been replaced with $text. If the target * section does not exist, $oldtext is returned unchanged. * - * @param string $oldtext former text of the article - * @param int $section section identifier - * @param string $text replacing text - * @return String: modified text + * @param string $oldText Former text of the article + * @param string|number $sectionId Section identifier as a number or string + * (e.g. 0, 1 or 'T-1'). + * @param string $newText Replacing text + * + * @return string Modified text */ - public function replaceSection( $oldtext, $section, $text ) { - return $this->extractSections( $oldtext, $section, "replace", $text ); + public function replaceSection( $oldText, $sectionId, $newText ) { + return $this->extractSections( $oldText, $sectionId, 'replace', $newText ); } /** * Get the ID of the revision we are parsing * - * @return Mixed: integer or null + * @return int|null */ - function getRevisionId() { + public function getRevisionId() { return $this->mRevisionId; } /** * Get the revision object for $this->mRevisionId * - * @return Revision|null either a Revision object or null + * @return Revision|null Either a Revision object or null + * @since 1.23 (public since 1.23) */ - protected function getRevisionObject() { + public function getRevisionObject() { if ( !is_null( $this->mRevisionObject ) ) { return $this->mRevisionObject; } @@ -5776,8 +5976,9 @@ class Parser { /** * Get the timestamp associated with the current revision, adjusted for * the default server-local timestamp + * @return string */ - function getRevisionTimestamp() { + public function getRevisionTimestamp() { if ( is_null( $this->mRevisionTimestamp ) ) { wfProfileIn( __METHOD__ ); @@ -5802,9 +6003,9 @@ class Parser { /** * Get the name of the user that edited the last revision * - * @return String: user name + * @return string User name */ - function getRevisionUser() { + public function getRevisionUser() { if ( is_null( $this->mRevisionUser ) ) { $revObject = $this->getRevisionObject(); @@ -5822,9 +6023,9 @@ class Parser { /** * Get the size of the revision * - * @return int|null revision size + * @return int|null Revision size */ - function getRevisionSize() { + public function getRevisionSize() { if ( is_null( $this->mRevisionSize ) ) { $revObject = $this->getRevisionObject(); @@ -5872,7 +6073,7 @@ class Parser { * Accessor for $mDefaultSort * Unlike getDefaultSort(), will return false if none is set * - * @return string or false + * @return string|bool */ public function getCustomDefaultSort() { return $this->mDefaultSort; @@ -5883,7 +6084,7 @@ class Parser { * presumably extracted from a heading, for example "Header" from * "== Header ==". * - * @param $text string + * @param string $text * * @return string */ @@ -5919,7 +6120,7 @@ class Parser { * to create valid section anchors by mimicing the output of the * parser when headings are parsed. * - * @param string $text text string to be stripped of wikitext + * @param string $text Text string to be stripped of wikitext * for use in a Section anchor * @return string Filtered text string */ @@ -5930,7 +6131,7 @@ class Parser { # Strip external link markup # @todo FIXME: Not tolerant to blank link text - # I.E. [http://www.mediawiki.org] will render as [1] or something depending + # I.E. [https://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( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text ); @@ -5945,14 +6146,15 @@ class Parser { /** * strip/replaceVariables/unstrip for preprocessor regression testing * - * @param $text string - * @param $title Title - * @param $options ParserOptions - * @param $outputType int + * @param string $text + * @param Title $title + * @param ParserOptions $options + * @param int $outputType * * @return string */ - function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) { + public function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) { + $magicScopeVariable = $this->lock(); $this->startParse( $title, $options, $outputType, true ); $text = $this->replaceVariables( $text ); @@ -5962,22 +6164,22 @@ class Parser { } /** - * @param $text string - * @param $title Title - * @param $options ParserOptions + * @param string $text + * @param Title $title + * @param ParserOptions $options * @return string */ - function testPst( $text, Title $title, ParserOptions $options ) { + public function testPst( $text, Title $title, ParserOptions $options ) { return $this->preSaveTransform( $text, $title, $options->getUser(), $options ); } /** - * @param $text - * @param $title Title - * @param $options ParserOptions + * @param string $text + * @param Title $title + * @param ParserOptions $options * @return string */ - function testPreprocess( $text, Title $title, ParserOptions $options ) { + public function testPreprocess( $text, Title $title, ParserOptions $options ) { return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS ); } @@ -5992,12 +6194,12 @@ class Parser { * two strings will be replaced with the value returned by the callback in * each case. * - * @param $s string - * @param $callback + * @param string $s + * @param callable $callback * * @return string */ - function markerSkipCallback( $s, $callback ) { + public function markerSkipCallback( $s, $callback ) { $i = 0; $out = ''; while ( $i < strlen( $s ) ) { @@ -6024,10 +6226,10 @@ class Parser { /** * Remove any strip markers found in the given text. * - * @param $text Input string + * @param string $text Input string * @return string */ - function killMarkers( $text ) { + public function killMarkers( $text ) { return $this->mStripState->killMarkers( $text ); } @@ -6043,11 +6245,11 @@ class Parser { * unserializeHalfParsedText(). The text can then be safely incorporated into * the return value of a parser hook. * - * @param $text string + * @param string $text * * @return array */ - function serializeHalfParsedText( $text ) { + public function serializeHalfParsedText( $text ) { wfProfileIn( __METHOD__ ); $data = array( 'text' => $text, @@ -6072,9 +6274,9 @@ class Parser { * * @param array $data Serialized data * @throws MWException - * @return String + * @return string */ - function unserializeHalfParsedText( $data ) { + public function unserializeHalfParsedText( $data ) { if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) { throw new MWException( __METHOD__ . ': invalid version' ); } @@ -6095,18 +6297,18 @@ class Parser { * serializeHalfParsedText(), is compatible with the current version of the * parser. * - * @param $data Array + * @param array $data * * @return bool */ - function isValidHalfParsedText( $data ) { + public 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 + * @param string $value * * @return array * @since 1.20 @@ -6130,4 +6332,68 @@ class Parser { } return $parsedWidthParam; } + + /** + * Lock the current instance of the parser. + * + * This is meant to stop someone from calling the parser + * recursively and messing up all the strip state. + * + * @throws MWException If parser is in a parse + * @return ScopedCallback The lock will be released once the return value goes out of scope. + */ + protected function lock() { + if ( $this->mInParse ) { + throw new MWException( "Parser state cleared while parsing. " + . "Did you call Parser::parse recursively?" ); + } + $this->mInParse = true; + + $that = $this; + $recursiveCheck = new ScopedCallback( function() use ( $that ) { + $that->mInParse = false; + } ); + + return $recursiveCheck; + } + + /** + * Strip outer

tag from the HTML source of a single paragraph. + * + * Returns original HTML if the

tag has any attributes, if there's no wrapping

tag, + * or if there is more than one

tag in the input HTML. + * + * @param string $html + * @return string + * @since 1.24 + */ + public static function stripOuterParagraph( $html ) { + $m = array(); + if ( preg_match( '/^

(.*)\n?<\/p>\n?$/sU', $html, $m ) ) { + if ( strpos( $m[1], '

' ) === false ) { + $html = $m[1]; + } + } + + return $html; + } + + /** + * Return this parser if it is not doing anything, otherwise + * get a fresh parser. You can use this method by doing + * $myParser = $wgParser->getFreshParser(), or more simply + * $wgParser->getFreshParser()->parse( ... ); + * if you're unsure if $wgParser is safe to use. + * + * @since 1.24 + * @return Parser A parser object that is not parsing anything + */ + public function getFreshParser() { + global $wgParserConf; + if ( $this->mInParse ) { + return new $wgParserConf['class']( $wgParserConf ); + } else { + return $this; + } + } } diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php index 7053f134..79523003 100644 --- a/includes/parser/ParserCache.php +++ b/includes/parser/ParserCache.php @@ -26,9 +26,8 @@ * @todo document */ class ParserCache { + /** @var MWMemcached */ private $mMemc; - const try116cache = false; /* Only useful $wgParserCacheExpireTime after updating to 1.17 */ - /** * Get an instance of this object * @@ -47,7 +46,7 @@ class ParserCache { * Setup a cache pathway with a given back-end storage mechanism. * May be a memcached client or a BagOStuff derivative. * - * @param $memCached Object + * @param MWMemcached $memCached * @throws MWException */ protected function __construct( $memCached ) { @@ -58,8 +57,8 @@ class ParserCache { } /** - * @param $article Article - * @param $hash string + * @param Article $article + * @param string $hash * @return mixed|string */ protected function getParserOutputKey( $article, $hash ) { @@ -74,7 +73,7 @@ class ParserCache { } /** - * @param $article Article + * @param Article $article * @return mixed|string */ protected function getOptionsKey( $article ) { @@ -92,11 +91,11 @@ class ParserCache { * English preferences. That's why we take into account *all* user * options. (r70809 CR) * - * @param $article Article - * @param $popts ParserOptions + * @param Article $article + * @param ParserOptions $popts * @return string */ - function getETag( $article, $popts ) { + public function getETag( $article, $popts ) { return 'W/"' . $this->getParserOutputKey( $article, $popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) ) . "--" . $article->getTouched() . '"'; @@ -104,8 +103,8 @@ class ParserCache { /** * Retrieve the ParserOutput from ParserCache, even if it's outdated. - * @param $article Article - * @param $popts ParserOptions + * @param Article $article + * @param ParserOptions $popts * @return ParserOutput|bool False on failure */ public function getDirty( $article, $popts ) { @@ -114,15 +113,22 @@ class ParserCache { } /** - * Used to provide a unique id for the PoolCounter. + * Generates a key for caching the given article considering + * the given parser options. + * + * @note Which parser options influence the cache key + * is controlled via ParserOutput::recordOption() or + * ParserOptions::addExtraKey(). + * + * @note Used by Article to provide a unique id for the PoolCounter. * It would be preferable to have this code in get() * instead of having Article looking in our internals. * * @todo Document parameter $useOutdated * - * @param $article Article - * @param $popts ParserOptions - * @param $useOutdated Boolean (default true) + * @param Article $article + * @param ParserOptions $popts + * @param bool $useOutdated (default true) * @return bool|mixed|string */ public function getKey( $article, $popts, $useOutdated = true ) { @@ -139,29 +145,40 @@ class ParserCache { if ( !$useOutdated && $optionsKey->expired( $article->getTouched() ) ) { wfIncrStats( "pcache_miss_expired" ); $cacheTime = $optionsKey->getCacheTime(); - wfDebug( "Parser options key expired, touched " . $article->getTouched() . ", epoch $wgCacheEpoch, cached $cacheTime\n" ); + wfDebug( "Parser options key expired, touched " . $article->getTouched() + . ", epoch $wgCacheEpoch, cached $cacheTime\n" ); + return false; + } elseif ( $optionsKey->isDifferentRevision( $article->getLatest() ) ) { + wfIncrStats( "pcache_miss_revid" ); + $revId = $article->getLatest(); + $cachedRevId = $optionsKey->getCacheRevisionId(); + wfDebug( "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n" ); return false; } + // $optionsKey->mUsedOptions is set by save() by calling ParserOutput::getUsedOptions() $usedOptions = $optionsKey->mUsedOptions; wfDebug( "Parser cache options found.\n" ); } else { - if ( !$useOutdated && !self::try116cache ) { + if ( !$useOutdated ) { return false; } $usedOptions = ParserOptions::legacyOptions(); } - return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions, $article->getTitle() ) ); + return $this->getParserOutputKey( + $article, + $popts->optionsHash( $usedOptions, $article->getTitle() ) + ); } /** * Retrieve the ParserOutput from ParserCache. * false if not found or outdated. * - * @param $article Article - * @param $popts ParserOptions - * @param $useOutdated Boolean (default false) + * @param Article $article + * @param ParserOptions $popts + * @param bool $useOutdated (default false) * * @return ParserOutput|bool False on failure */ @@ -186,12 +203,6 @@ class ParserCache { } $value = $this->mMemc->get( $parserOutputKey ); - if ( self::try116cache && !$value && strpos( $value, '*' ) !== -1 ) { - wfDebug( "New format parser cache miss.\n" ); - $parserOutputKey = $this->getParserOutputKey( $article, - $popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) ); - $value = $this->mMemc->get( $parserOutputKey ); - } if ( !$value ) { wfDebug( "ParserOutput cache miss.\n" ); wfIncrStats( "pcache_miss_absent" ); @@ -209,7 +220,14 @@ class ParserCache { if ( !$useOutdated && $value->expired( $touched ) ) { wfIncrStats( "pcache_miss_expired" ); $cacheTime = $value->getCacheTime(); - wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" ); + wfDebug( "ParserOutput key expired, touched $touched, " + . "epoch $wgCacheEpoch, cached $cacheTime\n" ); + $value = false; + } elseif ( $value->isDifferentRevision( $article->getLatest() ) ) { + wfIncrStats( "pcache_miss_revid" ); + $revId = $article->getLatest(); + $cachedRevId = $value->getCacheRevisionId(); + wfDebug( "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n" ); $value = false; } else { wfIncrStats( "pcache_hit" ); @@ -220,15 +238,20 @@ class ParserCache { } /** - * @param $parserOutput ParserOutput - * @param $article Article - * @param $popts ParserOptions - * @param $cacheTime Time when the cache was generated + * @param ParserOutput $parserOutput + * @param WikiPage $page + * @param ParserOptions $popts + * @param string $cacheTime Time when the cache was generated + * @param int $revId Revision ID that was parsed */ - public function save( $parserOutput, $article, $popts, $cacheTime = null ) { + public function save( $parserOutput, $page, $popts, $cacheTime = null, $revId = null ) { $expire = $parserOutput->getCacheExpiry(); if ( $expire > 0 ) { $cacheTime = $cacheTime ?: wfTimestampNow(); + if ( !$revId ) { + $revision = $page->getRevision(); + $revId = $revision ? $revision->getId() : null; + } $optionsKey = new CacheTime; $optionsKey->mUsedOptions = $parserOutput->getUsedOptions(); @@ -236,23 +259,30 @@ class ParserCache { $optionsKey->setCacheTime( $cacheTime ); $parserOutput->setCacheTime( $cacheTime ); + $optionsKey->setCacheRevisionId( $revId ); + $parserOutput->setCacheRevisionId( $revId ); $optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() ); - $parserOutputKey = $this->getParserOutputKey( $article, - $popts->optionsHash( $optionsKey->mUsedOptions, $article->getTitle() ) ); + $parserOutputKey = $this->getParserOutputKey( $page, + $popts->optionsHash( $optionsKey->mUsedOptions, $page->getTitle() ) ); // Save the timestamp so that we don't have to load the revision row on view - $parserOutput->setTimestamp( $article->getTimestamp() ); + $parserOutput->setTimestamp( $page->getTimestamp() ); + + $msg = "Saved in parser cache with key $parserOutputKey" . + " and timestamp $cacheTime" . + " and revision id $revId" . + "\n"; - $parserOutput->mText .= "\n\n"; - wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n" ); + $parserOutput->mText .= "\n\n"; + wfDebug( $msg ); // Save the parser output $this->mMemc->set( $parserOutputKey, $parserOutput, $expire ); // ...and its pointer - $this->mMemc->set( $this->getOptionsKey( $article ), $optionsKey, $expire ); + $this->mMemc->set( $this->getOptionsKey( $page ), $optionsKey, $expire ); } else { wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" ); } diff --git a/includes/parser/ParserDiffTest.php b/includes/parser/ParserDiffTest.php new file mode 100644 index 00000000..174c1d61 --- /dev/null +++ b/includes/parser/ParserDiffTest.php @@ -0,0 +1,143 @@ +conf = $conf; + } + + public function init() { + if ( !is_null( $this->parsers ) ) { + return; + } + + global $wgHooks; + static $doneHook = false; + if ( !$doneHook ) { + $doneHook = true; + $wgHooks['ParserClearState'][] = array( $this, 'onClearState' ); + } + if ( isset( $this->conf['shortOutput'] ) ) { + $this->shortOutput = $this->conf['shortOutput']; + } + + foreach ( $this->conf['parsers'] as $i => $parserConf ) { + if ( !is_array( $parserConf ) ) { + $class = $parserConf; + $parserConf = array( 'class' => $parserConf ); + } else { + $class = $parserConf['class']; + } + $this->parsers[$i] = new $class( $parserConf ); + } + } + + public function __call( $name, $args ) { + $this->init(); + $results = array(); + $mismatch = false; + $lastResult = null; + $first = true; + foreach ( $this->parsers as $i => $parser ) { + $currentResult = call_user_func_array( array( &$this->parsers[$i], $name ), $args ); + if ( $first ) { + $first = false; + } else { + if ( is_object( $lastResult ) ) { + if ( $lastResult != $currentResult ) { + $mismatch = true; + } + } else { + if ( $lastResult !== $currentResult ) { + $mismatch = true; + } + } + } + $results[$i] = $currentResult; + $lastResult = $currentResult; + } + if ( $mismatch ) { + if ( count( $results ) == 2 ) { + $resultsList = array(); + foreach ( $this->parsers as $i => $parser ) { + $resultsList[] = var_export( $results[$i], true ); + } + $diff = wfDiff( $resultsList[0], $resultsList[1] ); + } else { + $diff = '[too many parsers]'; + } + $msg = "ParserDiffTest: results mismatch on call to $name\n"; + if ( !$this->shortOutput ) { + $msg .= 'Arguments: ' . $this->formatArray( $args ) . "\n"; + } + $msg .= 'Results: ' . $this->formatArray( $results ) . "\n" . + "Diff: $diff\n"; + throw new MWException( $msg ); + } + return $lastResult; + } + + public function formatArray( $array ) { + if ( $this->shortOutput ) { + foreach ( $array as $key => $value ) { + if ( $value instanceof ParserOutput ) { + $array[$key] = "ParserOutput: {$value->getText()}"; + } + } + } + return var_export( $array, true ); + } + + public function setFunctionHook( $id, $callback, $flags = 0 ) { + $this->init(); + foreach ( $this->parsers as $parser ) { + $parser->setFunctionHook( $id, $callback, $flags ); + } + } + + /** + * @param Parser $parser + * @return bool + */ + public function onClearState( &$parser ) { + // hack marker prefixes to get identical output + if ( !isset( $this->dtUniqPrefix ) ) { + $this->dtUniqPrefix = $parser->uniqPrefix(); + } else { + $parser->mUniqPrefix = $this->dtUniqPrefix; + } + return true; + } +} diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php index e12f32d8..7e4059b8 100644 --- a/includes/parser/ParserOptions.php +++ b/includes/parser/ParserOptions.php @@ -22,9 +22,10 @@ */ /** - * \brief Set options of the Parser + * @brief Set options of the Parser * - * All member variables are supposed to be private in theory, although in practise this is not the case. + * All member variables are supposed to be private in theory, although in + * practise this is not the case. * * @ingroup Parser */ @@ -33,108 +34,108 @@ class ParserOptions { /** * Interlanguage links are removed and returned in an array */ - var $mInterwikiMagic; + public $mInterwikiMagic; /** * Allow external images inline? */ - var $mAllowExternalImages; + public $mAllowExternalImages; /** * If not, any exception? */ - var $mAllowExternalImagesFrom; + public $mAllowExternalImagesFrom; /** * If not or it doesn't match, should we check an on-wiki whitelist? */ - var $mEnableImageWhitelist; + public $mEnableImageWhitelist; /** * Date format index */ - var $mDateFormat = null; + public $mDateFormat = null; /** * Create "edit section" links? */ - var $mEditSection = true; + public $mEditSection = true; /** * Allow inclusion of special pages? */ - var $mAllowSpecialInclusion; + public $mAllowSpecialInclusion; /** * Use tidy to cleanup output HTML? */ - var $mTidy = false; + public $mTidy = false; /** * Which lang to call for PLURAL and GRAMMAR */ - var $mInterfaceMessage = false; + public $mInterfaceMessage = false; /** * Overrides $mInterfaceMessage with arbitrary language */ - var $mTargetLanguage = null; + public $mTargetLanguage = null; /** * Maximum size of template expansions, in bytes */ - var $mMaxIncludeSize; + public $mMaxIncludeSize; /** * Maximum number of nodes touched by PPFrame::expand() */ - var $mMaxPPNodeCount; + public $mMaxPPNodeCount; /** * Maximum number of nodes generated by Preprocessor::preprocessToObj() */ - var $mMaxGeneratedPPNodeCount; + public $mMaxGeneratedPPNodeCount; /** * Maximum recursion depth in PPFrame::expand() */ - var $mMaxPPExpandDepth; + public $mMaxPPExpandDepth; /** * Maximum recursion depth for templates within templates */ - var $mMaxTemplateDepth; + public $mMaxTemplateDepth; /** * Maximum number of calls per parse to expensive parser functions */ - var $mExpensiveParserFunctionLimit; + public $mExpensiveParserFunctionLimit; /** * Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS */ - var $mRemoveComments = true; + public $mRemoveComments = true; /** * Callback for template fetching. Used as first argument to call_user_func(). */ - var $mTemplateCallback = + public $mTemplateCallback = array( 'Parser', 'statelessFetchTemplate' ); /** * Enable limit report in an HTML comment on output */ - var $mEnableLimitReport = false; + public $mEnableLimitReport = false; /** * Timestamp used for {{CURRENTDAY}} etc. */ - var $mTimestamp; + public $mTimestamp; /** * Target attribute for external links */ - var $mExternalLinkTarget; + public $mExternalLinkTarget; /** * Clean up signature texts? @@ -142,37 +143,32 @@ class ParserOptions { * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures * 2) Substitute all transclusions */ - var $mCleanSignatures; + public $mCleanSignatures; /** * Transform wiki markup when saving the page? */ - var $mPreSaveTransform = true; + public $mPreSaveTransform = true; /** * Whether content conversion should be disabled */ - var $mDisableContentConversion; + public $mDisableContentConversion; /** * Whether title conversion should be disabled */ - var $mDisableTitleConversion; + public $mDisableTitleConversion; /** * Automatically number headings? */ - var $mNumberHeadings; - - /** - * User math preference (as integer). Not used (1.19) - */ - var $mMath; + public $mNumberHeadings; /** * Thumb size preferred by the user. */ - var $mThumbSize; + public $mThumbSize; /** * Maximum article size of an article to be marked as "stub" @@ -182,90 +178,176 @@ class ParserOptions { /** * Language object of the User language. */ - var $mUserLang; + public $mUserLang; /** * @var User * Stored user object */ - var $mUser; + public $mUser; /** * Parsing the page for a "preview" operation? */ - var $mIsPreview = false; + public $mIsPreview = false; /** * Parsing the page for a "preview" operation on a single section? */ - var $mIsSectionPreview = false; + public $mIsSectionPreview = false; /** * Parsing the printable version of the page? */ - var $mIsPrintable = false; + public $mIsPrintable = false; /** * Extra key that should be present in the caching key. */ - var $mExtraKey = ''; + public $mExtraKey = ''; /** * Function to be called when an option is accessed. */ protected $onAccessCallback = null; - function getInterwikiMagic() { return $this->mInterwikiMagic; } - function getAllowExternalImages() { return $this->mAllowExternalImages; } - function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; } - function getEnableImageWhitelist() { return $this->mEnableImageWhitelist; } - function getEditSection() { return $this->mEditSection; } - function getNumberHeadings() { $this->optionUsed( 'numberheadings' ); - return $this->mNumberHeadings; } - function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; } - function getTidy() { return $this->mTidy; } - function getInterfaceMessage() { return $this->mInterfaceMessage; } - function getTargetLanguage() { return $this->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; } + /** + * If the page being parsed is a redirect, this should hold the redirect + * target. + * @var Title|null + */ + private $redirectTarget = null; + + public function getInterwikiMagic() { + return $this->mInterwikiMagic; + } + + public function getAllowExternalImages() { + return $this->mAllowExternalImages; + } + + public function getAllowExternalImagesFrom() { + return $this->mAllowExternalImagesFrom; + } + + public function getEnableImageWhitelist() { + return $this->mEnableImageWhitelist; + } + + public function getEditSection() { + return $this->mEditSection; + } + + public function getNumberHeadings() { + $this->optionUsed( 'numberheadings' ); + + return $this->mNumberHeadings; + } + + public function getAllowSpecialInclusion() { + return $this->mAllowSpecialInclusion; + } + + public function getTidy() { + return $this->mTidy; + } + + public function getInterfaceMessage() { + return $this->mInterfaceMessage; + } + + public function getTargetLanguage() { + return $this->mTargetLanguage; + } + + public function getMaxIncludeSize() { + return $this->mMaxIncludeSize; + } + + public function getMaxPPNodeCount() { + return $this->mMaxPPNodeCount; + } + + public function getMaxGeneratedPPNodeCount() { + return $this->mMaxGeneratedPPNodeCount; + } + + public function getMaxPPExpandDepth() { + return $this->mMaxPPExpandDepth; + } + + public 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; } - /** @deprecated since 1.22 use User::getOption('math') instead */ - function getMath() { $this->optionUsed( 'math' ); - return $this->mMath; } - function getThumbSize() { $this->optionUsed( 'thumbsize' ); - return $this->mThumbSize; } - function getStubThreshold() { $this->optionUsed( 'stubthreshold' ); - return $this->mStubThreshold; } - - function getIsPreview() { return $this->mIsPreview; } - function getIsSectionPreview() { return $this->mIsSectionPreview; } - function getIsPrintable() { $this->optionUsed( 'printable' ); - return $this->mIsPrintable; } - function getUser() { return $this->mUser; } - function getPreSaveTransform() { return $this->mPreSaveTransform; } - - /** - * @param $title Title - * @return Skin - * @deprecated since 1.18 Use Linker::* instead - */ - function getSkin( $title = null ) { - wfDeprecated( __METHOD__, '1.18' ); - return new DummyLinker; - } - - function getDateFormat() { + public function getExpensiveParserFunctionLimit() { + return $this->mExpensiveParserFunctionLimit; + } + + public function getRemoveComments() { + return $this->mRemoveComments; + } + + public function getTemplateCallback() { + return $this->mTemplateCallback; + } + + public function getEnableLimitReport() { + return $this->mEnableLimitReport; + } + + public function getCleanSignatures() { + return $this->mCleanSignatures; + } + + public function getExternalLinkTarget() { + return $this->mExternalLinkTarget; + } + + public function getDisableContentConversion() { + return $this->mDisableContentConversion; + } + + public function getDisableTitleConversion() { + return $this->mDisableTitleConversion; + } + + public function getThumbSize() { + $this->optionUsed( 'thumbsize' ); + + return $this->mThumbSize; + } + + public function getStubThreshold() { + $this->optionUsed( 'stubthreshold' ); + + return $this->mStubThreshold; + } + + public function getIsPreview() { + return $this->mIsPreview; + } + + public function getIsSectionPreview() { + return $this->mIsSectionPreview; + } + + public function getIsPrintable() { + $this->optionUsed( 'printable' ); + + return $this->mIsPrintable; + } + + public function getUser() { + return $this->mUser; + } + + public function getPreSaveTransform() { + return $this->mPreSaveTransform; + } + + public function getDateFormat() { $this->optionUsed( 'dateformat' ); if ( !isset( $this->mDateFormat ) ) { $this->mDateFormat = $this->mUser->getDatePreference(); @@ -273,7 +355,7 @@ class ParserOptions { return $this->mDateFormat; } - function getTimestamp() { + public function getTimestamp() { if ( !isset( $this->mTimestamp ) ) { $this->mTimestamp = wfTimestampNow(); } @@ -293,10 +375,10 @@ class ParserOptions { * * {{int: }} uses this which used to produce inconsistent link tables (bug 14404). * - * @return Language object + * @return Language * @since 1.19 */ - function getUserLangObj() { + public function getUserLangObj() { $this->optionUsed( 'userlang' ); return $this->mUserLang; } @@ -304,70 +386,180 @@ class ParserOptions { /** * Same as getUserLangObj() but returns a string instead. * - * @return String Language code + * @return string Language code * @since 1.17 */ - function getUserLang() { + public function getUserLang() { return $this->getUserLangObj()->getCode(); } - function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); } - function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); } - function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); } - function setEnableImageWhitelist( $x ) { return wfSetVar( $this->mEnableImageWhitelist, $x ); } - function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); } - function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); } - function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); } - function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); } - function setTidy( $x ) { return wfSetVar( $this->mTidy, $x ); } - - /** @deprecated in 1.19 */ - function setSkin( $x ) { wfDeprecated( __METHOD__, '1.19' ); } - function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x ); } - 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 ); } + public function setInterwikiMagic( $x ) { + return wfSetVar( $this->mInterwikiMagic, $x ); + } + + public function setAllowExternalImages( $x ) { + return wfSetVar( $this->mAllowExternalImages, $x ); + } + + public function setAllowExternalImagesFrom( $x ) { + return wfSetVar( $this->mAllowExternalImagesFrom, $x ); + } + + public function setEnableImageWhitelist( $x ) { + return wfSetVar( $this->mEnableImageWhitelist, $x ); + } + + public function setDateFormat( $x ) { + return wfSetVar( $this->mDateFormat, $x ); + } + + public function setEditSection( $x ) { + return wfSetVar( $this->mEditSection, $x ); + } + + public function setNumberHeadings( $x ) { + return wfSetVar( $this->mNumberHeadings, $x ); + } + + public function setAllowSpecialInclusion( $x ) { + return wfSetVar( $this->mAllowSpecialInclusion, $x ); + } + + public function setTidy( $x ) { + return wfSetVar( $this->mTidy, $x ); + } + + public function setInterfaceMessage( $x ) { + return wfSetVar( $this->mInterfaceMessage, $x ); + } + + public function setTargetLanguage( $x ) { + return wfSetVar( $this->mTargetLanguage, $x, true ); + } + + public function setMaxIncludeSize( $x ) { + return wfSetVar( $this->mMaxIncludeSize, $x ); + } + + public function setMaxPPNodeCount( $x ) { + return wfSetVar( $this->mMaxPPNodeCount, $x ); + } + + public function setMaxGeneratedPPNodeCount( $x ) { + return wfSetVar( $this->mMaxGeneratedPPNodeCount, $x ); + } + + public 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 ); } - /** @deprecated since 1.22 */ - function setMath( $x ) { return wfSetVar( $this->mMath, $x ); } - function setUserLang( $x ) { + public function setExpensiveParserFunctionLimit( $x ) { + return wfSetVar( $this->mExpensiveParserFunctionLimit, $x ); + } + + public function setRemoveComments( $x ) { + return wfSetVar( $this->mRemoveComments, $x ); + } + + public function setTemplateCallback( $x ) { + return wfSetVar( $this->mTemplateCallback, $x ); + } + + public function enableLimitReport( $x = true ) { + return wfSetVar( $this->mEnableLimitReport, $x ); + } + + public function setTimestamp( $x ) { + return wfSetVar( $this->mTimestamp, $x ); + } + + public function setCleanSignatures( $x ) { + return wfSetVar( $this->mCleanSignatures, $x ); + } + + public function setExternalLinkTarget( $x ) { + return wfSetVar( $this->mExternalLinkTarget, $x ); + } + + public function disableContentConversion( $x = true ) { + return wfSetVar( $this->mDisableContentConversion, $x ); + } + + public function disableTitleConversion( $x = true ) { + return wfSetVar( $this->mDisableTitleConversion, $x ); + } + + public function setUserLang( $x ) { if ( is_string( $x ) ) { $x = Language::factory( $x ); } + return wfSetVar( $this->mUserLang, $x ); } - function setThumbSize( $x ) { return wfSetVar( $this->mThumbSize, $x ); } - function setStubThreshold( $x ) { return wfSetVar( $this->mStubThreshold, $x ); } - function setPreSaveTransform( $x ) { return wfSetVar( $this->mPreSaveTransform, $x ); } - function setIsPreview( $x ) { return wfSetVar( $this->mIsPreview, $x ); } - function setIsSectionPreview( $x ) { return wfSetVar( $this->mIsSectionPreview, $x ); } - function setIsPrintable( $x ) { return wfSetVar( $this->mIsPrintable, $x ); } + public function setThumbSize( $x ) { + return wfSetVar( $this->mThumbSize, $x ); + } + + public function setStubThreshold( $x ) { + return wfSetVar( $this->mStubThreshold, $x ); + } + + public function setPreSaveTransform( $x ) { + return wfSetVar( $this->mPreSaveTransform, $x ); + } + + public function setIsPreview( $x ) { + return wfSetVar( $this->mIsPreview, $x ); + } + + public function setIsSectionPreview( $x ) { + return wfSetVar( $this->mIsSectionPreview, $x ); + } + + public function setIsPrintable( $x ) { + return wfSetVar( $this->mIsPrintable, $x ); + } + + /** + * Set the redirect target. + * + * Note that setting or changing this does not *make* the page a redirect + * or change its target, it merely records the information for reference + * during the parse. + * + * @since 1.24 + * @param Title|null $title + */ + function setRedirectTarget( $title ) { + $this->redirectTarget = $title; + } + + /** + * Get the previously-set redirect target. + * + * @since 1.24 + * @return Title|null + */ + function getRedirectTarget() { + return $this->redirectTarget; + } /** * Extra key that should be present in the parser cache key. + * @param string $key */ - function addExtraKey( $key ) { + public function addExtraKey( $key ) { $this->mExtraKey .= '!' . $key; } /** * Constructor - * @param $user User object - * @param $lang Language object + * @param User $user + * @param Language $lang */ - function __construct( $user = null, $lang = null ) { + public function __construct( $user = null, $lang = null ) { if ( $user === null ) { global $wgUser; if ( $wgUser === null ) { @@ -390,8 +582,8 @@ class ParserOptions { * Get a ParserOptions object from a given user. * Language will be taken from $wgLang. * - * @param $user User object - * @return ParserOptions object + * @param User $user + * @return ParserOptions */ public static function newFromUser( $user ) { return new ParserOptions( $user ); @@ -400,9 +592,9 @@ class ParserOptions { /** * Get a ParserOptions object from a given user and language * - * @param $user User object - * @param $lang Language object - * @return ParserOptions object + * @param User $user + * @param Language $lang + * @return ParserOptions */ public static function newFromUserAndLang( User $user, Language $lang ) { return new ParserOptions( $user, $lang ); @@ -411,8 +603,8 @@ class ParserOptions { /** * Get a ParserOptions object from a IContextSource object * - * @param $context IContextSource object - * @return ParserOptions object + * @param IContextSource $context + * @return ParserOptions */ public static function newFromContext( IContextSource $context ) { return new ParserOptions( $context->getUser(), $context->getLanguage() ); @@ -421,8 +613,8 @@ class ParserOptions { /** * Get user options * - * @param $user User object - * @param $lang Language object + * @param User $user + * @param Language $lang */ private function initialiseFromUser( $user, $lang ) { global $wgInterwikiMagic, $wgAllowExternalImages, @@ -451,7 +643,6 @@ class ParserOptions { $this->mUser = $user; $this->mNumberHeadings = $user->getOption( 'numberheadings' ); - $this->mMath = $user->getOption( 'math' ); $this->mThumbSize = $user->getOption( 'thumbsize' ); $this->mStubThreshold = $user->getStubThreshold(); $this->mUserLang = $lang; @@ -462,15 +653,17 @@ class ParserOptions { /** * Registers a callback for tracking which ParserOptions which are used. * This is a private API with the parser. + * @param callable $callback */ - function registerWatcher( $callback ) { + public function registerWatcher( $callback ) { $this->onAccessCallback = $callback; } /** * Called when an option is accessed. + * @param string $optionName Name of the option */ - protected function optionUsed( $optionName ) { + public function optionUsed( $optionName ) { if ( $this->onAccessCallback ) { call_user_func( $this->onAccessCallback, $optionName ); } @@ -483,37 +676,39 @@ class ParserOptions { * @return array */ public static function legacyOptions() { - return array( 'math', 'stubthreshold', 'numberheadings', 'userlang', 'thumbsize', 'editsection', 'printable' ); + return array( + 'stubthreshold', + 'numberheadings', + 'userlang', + 'thumbsize', + 'editsection', + 'printable' + ); } /** * Generate a hash string with the values set on these ParserOptions * for the keys given in the array. * This will be used as part of the hash key for the parser cache, - * so users sharign the options with vary for the same page share + * so users sharing the options with vary for the same page share * the same cached data safely. * - * Replaces User::getPageRenderingHash() - * * Extensions which require it should install 'PageRenderingHash' hook, * which will give them a chance to modify this key based on their own * settings. * * @since 1.17 - * @param $forOptions Array - * @param $title Title: used to get the content language of the page (since r97636) + * @param array $forOptions + * @param Title $title Used to get the content language of the page (since r97636) * @return string Page rendering hash */ public function optionsHash( $forOptions, $title = null ) { global $wgRenderHashAppend; - $confstr = ''; - - if ( in_array( 'math', $forOptions ) ) { - $confstr .= $this->mMath; - } else { - $confstr .= '*'; - } + // FIXME: Once the cache key is reorganized this argument + // can be dropped. It was used when the math extension was + // part of core. + $confstr = '*'; // Space assigned for the stubthreshold but unused // since it disables the parser cache, its value will always @@ -573,7 +768,7 @@ class ParserOptions { // Give a chance for extensions to modify the hash, if they have // extra options or other effects on the parser cache. - wfRunHooks( 'PageRenderingHash', array( &$confstr ) ); + wfRunHooks( 'PageRenderingHash', array( &$confstr, $this->getUser(), &$forOptions ) ); // Make it a valid memcached key fragment $confstr = str_replace( ' ', '_', $confstr ); diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php index 460f3211..5037ce18 100644 --- a/includes/parser/ParserOutput.php +++ b/includes/parser/ParserOutput.php @@ -22,7 +22,7 @@ * @ingroup Parser */ class ParserOutput extends CacheTime { - var $mText, # The output text + public $mText, # The output text $mLanguageLinks, # List of the full text of language links, in the order they appear $mCategories, # Map of category names to sort keys $mTitleText, # title text of the chosen language variant @@ -41,6 +41,7 @@ class ParserOutput extends CacheTime { $mModuleScripts = array(), # Modules of which only the JS will be loaded by the resource loader $mModuleStyles = array(), # Modules of which only the CSSS will be loaded by the resource loader $mModuleMessages = array(), # Modules of which only the messages will be loaded by the resource loader + $mJsConfigVars = array(), # JavaScript config variable for mw.config combined with this page $mOutputHooks = array(), # Hook tags as per $wgParserOutputHooks $mWarnings = array(), # Warning text to be returned to the user. Wikitext formatted, in the key only $mSections = array(), # Table of contents @@ -49,19 +50,20 @@ class ParserOutput extends CacheTime { $mTOCHTML = '', # HTML of the TOC $mTimestamp, # Timestamp of the revision $mTOCEnabled = true; # Whether TOC should be shown, can't override __NOTOC__ - 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 DataUpdate, used to save info from the page somewhere else. - private $mExtensionData = array(); # extra data used by extensions - private $mLimitReportData = array(); # Parser limit report data - private $mParseStartTime = array(); # Timestamps for getTimeSinceStart() - private $mPreventClickjacking = false; # Whether to emit X-Frame-Options: DENY - - const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)())#'; - - function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(), - $containsOldMagic = false, $titletext = '' ) - { + 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 DataUpdate, used to save info from the page somewhere else. + private $mExtensionData = array(); # extra data used by extensions + private $mLimitReportData = array(); # Parser limit report data + private $mParseStartTime = array(); # Timestamps for getTimeSinceStart() + private $mPreventClickjacking = false; # Whether to emit X-Frame-Options: DENY + + const EDITSECTION_REGEX = + '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)())#'; + + public function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(), + $containsOldMagic = false, $titletext = '' + ) { $this->mText = $text; $this->mLanguageLinks = $languageLinks; $this->mCategories = $categoryLinks; @@ -69,12 +71,31 @@ class ParserOutput extends CacheTime { $this->mTitleText = $titletext; } - function getText() { + public function getText() { wfProfileIn( __METHOD__ ); $text = $this->mText; if ( $this->mEditSectionTokens ) { - $text = preg_replace_callback( ParserOutput::EDITSECTION_REGEX, - array( &$this, 'replaceEditSectionLinksCallback' ), $text ); + $text = preg_replace_callback( + ParserOutput::EDITSECTION_REGEX, + function ( $m ) { + global $wgOut, $wgLang; + $editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) ); + $editsectionSection = htmlspecialchars_decode( $m[2] ); + $editsectionContent = isset( $m[4] ) ? $m[3] : null; + + if ( !is_object( $editsectionPage ) ) { + throw new MWException( "Bad parser output text." ); + } + + $skin = $wgOut->getSkin(); + return call_user_func_array( + array( $skin, 'doEditSectionLink' ), + array( $editsectionPage, $editsectionSection, + $editsectionContent, $wgLang->getCode() ) + ); + }, + $text + ); } else { $text = preg_replace( ParserOutput::EDITSECTION_REGEX, '', $text ); } @@ -84,7 +105,7 @@ class ParserOutput extends CacheTime { $text = str_replace( array( Parser::TOC_START, Parser::TOC_END ), '', $text ); } else { $text = preg_replace( - '#'. preg_quote( Parser::TOC_START ) . '.*?' . preg_quote( Parser::TOC_END ) . '#s', + '#' . preg_quote( Parser::TOC_START ) . '.*?' . preg_quote( Parser::TOC_END ) . '#s', '', $text ); @@ -93,97 +114,192 @@ class ParserOutput extends CacheTime { return $text; } - /** - * callback used by getText to replace editsection tokens - * @private - * @param $m - * @throws MWException - * @return mixed - */ - function replaceEditSectionLinksCallback( $m ) { - global $wgOut, $wgLang; - $args = array( - htmlspecialchars_decode( $m[1] ), - htmlspecialchars_decode( $m[2] ), - isset( $m[4] ) ? $m[3] : null, - ); - $args[0] = Title::newFromText( $args[0] ); - if ( !is_object( $args[0] ) ) { - throw new MWException( "Bad parser output text." ); - } - $args[] = $wgLang->getCode(); - $skin = $wgOut->getSkin(); - return call_user_func_array( array( $skin, 'doEditSectionLink' ), $args ); - } - - function &getLanguageLinks() { return $this->mLanguageLinks; } - function getInterwikiLinks() { return $this->mInterwikiLinks; } - function getCategoryLinks() { return array_keys( $this->mCategories ); } - function &getCategories() { return $this->mCategories; } - function getTitleText() { return $this->mTitleText; } - function getSections() { return $this->mSections; } - function getEditSectionTokens() { return $this->mEditSectionTokens; } - function &getLinks() { return $this->mLinks; } - function &getTemplates() { return $this->mTemplates; } - function &getTemplateIds() { return $this->mTemplateIds; } - function &getImages() { return $this->mImages; } - function &getFileSearchOptions() { return $this->mFileSearchOptions; } - function &getExternalLinks() { return $this->mExternalLinks; } - function getNoGallery() { return $this->mNoGallery; } - function getHeadItems() { return $this->mHeadItems; } - function getModules() { return $this->mModules; } - function getModuleScripts() { return $this->mModuleScripts; } - function getModuleStyles() { return $this->mModuleStyles; } - function getModuleMessages() { return $this->mModuleMessages; } - function getOutputHooks() { return (array)$this->mOutputHooks; } - function getWarnings() { return array_keys( $this->mWarnings ); } - function getIndexPolicy() { return $this->mIndexPolicy; } - function getTOCHTML() { return $this->mTOCHTML; } - function getTimestamp() { return $this->mTimestamp; } - function getLimitReportData() { return $this->mLimitReportData; } - function getTOCEnabled() { return $this->mTOCEnabled; } - - function setText( $text ) { return wfSetVar( $this->mText, $text ); } - function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); } - function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); } - - function setTitleText( $t ) { return wfSetVar( $this->mTitleText, $t ); } - function setSections( $toc ) { return wfSetVar( $this->mSections, $toc ); } - function setEditSectionTokens( $t ) { return wfSetVar( $this->mEditSectionTokens, $t ); } - function setIndexPolicy( $policy ) { return wfSetVar( $this->mIndexPolicy, $policy ); } - function setTOCHTML( $tochtml ) { return wfSetVar( $this->mTOCHTML, $tochtml ); } - function setTimestamp( $timestamp ) { return wfSetVar( $this->mTimestamp, $timestamp ); } - function setTOCEnabled( $flag ) { return wfSetVar( $this->mTOCEnabled, $flag ); } - - function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; } - function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; } - function addWarning( $s ) { $this->mWarnings[$s] = 1; } - - function addOutputHook( $hook, $data = false ) { + public function &getLanguageLinks() { + return $this->mLanguageLinks; + } + + public function getInterwikiLinks() { + return $this->mInterwikiLinks; + } + + public function getCategoryLinks() { + return array_keys( $this->mCategories ); + } + + public function &getCategories() { + return $this->mCategories; + } + + public function getTitleText() { + return $this->mTitleText; + } + + public function getSections() { + return $this->mSections; + } + + public function getEditSectionTokens() { + return $this->mEditSectionTokens; + } + + public function &getLinks() { + return $this->mLinks; + } + + public function &getTemplates() { + return $this->mTemplates; + } + + public function &getTemplateIds() { + return $this->mTemplateIds; + } + + public function &getImages() { + return $this->mImages; + } + + public function &getFileSearchOptions() { + return $this->mFileSearchOptions; + } + + public function &getExternalLinks() { + return $this->mExternalLinks; + } + + public function getNoGallery() { + return $this->mNoGallery; + } + + public function getHeadItems() { + return $this->mHeadItems; + } + + public function getModules() { + return $this->mModules; + } + + public function getModuleScripts() { + return $this->mModuleScripts; + } + + public function getModuleStyles() { + return $this->mModuleStyles; + } + + public function getModuleMessages() { + return $this->mModuleMessages; + } + + /** @since 1.23 */ + public function getJsConfigVars() { + return $this->mJsConfigVars; + } + + public function getOutputHooks() { + return (array)$this->mOutputHooks; + } + + public function getWarnings() { + return array_keys( $this->mWarnings ); + } + + public function getIndexPolicy() { + return $this->mIndexPolicy; + } + + public function getTOCHTML() { + return $this->mTOCHTML; + } + + public function getTimestamp() { + return $this->mTimestamp; + } + + public function getLimitReportData() { + return $this->mLimitReportData; + } + + public function getTOCEnabled() { + return $this->mTOCEnabled; + } + + public function setText( $text ) { + return wfSetVar( $this->mText, $text ); + } + + public function setLanguageLinks( $ll ) { + return wfSetVar( $this->mLanguageLinks, $ll ); + } + + public function setCategoryLinks( $cl ) { + return wfSetVar( $this->mCategories, $cl ); + } + + public function setTitleText( $t ) { + return wfSetVar( $this->mTitleText, $t ); + } + + public function setSections( $toc ) { + return wfSetVar( $this->mSections, $toc ); + } + + public function setEditSectionTokens( $t ) { + return wfSetVar( $this->mEditSectionTokens, $t ); + } + + public function setIndexPolicy( $policy ) { + return wfSetVar( $this->mIndexPolicy, $policy ); + } + + public function setTOCHTML( $tochtml ) { + return wfSetVar( $this->mTOCHTML, $tochtml ); + } + + public function setTimestamp( $timestamp ) { + return wfSetVar( $this->mTimestamp, $timestamp ); + } + + public function setTOCEnabled( $flag ) { + return wfSetVar( $this->mTOCEnabled, $flag ); + } + + public function addCategory( $c, $sort ) { + $this->mCategories[$c] = $sort; + } + + public function addLanguageLink( $t ) { + $this->mLanguageLinks[] = $t; + } + + public function addWarning( $s ) { + $this->mWarnings[$s] = 1; + } + + public function addOutputHook( $hook, $data = false ) { $this->mOutputHooks[] = array( $hook, $data ); } - function setNewSection( $value ) { + public function setNewSection( $value ) { $this->mNewSection = (bool)$value; } - function hideNewSection( $value ) { + public function hideNewSection( $value ) { $this->mHideNewSection = (bool)$value; } - function getHideNewSection() { + public function getHideNewSection() { return (bool)$this->mHideNewSection; } - function getNewSection() { + public function getNewSection() { return (bool)$this->mNewSection; } /** * Checks, if a url is pointing to the own server * - * @param string $internal the server to check against - * @param string $url the url to check + * @param string $internal The server to check against + * @param string $url The url to check * @return bool */ - static function isLinkInternal( $internal, $url ) { + public static function isLinkInternal( $internal, $url ) { return (bool)preg_match( '/^' . # If server is proto relative, check also for http/https links ( substr( $internal, 0, 2 ) === '//' ? '(?:https?:)?' : '' ) . @@ -194,7 +310,7 @@ class ParserOutput extends CacheTime { ); } - function addExternalLink( $url ) { + public function addExternalLink( $url ) { # We don't register links pointing to our own server, unless... :-) global $wgServer, $wgRegisterInternalExternals; @@ -210,10 +326,10 @@ class ParserOutput extends CacheTime { /** * Record a local or interwiki inline link for saving in future link tables. * - * @param $title Title object - * @param $id Mixed: optional known page_id so we can skip the lookup + * @param Title $title + * @param int|null $id Optional known page_id so we can skip the lookup */ - function addLink( Title $title, $id = null ) { + public function addLink( Title $title, $id = null ) { if ( $title->isExternal() ) { // Don't record interwikis in pagelinks $this->addInterwikiLink( $title ); @@ -245,10 +361,10 @@ class ParserOutput extends CacheTime { * Register a file dependency for this output * @param string $name Title dbKey * @param string $timestamp MW timestamp of file creation (or false if non-existing) - * @param string $sha1 base 36 SHA-1 of file (or false if non-existing) + * @param string $sha1 Base 36 SHA-1 of file (or false if non-existing) * @return void */ - function addImage( $name, $timestamp = null, $sha1 = null ) { + public function addImage( $name, $timestamp = null, $sha1 = null ) { $this->mImages[$name] = 1; if ( $timestamp !== null && $sha1 !== null ) { $this->mFileSearchOptions[$name] = array( 'time' => $timestamp, 'sha1' => $sha1 ); @@ -257,12 +373,12 @@ class ParserOutput extends CacheTime { /** * Register a template dependency for this output - * @param $title Title - * @param $page_id - * @param $rev_id + * @param Title $title + * @param int $page_id + * @param int $rev_id * @return void */ - function addTemplate( $title, $page_id, $rev_id ) { + public function addTemplate( $title, $page_id, $rev_id ) { $ns = $title->getNamespace(); $dbk = $title->getDBkey(); if ( !isset( $this->mTemplates[$ns] ) ) { @@ -276,14 +392,14 @@ class ParserOutput extends CacheTime { } /** - * @param $title Title object, must be an interwiki link - * @throws MWException if given invalid input + * @param Title $title Title object, must be an interwiki link + * @throws MWException If given invalid input */ - function addInterwikiLink( $title ) { - $prefix = $title->getInterwiki(); - if ( $prefix == '' ) { + public function addInterwikiLink( $title ) { + if ( !$title->isExternal() ) { throw new MWException( 'Non-interwiki link passed, internal parser error.' ); } + $prefix = $title->getInterwiki(); if ( !isset( $this->mInterwikiLinks[$prefix] ) ) { $this->mInterwikiLinks[$prefix] = array(); } @@ -294,8 +410,10 @@ class ParserOutput extends CacheTime { * Add some text to the "". * If $tag is set, the section with that tag will only be included once * in a given page. + * @param string $section + * @param string|bool $tag */ - function addHeadItem( $section, $tag = false ) { + public function addHeadItem( $section, $tag = false ) { if ( $tag !== false ) { $this->mHeadItems[$tag] = $section; } else { @@ -319,16 +437,35 @@ class ParserOutput extends CacheTime { $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules ); } + /** + * Add one or more variables to be set in mw.config in JavaScript. + * + * @param string|array $keys Key or array of key/value pairs. + * @param mixed $value [optional] Value of the configuration variable. + * @since 1.23 + */ + public function addJsConfigVars( $keys, $value = null ) { + if ( is_array( $keys ) ) { + foreach ( $keys as $key => $value ) { + $this->mJsConfigVars[$key] = $value; + } + return; + } + + $this->mJsConfigVars[$keys] = $value; + } + /** * Copy items from the OutputPage object into this one * - * @param $out OutputPage object + * @param OutputPage $out */ public function addOutputPageMetadata( OutputPage $out ) { $this->addModules( $out->getModules() ); $this->addModuleScripts( $out->getModuleScripts() ); $this->addModuleStyles( $out->getModuleStyles() ); $this->addModuleMessages( $out->getModuleMessages() ); + $this->addJsConfigVars( $out->getJsConfigVars() ); $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() ); $this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking(); @@ -339,7 +476,7 @@ class ParserOutput extends CacheTime { * -- this is assumed to have been validated * (check equal normalisation, etc.) * - * @param string $text desired title text + * @param string $text Desired title text */ public function setDisplayTitle( $text ) { $this->setTitleText( $text ); @@ -349,7 +486,7 @@ class ParserOutput extends CacheTime { /** * Get the title to be used for display * - * @return String + * @return string */ public function getDisplayTitle() { $t = $this->getTitleText(); @@ -361,6 +498,7 @@ class ParserOutput extends CacheTime { /** * Fairly generic flag setter thingy. + * @param string $flag */ public function setFlag( $flag ) { $this->mFlags[$flag] = true; @@ -378,6 +516,9 @@ class ParserOutput extends CacheTime { * retrieved given the page ID or via a DB join when given the page * title. * + * Since 1.23, page_props are also indexed by numeric value, to allow + * for efficient "top k" queries of pages wrt a given property. + * * setProperty() is thus used to propagate properties from the parsed * page to request contexts other than a page view of the currently parsed * article. @@ -395,10 +536,10 @@ class ParserOutput extends CacheTime { * Wikimedia Commons. * This is not actually implemented, yet but would be pretty cool. * - * @note: Do not use setProperty() to set a property which is only used + * @note Do not use setProperty() to set a property which is only used * in a context where the ParserOutput object itself is already available, * for example a normal page view. There is no need to save such a property - * in the database since it the text is already parsed. You can just hook + * in the database since the text is already parsed. You can just hook * OutputPageParserOutput and get your data out of the ParserOutput object. * * If you are writing an extension where you want to set a property in the @@ -431,10 +572,22 @@ class ParserOutput extends CacheTime { $this->mProperties[$name] = $value; } + /** + * @param string $name The property name to look up. + * + * @return mixed|bool The value previously set using setProperty(). False if null or no value + * was set for the given property name. + * + * @note You need to use getProperties() to check for boolean and null properties. + */ public function getProperty( $name ) { return isset( $this->mProperties[$name] ) ? $this->mProperties[$name] : false; } + public function unsetProperty( $name ) { + unset( $this->mProperties[$name] ); + } + public function getProperties() { if ( !isset( $this->mProperties ) ) { $this->mProperties = array(); @@ -445,7 +598,7 @@ class ParserOutput extends CacheTime { /** * Returns the options from its ParserOptions which have been taken * into account to produce this output or false if not available. - * @return mixed Array + * @return array */ public function getUsedOptions() { if ( !isset( $this->mAccessedOptions ) ) { @@ -455,16 +608,24 @@ class ParserOutput extends CacheTime { } /** - * Callback passed by the Parser to the ParserOptions to keep track of which options are used. - * @access private + * Tags a parser option for use in the cache key for this parser output. + * Registered as a watcher at ParserOptions::registerWatcher() by Parser::clearState(). + * + * @see ParserCache::getKey + * @see ParserCache::save + * @see ParserOptions::addExtraKey + * @see ParserOptions::optionsHash + * @param string $option */ - function recordOption( $option ) { + public 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. + * Adds an update job to the output. Any update jobs added to the output will + * eventually be executed in order to store any secondary information extracted + * from the page's content. This is triggered by calling getSecondaryDataUpdates() + * and is used for forward links updates on edit and backlink updates by jobs. * * @since 1.20 * @@ -479,16 +640,16 @@ class ParserOutput extends CacheTime { * extracted from the page's content, including a LinksUpdate object for all links stored in * this ParserOutput object. * - * @note: Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates() instead! The content - * handler may provide additional update objects. + * @note Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates() + * instead! The content handler may provide additional update objects. * * @since 1.20 * - * @param $title Title The 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? + * @param Title $title The title of the page we're updating. If not given, a title object will + * be created based on $this->getTitleText() + * @param bool $recursive Queue jobs for recursive updates? * - * @return Array. An array of instances of DataUpdate + * @return array An array of instances of DataUpdate */ public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) { if ( is_null( $title ) ) { @@ -535,11 +696,10 @@ class ParserOutput extends CacheTime { * @since 1.21 * * @param string $key The key for accessing the data. Extensions should take care to avoid - * conflicts in naming keys. It is suggested to use the extension's name as a - * prefix. + * conflicts in naming keys. It is suggested to use the extension's name as a prefix. * * @param mixed $value The value to set. Setting a value to null is equivalent to removing - * the value. + * the value. */ public function setExtensionData( $key, $value ) { if ( $value === null ) { @@ -557,7 +717,7 @@ class ParserOutput extends CacheTime { * * @param string $key The key to look up. * - * @return mixed The value previously set for the given key using setExtensionData( $key ), + * @return mixed|null The value previously set for the given key using setExtensionData() * or null if no value was set for this key. */ public function getExtensionData( $key ) { @@ -573,10 +733,12 @@ class ParserOutput extends CacheTime { if ( !$clock || $clock === 'wall' ) { $ret['wall'] = microtime( true ); } - if ( ( !$clock || $clock === 'cpu' ) && function_exists( 'getrusage' ) ) { - $ru = getrusage(); - $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6; - $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6; + if ( !$clock || $clock === 'cpu' ) { + $ru = wfGetRusage(); + if ( $ru ) { + $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6; + $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6; + } } return $ret; } @@ -585,7 +747,7 @@ class ParserOutput extends CacheTime { * Resets the parse start timestamps for future calls to getTimeSinceStart() * @since 1.22 */ - function resetParseStartTime() { + public function resetParseStartTime() { $this->mParseStartTime = self::getTimes(); } @@ -600,7 +762,7 @@ class ParserOutput extends CacheTime { * @param string $clock * @return float|null */ - function getTimeSinceStart( $clock ) { + public function getTimeSinceStart( $clock ) { if ( !isset( $this->mParseStartTime[$clock] ) ) { return null; } @@ -628,7 +790,7 @@ class ParserOutput extends CacheTime { * @param string $key Message key * @param mixed $value Appropriate for Message::params() */ - function setLimitReportData( $key, $value ) { + public function setLimitReportData( $key, $value ) { $this->mLimitReportData[$key] = $value; } @@ -636,10 +798,21 @@ class ParserOutput extends CacheTime { * Get or set the prevent-clickjacking flag * * @since 1.24 - * @param boolean|null $flag New flag value, or null to leave it unchanged - * @return boolean Old flag value + * @param bool|null $flag New flag value, or null to leave it unchanged + * @return bool Old flag value */ public function preventClickjacking( $flag = null ) { return wfSetVar( $this->mPreventClickjacking, $flag ); } + + /** + * Save space for for serialization by removing useless values + * @return array + */ + public function __sleep() { + return array_diff( + array_keys( get_object_vars( $this ) ), + array( 'mSecondaryDataUpdates', 'mParseStartTime' ) + ); + } } diff --git a/includes/parser/Parser_DiffTest.php b/includes/parser/Parser_DiffTest.php deleted file mode 100644 index aeae234a..00000000 --- a/includes/parser/Parser_DiffTest.php +++ /dev/null @@ -1,143 +0,0 @@ -conf = $conf; - } - - function init() { - if ( !is_null( $this->parsers ) ) { - return; - } - - global $wgHooks; - static $doneHook = false; - if ( !$doneHook ) { - $doneHook = true; - $wgHooks['ParserClearState'][] = array( $this, 'onClearState' ); - } - if ( isset( $this->conf['shortOutput'] ) ) { - $this->shortOutput = $this->conf['shortOutput']; - } - - foreach ( $this->conf['parsers'] as $i => $parserConf ) { - if ( !is_array( $parserConf ) ) { - $class = $parserConf; - $parserConf = array( 'class' => $parserConf ); - } else { - $class = $parserConf['class']; - } - $this->parsers[$i] = new $class( $parserConf ); - } - } - - function __call( $name, $args ) { - $this->init(); - $results = array(); - $mismatch = false; - $lastResult = null; - $first = true; - foreach ( $this->parsers as $i => $parser ) { - $currentResult = call_user_func_array( array( &$this->parsers[$i], $name ), $args ); - if ( $first ) { - $first = false; - } else { - if ( is_object( $lastResult ) ) { - if ( $lastResult != $currentResult ) { - $mismatch = true; - } - } else { - if ( $lastResult !== $currentResult ) { - $mismatch = true; - } - } - } - $results[$i] = $currentResult; - $lastResult = $currentResult; - } - if ( $mismatch ) { - if ( count( $results ) == 2 ) { - $resultsList = array(); - foreach ( $this->parsers as $i => $parser ) { - $resultsList[] = var_export( $results[$i], true ); - } - $diff = wfDiff( $resultsList[0], $resultsList[1] ); - } else { - $diff = '[too many parsers]'; - } - $msg = "Parser_DiffTest: results mismatch on call to $name\n"; - if ( !$this->shortOutput ) { - $msg .= 'Arguments: ' . $this->formatArray( $args ) . "\n"; - } - $msg .= 'Results: ' . $this->formatArray( $results ) . "\n" . - "Diff: $diff\n"; - throw new MWException( $msg ); - } - return $lastResult; - } - - function formatArray( $array ) { - if ( $this->shortOutput ) { - foreach ( $array as $key => $value ) { - if ( $value instanceof ParserOutput ) { - $array[$key] = "ParserOutput: {$value->getText()}"; - } - } - } - return var_export( $array, true ); - } - - function setFunctionHook( $id, $callback, $flags = 0 ) { - $this->init(); - foreach ( $this->parsers as $parser ) { - $parser->setFunctionHook( $id, $callback, $flags ); - } - } - - /** - * @param $parser Parser - * @return bool - */ - function onClearState( &$parser ) { - // hack marker prefixes to get identical output - if ( !isset( $this->dtUniqPrefix ) ) { - $this->dtUniqPrefix = $parser->uniqPrefix(); - } else { - $parser->mUniqPrefix = $this->dtUniqPrefix; - } - return true; - } -} diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php index aeacd2e1..b32593c9 100644 --- a/includes/parser/Preprocessor.php +++ b/includes/parser/Preprocessor.php @@ -28,42 +28,44 @@ interface Preprocessor { /** * Create a new preprocessor object based on an initialised Parser object * - * @param $parser Parser + * @param Parser $parser */ - function __construct( $parser ); + public function __construct( $parser ); /** * Create a new top-level frame for expansion of a page * * @return PPFrame */ - function newFrame(); + public function newFrame(); /** - * Create a new custom frame for programmatic use of parameter replacement as used in some extensions + * Create a new custom frame for programmatic use of parameter replacement + * as used in some extensions. * - * @param $args array + * @param array $args * * @return PPFrame */ - function newCustomFrame( $args ); + public function newCustomFrame( $args ); /** - * Create a new custom node for programmatic use of parameter replacement as used in some extensions + * Create a new custom node for programmatic use of parameter replacement + * as used in some extensions. * - * @param $values + * @param array $values */ - function newPartNodeArray( $values ); + public function newPartNodeArray( $values ); /** * Preprocess text to a PPNode * - * @param $text - * @param $flags + * @param string $text + * @param int $flags * * @return PPNode */ - function preprocessToObj( $text, $flags = 0 ); + public function preprocessToObj( $text, $flags = 0 ); } /** @@ -75,8 +77,9 @@ interface PPFrame { const STRIP_COMMENTS = 4; const NO_IGNORE = 8; const RECOVER_COMMENTS = 16; + const NO_TAGS = 32; - const RECOVER_ORIG = 27; // = 1|2|8|16 no constant expression support in PHP yet + const RECOVER_ORIG = 59; // = 1|2|8|16|32 no constant expression support in PHP yet /** This constant exists when $indexOffset is supported in newChild() */ const SUPPORTS_INDEX_OFFSET = 1; @@ -84,87 +87,168 @@ interface PPFrame { /** * Create a child frame * - * @param array $args - * @param Title $title + * @param array|bool $args + * @param bool|Title $title * @param int $indexOffset A number subtracted from the index attributes of the arguments * * @return PPFrame */ - function newChild( $args = false, $title = false, $indexOffset = 0 ); + public function newChild( $args = false, $title = false, $indexOffset = 0 ); + + /** + * Expand a document tree node, caching the result on its parent with the given key + * @param string|int $key + * @param string|PPNode $root + * @param int $flags + * @return string + */ + public function cachedExpand( $key, $root, $flags = 0 ); /** * Expand a document tree node + * @param string|PPNode $root + * @param int $flags + * @return string */ - function expand( $root, $flags = 0 ); + public function expand( $root, $flags = 0 ); /** * Implode with flags for expand() + * @param string $sep + * @param int $flags + * @param string|PPNode $args,... + * @return string */ - function implodeWithFlags( $sep, $flags /*, ... */ ); + public function implodeWithFlags( $sep, $flags /*, ... */ ); /** * Implode with no flags specified + * @param string $sep + * @param string|PPNode $args,... + * @return string */ - function implode( $sep /*, ... */ ); + public function implode( $sep /*, ... */ ); /** * Makes an object that, when expand()ed, will be the same as one obtained * with implode() + * @param string $sep + * @param string|PPNode $args,... + * @return PPNode */ - function virtualImplode( $sep /*, ... */ ); + public function virtualImplode( $sep /*, ... */ ); /** * Virtual implode with brackets + * @param string $start + * @param string $sep + * @param string $end + * @param string|PPNode $args,... + * @return PPNode */ - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ); + public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ); /** * Returns true if there are no arguments in this frame * * @return bool */ - function isEmpty(); + public function isEmpty(); /** * Returns all arguments of this frame + * @return array */ - function getArguments(); + public function getArguments(); /** * Returns all numbered arguments of this frame + * @return array */ - function getNumberedArguments(); + public function getNumberedArguments(); /** * Returns all named arguments of this frame + * @return array */ - function getNamedArguments(); + public function getNamedArguments(); /** * Get an argument to this frame by name + * @param string $name + * @return bool */ - function getArgument( $name ); + public function getArgument( $name ); /** * Returns true if the infinite loop check is OK, false if a loop is detected * - * @param $title - * + * @param Title $title * @return bool */ - function loopCheck( $title ); + public function loopCheck( $title ); /** * Return true if the frame is a template frame + * @return bool */ - function isTemplate(); + public function isTemplate(); + + /** + * Set the "volatile" flag. + * + * Note that this is somewhat of a "hack" in order to make extensions + * with side effects (such as Cite) work with the PHP parser. New + * extensions should be written in a way that they do not need this + * function, because other parsers (such as Parsoid) are not guaranteed + * to respect it, and it may be removed in the future. + * + * @param bool $flag + */ + public function setVolatile( $flag = true ); + + /** + * Get the "volatile" flag. + * + * Callers should avoid caching the result of an expansion if it has the + * volatile flag set. + * + * @see self::setVolatile() + * @return bool + */ + public function isVolatile(); + + /** + * Get the TTL of the frame's output. + * + * This is the maximum amount of time, in seconds, that this frame's + * output should be cached for. A value of null indicates that no + * maximum has been specified. + * + * Note that this TTL only applies to caching frames as parts of pages. + * It is not relevant to caching the entire rendered output of a page. + * + * @return int|null + */ + public function getTTL(); + + /** + * Set the TTL of the output of this frame and all of its ancestors. + * Has no effect if the new TTL is greater than the one already set. + * Note that it is the caller's responsibility to change the cache + * expiry of the page as a whole, if such behavior is desired. + * + * @see self::getTTL() + * @param int $ttl + */ + public function setTTL( $ttl ); /** * Get a title of frame * * @return Title */ - function getTitle(); + public function getTitle(); } /** @@ -184,36 +268,42 @@ interface PPNode { /** * Get an array-type node containing the children of this node. * Returns false if this is not a tree node. + * @return PPNode */ - function getChildren(); + public function getChildren(); /** * Get the first child of a tree node. False if there isn't one. * * @return PPNode */ - function getFirstChild(); + public function getFirstChild(); /** * Get the next sibling of any node. False if there isn't one + * @return PPNode */ - function getNextSibling(); + public function getNextSibling(); /** * Get all children of this tree node which have a given name. * Returns an array-type node, or false if this is not a tree node. + * @param string $type + * @return bool|PPNode */ - function getChildrenOfType( $type ); + public function getChildrenOfType( $type ); /** * Returns the length of the array, or false if this is not an array-type node */ - function getLength(); + public function getLength(); /** * Returns an item of an array-type node + * @param int $i + * @return bool|PPNode */ - function item( $i ); + public function item( $i ); /** * Get the name of this node. The following names are defined here: @@ -226,25 +316,29 @@ interface PPNode { * #nodelist An array-type node * * The subclass may define various other names for tree and leaf nodes. + * @return string */ - function getName(); + public function getName(); /** * Split a "" node into an associative array containing: * name PPNode name * index String index * value PPNode value + * @return array */ - function splitArg(); + public function splitArg(); /** * 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 */ - function splitExt(); + public function splitExt(); /** * Split an "" node + * @return array */ - function splitHeading(); + public function splitHeading(); } diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php index 3138f483..2edb79a2 100644 --- a/includes/parser/Preprocessor_DOM.php +++ b/includes/parser/Preprocessor_DOM.php @@ -23,19 +23,21 @@ /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class Preprocessor_DOM implements Preprocessor { + // @codingStandardsIgnoreEnd /** * @var Parser */ - var $parser; + public $parser; - var $memoryLimit; + public $memoryLimit; const CACHE_VERSION = 1; - function __construct( $parser ) { + public function __construct( $parser ) { $this->parser = $parser; $mem = ini_get( 'memory_limit' ); $this->memoryLimit = false; @@ -51,40 +53,57 @@ class Preprocessor_DOM implements Preprocessor { /** * @return PPFrame_DOM */ - function newFrame() { + public function newFrame() { return new PPFrame_DOM( $this ); } /** - * @param $args array + * @param array $args * @return PPCustomFrame_DOM */ - function newCustomFrame( $args ) { + public function newCustomFrame( $args ) { return new PPCustomFrame_DOM( $this, $args ); } /** - * @param $values + * @param array $values * @return PPNode_DOM */ - function newPartNodeArray( $values ) { + public function newPartNodeArray( $values ) { //NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais) $xml = ""; foreach ( $values as $k => $val ) { if ( is_int( $k ) ) { - $xml .= "" . htmlspecialchars( $val ) . ""; + $xml .= "" + . htmlspecialchars( $val ) . ""; } else { - $xml .= "" . htmlspecialchars( $k ) . "=" . htmlspecialchars( $val ) . ""; + $xml .= "" . htmlspecialchars( $k ) + . "=" . htmlspecialchars( $val ) . ""; } } $xml .= ""; + wfProfileIn( __METHOD__ . '-loadXML' ); $dom = new DOMDocument(); - $dom->loadXML( $xml ); - $root = $dom->documentElement; + wfSuppressWarnings(); + $result = $dom->loadXML( $xml ); + wfRestoreWarnings(); + if ( !$result ) { + // Try running the XML through UtfNormal to get rid of invalid characters + $xml = UtfNormal::cleanUp( $xml ); + // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 + // don't barf when the XML is >256 levels deep + $result = $dom->loadXML( $xml, 1 << 19 ); + } + wfProfileOut( __METHOD__ . '-loadXML' ); + if ( !$result ) { + throw new MWException( 'Parameters passed to ' . __METHOD__ . ' result in invalid XML' ); + } + + $root = $dom->documentElement; $node = new PPNode_DOM( $root->childNodes ); return $node; } @@ -93,7 +112,7 @@ class Preprocessor_DOM implements Preprocessor { * @throws MWException * @return bool */ - function memCheck() { + public function memCheck() { if ( $this->memoryLimit === false ) { return true; } @@ -109,10 +128,11 @@ class Preprocessor_DOM implements Preprocessor { * Preprocess some wikitext and return the document tree. * This is the ghost of Parser::replace_variables(). * - * @param string $text the text to parse - * @param $flags Integer: bitwise combination of: - * Parser::PTD_FOR_INCLUSION Handle "" and "" as if the text is being - * included. Default is to assume a direct page view. + * @param string $text The text to parse + * @param int $flags Bitwise combination of: + * 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. * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. @@ -128,7 +148,7 @@ class Preprocessor_DOM implements Preprocessor { * @throws MWException * @return PPNode_DOM */ - function preprocessToObj( $text, $flags = 0 ) { + public function preprocessToObj( $text, $flags = 0 ) { wfProfileIn( __METHOD__ ); global $wgMemc, $wgPreprocessorCacheThreshold; @@ -160,7 +180,6 @@ class Preprocessor_DOM implements Preprocessor { $xml = $this->preprocessToXml( $text, $flags ); } - // Fail if the number of elements exceeds acceptable limits // Do not attempt to generate the DOM $this->parser->mGeneratedPPNodeCount += substr_count( $xml, '<' ); @@ -181,7 +200,8 @@ class Preprocessor_DOM implements Preprocessor { if ( !$result ) { // Try running the XML through UtfNormal to get rid of invalid characters $xml = UtfNormal::cleanUp( $xml ); - // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep + // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 + // don't barf when the XML is >256 levels deep. $result = $dom->loadXML( $xml, 1 << 19 ); } if ( $result ) { @@ -202,11 +222,11 @@ class Preprocessor_DOM implements Preprocessor { } /** - * @param $text string - * @param $flags int + * @param string $text + * @param int $flags * @return string */ - function preprocessToXml( $text, $flags = 0 ) { + public function preprocessToXml( $text, $flags = 0 ) { wfProfileIn( __METHOD__ ); $rules = array( '{' => array( @@ -234,7 +254,9 @@ class Preprocessor_DOM implements Preprocessor { $ignoredTags = array( 'includeonly', '/includeonly' ); $ignoredElements = array( 'noinclude' ); $xmlishElements[] = 'noinclude'; - if ( strpos( $text, '' ) !== false && strpos( $text, '' ) !== false ) { + if ( strpos( $text, '' ) !== false + && strpos( $text, '' ) !== false + ) { $enableOnlyinclude = true; } } else { @@ -250,19 +272,28 @@ class Preprocessor_DOM implements Preprocessor { $stack = new PPDStack; $searchBase = "[{<\n"; #} - $revText = strrev( $text ); // For fast reverse searches + // For fast reverse searches + $revText = strrev( $text ); $lengthText = strlen( $text ); - $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start - $accum =& $stack->getAccum(); # Current accumulator + // Input pointer, starts out pointing to a pseudo-newline before the start + $i = 0; + // Current accumulator + $accum =& $stack->getAccum(); $accum = ''; - $findEquals = false; # True to find equals signs in arguments - $findPipe = false; # True to take notice of pipe characters + // True to find equals signs in arguments + $findEquals = false; + // True to take notice of pipe characters + $findPipe = false; $headingIndex = 1; - $inHeading = false; # True if $i is inside a possible heading - $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i - $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next - $fakeLineStart = true; # Do a line-start run without outputting an LF character + // True if $i is inside a possible heading + $inHeading = false; + // True if there are no more greater-than (>) signs right of $i + $noMoreGT = false; + // True to ignore all input up to the next + $findOnlyinclude = $enableOnlyinclude; + // Do a line-start run without outputting an LF character + $fakeLineStart = true; while ( true ) { //$this->memCheck(); @@ -347,7 +378,9 @@ class Preprocessor_DOM implements Preprocessor { if ( $found == 'angle' ) { $matches = false; // Handle - if ( $enableOnlyinclude && substr( $text, $i, strlen( '' ) ) == '' ) { + if ( $enableOnlyinclude + && substr( $text, $i, strlen( '' ) ) == '' + ) { $findOnlyinclude = true; continue; } @@ -400,14 +433,14 @@ class Preprocessor_DOM implements Preprocessor { // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but // it's a possible beneficial b/c break. if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" - && substr( $text, $wsEnd + 1, 1 ) == "\n" ) - { + && substr( $text, $wsEnd + 1, 1 ) == "\n" + ) { // Remove leading whitespace from the end of the accumulator // Sanity check first though $wsLength = $i - $wsStart; if ( $wsLength > 0 - && strspn( $accum, " \t", -$wsLength ) === $wsLength ) - { + && strspn( $accum, " \t", -$wsLength ) === $wsLength + ) { $accum = substr( $accum, 0, -$wsLength ); } @@ -461,7 +494,9 @@ class Preprocessor_DOM implements Preprocessor { // Handle ignored tags if ( in_array( $lowerName, $ignoredTags ) ) { - $accum .= '' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) ) . ''; + $accum .= '' + . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) ) + . ''; $i = $tagEndPos + 1; continue; } @@ -476,8 +511,8 @@ class Preprocessor_DOM implements Preprocessor { $attrEnd = $tagEndPos; // Find closing tag if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", - $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) - { + $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) + ) { $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); $i = $matches[0][1] + strlen( $matches[0][0] ); $close = '' . htmlspecialchars( $matches[0][0] ) . ''; @@ -521,9 +556,11 @@ class Preprocessor_DOM implements Preprocessor { $count = strspn( $text, '=', $i, 6 ); if ( $count == 1 && $findEquals ) { - // DWIM: This looks kind of like a name/value separator - // Let's let the equals handler have it and break the potential heading - // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex. + // DWIM: This looks kind of like a name/value separator. + // Let's let the equals handler have it and break the + // potential heading. This is heuristic, but AFAICT the + // methods for completely correct disambiguation are very + // complex. } elseif ( $count > 0 ) { $piece = array( 'open' => "\n", @@ -542,8 +579,9 @@ class Preprocessor_DOM implements Preprocessor { // A heading must be open, otherwise \n wouldn't have been in the search list 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 + // 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", $lengthText - $i ); $searchStart = $i - $wsLength; if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { @@ -737,18 +775,18 @@ class Preprocessor_DOM implements Preprocessor { * @ingroup Parser */ class PPDStack { - var $stack, $rootAccum; + public $stack, $rootAccum; /** * @var PPDStack */ - var $top; - var $out; - var $elementClass = 'PPDStackElement'; + public $top; + public $out; + public $elementClass = 'PPDStackElement'; - static $false = false; + public static $false = false; - function __construct() { + public function __construct() { $this->stack = array(); $this->top = false; $this->rootAccum = ''; @@ -758,15 +796,15 @@ class PPDStack { /** * @return int */ - function count() { + public function count() { return count( $this->stack ); } - function &getAccum() { + public function &getAccum() { return $this->accum; } - function getCurrentPart() { + public function getCurrentPart() { if ( $this->top === false ) { return false; } else { @@ -774,7 +812,7 @@ class PPDStack { } } - function push( $data ) { + public function push( $data ) { if ( $data instanceof $this->elementClass ) { $this->stack[] = $data; } else { @@ -785,7 +823,7 @@ class PPDStack { $this->accum =& $this->top->getAccum(); } - function pop() { + public function pop() { if ( !count( $this->stack ) ) { throw new MWException( __METHOD__ . ': no elements remaining' ); } @@ -801,7 +839,7 @@ class PPDStack { return $temp; } - function addPart( $s = '' ) { + public function addPart( $s = '' ) { $this->top->addPart( $s ); $this->accum =& $this->top->getAccum(); } @@ -809,7 +847,7 @@ class PPDStack { /** * @return array */ - function getFlags() { + public function getFlags() { if ( !count( $this->stack ) ) { return array( 'findEquals' => false, @@ -826,15 +864,15 @@ class PPDStack { * @ingroup Parser */ class PPDStackElement { - var $open, // Opening character (\n for heading) + public $open, // Opening character (\n for heading) $close, // Matching closing character $count, // Number of opening characters found (number of "=" for heading) $parts, // Array of PPDPart objects describing pipe-separated parts. $lineStart; // True if the open char appeared at the start of the input line. Not set for headings. - var $partClass = 'PPDPart'; + public $partClass = 'PPDPart'; - function __construct( $data = array() ) { + public function __construct( $data = array() ) { $class = $this->partClass; $this->parts = array( new $class ); @@ -843,23 +881,23 @@ class PPDStackElement { } } - function &getAccum() { + public function &getAccum() { return $this->parts[count( $this->parts ) - 1]->out; } - function addPart( $s = '' ) { + public function addPart( $s = '' ) { $class = $this->partClass; $this->parts[] = new $class( $s ); } - function getCurrentPart() { + public function getCurrentPart() { return $this->parts[count( $this->parts ) - 1]; } /** * @return array */ - function getFlags() { + public function getFlags() { $partCount = count( $this->parts ); $findPipe = $this->open != "\n" && $this->open != '['; return array( @@ -872,9 +910,10 @@ class PPDStackElement { /** * Get the output string that would result if the close is not found. * + * @param bool|int $openingCount * @return string */ - function breakSyntax( $openingCount = false ) { + public function breakSyntax( $openingCount = false ) { if ( $this->open == "\n" ) { $s = $this->parts[0]->out; } else { @@ -900,14 +939,14 @@ class PPDStackElement { * @ingroup Parser */ class PPDPart { - var $out; // Output accumulator string + public $out; // Output accumulator string // Optional member variables: // eqpos Position of equals sign in output accumulator // commentEnd Past-the-end input pointer for the last comment encountered // visualEnd Past-the-end input pointer for the end of the accumulator minus comments - function __construct( $out = '' ) { + public function __construct( $out = '' ) { $this->out = $out; } } @@ -915,57 +954,71 @@ class PPDPart { /** * An expansion frame, used as a context to expand the result of preprocessToObj() * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPFrame_DOM implements PPFrame { + // @codingStandardsIgnoreEnd /** * @var Preprocessor */ - var $preprocessor; + public $preprocessor; /** * @var Parser */ - var $parser; + public $parser; /** * @var Title */ - var $title; - var $titleCache; + public $title; + public $titleCache; /** * Hashtable listing templates which are disallowed for expansion in this frame, * having been encountered previously in parent frames. */ - var $loopCheckHash; + public $loopCheckHash; /** * Recursion depth of this frame, top = 0 * Note that this is NOT the same as expansion depth in expand() */ - var $depth; + public $depth; + + private $volatile = false; + private $ttl = null; + + /** + * @var array + */ + protected $childExpansionCache; /** * Construct a new preprocessor frame. - * @param $preprocessor Preprocessor The parent preprocessor + * @param Preprocessor $preprocessor The parent preprocessor */ - function __construct( $preprocessor ) { + public function __construct( $preprocessor ) { $this->preprocessor = $preprocessor; $this->parser = $preprocessor->parser; $this->title = $this->parser->mTitle; $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); $this->loopCheckHash = array(); $this->depth = 0; + $this->childExpansionCache = array(); } /** * Create a new child frame * $args is optionally a multi-root PPNode or array containing the template arguments * + * @param bool|array $args + * @param Title|bool $title + * @param int $indexOffset * @return PPTemplateFrame_DOM */ - function newChild( $args = false, $title = false, $indexOffset = 0 ) { + public function newChild( $args = false, $title = false, $indexOffset = 0 ) { $namedArgs = array(); $numberedArgs = array(); if ( $title === false ) { @@ -980,7 +1033,7 @@ class PPFrame_DOM implements PPFrame { if ( $arg instanceof PPNode ) { $arg = $arg->node; } - if ( !$xpath ) { + if ( !$xpath || $xpath->document !== $arg->ownerDocument ) { $xpath = new DOMXPath( $arg->ownerDocument ); } @@ -1005,11 +1058,23 @@ class PPFrame_DOM implements PPFrame { /** * @throws MWException - * @param $root - * @param $flags int + * @param string|int $key + * @param string|PPNode_DOM|DOMDocument $root + * @param int $flags + * @return string + */ + public function cachedExpand( $key, $root, $flags = 0 ) { + // we don't have a parent, so we don't have a cache + return $this->expand( $root, $flags ); + } + + /** + * @throws MWException + * @param string|PPNode_DOM|DOMDocument $root + * @param int $flags * @return string */ - function expand( $root, $flags = 0 ) { + public function expand( $root, $flags = 0 ) { static $expansionDepth = 0; if ( is_string( $root ) ) { return $root; @@ -1142,17 +1207,16 @@ class PPFrame_DOM implements PPFrame { # Remove it in HTML, pre+remove and STRIP_COMMENTS modes if ( $this->parser->ot['html'] || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) - || ( $flags & PPFrame::STRIP_COMMENTS ) ) - { + || ( $flags & PPFrame::STRIP_COMMENTS ) + ) { $out .= ''; - } - # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result - # Not in RECOVER_COMMENTS mode (extractSections) though - elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { + } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { + # Add a strip marker in PST mode so that pstPass2() can + # run some old-fashioned regexes on the result. + # Not in RECOVER_COMMENTS mode (extractSections) though. $out .= $this->parser->insertStripItem( $contextNode->textContent ); - } - # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove - else { + } else { + # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove $out .= $contextNode->textContent; } } elseif ( $contextNode->nodeName == 'ignore' ) { @@ -1160,7 +1224,9 @@ class PPFrame_DOM implements PPFrame { # OT_WIKI will only respect in substed templates. # The other output types respect it unless NO_IGNORE is set. # extractSections() sets NO_IGNORE and so never respects it. - if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) { + if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) + || ( $flags & PPFrame::NO_IGNORE ) + ) { $out .= $contextNode->textContent; } else { $out .= ''; @@ -1172,13 +1238,29 @@ class PPFrame_DOM implements PPFrame { $attrs = $xpath->query( 'attr', $contextNode ); $inners = $xpath->query( 'inner', $contextNode ); $closes = $xpath->query( 'close', $contextNode ); - $params = array( - 'name' => new PPNode_DOM( $names->item( 0 ) ), - 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null, - 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null, - 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null, - ); - $out .= $this->parser->extensionSubstitution( $params, $this ); + if ( $flags & PPFrame::NO_TAGS ) { + $s = '<' . $this->expand( $names->item( 0 ), $flags ); + if ( $attrs->length > 0 ) { + $s .= $this->expand( $attrs->item( 0 ), $flags ); + } + if ( $inners->length > 0 ) { + $s .= '>' . $this->expand( $inners->item( 0 ), $flags ); + if ( $closes->length > 0 ) { + $s .= $this->expand( $closes->item( 0 ), $flags ); + } + } else { + $s .= '/>'; + } + $out .= $s; + } else { + $params = array( + 'name' => new PPNode_DOM( $names->item( 0 ) ), + 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null, + 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null, + 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null, + ); + $out .= $this->parser->extensionSubstitution( $params, $this ); + } } elseif ( $contextNode->nodeName == 'h' ) { # Heading $s = $this->expand( $contextNode->childNodes, $flags ); @@ -1231,11 +1313,12 @@ class PPFrame_DOM implements PPFrame { } /** - * @param $sep - * @param $flags + * @param string $sep + * @param int $flags + * @param string|PPNode_DOM|DOMDocument $args,... * @return string */ - function implodeWithFlags( $sep, $flags /*, ... */ ) { + public function implodeWithFlags( $sep, $flags /*, ... */ ) { $args = array_slice( func_get_args(), 2 ); $first = true; @@ -1263,9 +1346,11 @@ class PPFrame_DOM implements PPFrame { * Implode with no flags specified * This previously called implodeWithFlags but has now been inlined to reduce stack depth * + * @param string $sep + * @param string|PPNode_DOM|DOMDocument $args,... * @return string */ - function implode( $sep /*, ... */ ) { + public function implode( $sep /*, ... */ ) { $args = array_slice( func_get_args(), 1 ); $first = true; @@ -1293,9 +1378,11 @@ class PPFrame_DOM implements PPFrame { * Makes an object that, when expand()ed, will be the same as one obtained * with implode() * + * @param string $sep + * @param string|PPNode_DOM|DOMDocument $args,... * @return array */ - function virtualImplode( $sep /*, ... */ ) { + public function virtualImplode( $sep /*, ... */ ) { $args = array_slice( func_get_args(), 1 ); $out = array(); $first = true; @@ -1321,9 +1408,13 @@ class PPFrame_DOM implements PPFrame { /** * Virtual implode with brackets + * @param string $start + * @param string $sep + * @param string $end + * @param string|PPNode_DOM|DOMDocument $args,... * @return array */ - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { + public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { $args = array_slice( func_get_args(), 3 ); $out = array( $start ); $first = true; @@ -1348,11 +1439,11 @@ class PPFrame_DOM implements PPFrame { return $out; } - function __toString() { + public function __toString() { return 'frame{}'; } - function getPDBK( $level = false ) { + public function getPDBK( $level = false ) { if ( $level === false ) { return $this->title->getPrefixedDBkey(); } else { @@ -1363,21 +1454,21 @@ class PPFrame_DOM implements PPFrame { /** * @return array */ - function getArguments() { + public function getArguments() { return array(); } /** * @return array */ - function getNumberedArguments() { + public function getNumberedArguments() { return array(); } /** * @return array */ - function getNamedArguments() { + public function getNamedArguments() { return array(); } @@ -1386,20 +1477,21 @@ class PPFrame_DOM implements PPFrame { * * @return bool */ - function isEmpty() { + public function isEmpty() { return true; } - function getArgument( $name ) { + public function getArgument( $name ) { return false; } /** * Returns true if the infinite loop check is OK, false if a loop is detected * + * @param Title $title * @return bool */ - function loopCheck( $title ) { + public function loopCheck( $title ) { return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); } @@ -1408,7 +1500,7 @@ class PPFrame_DOM implements PPFrame { * * @return bool */ - function isTemplate() { + public function isTemplate() { return false; } @@ -1417,32 +1509,75 @@ class PPFrame_DOM implements PPFrame { * * @return Title */ - function getTitle() { + public function getTitle() { return $this->title; } + + /** + * Set the volatile flag + * + * @param bool $flag + */ + public function setVolatile( $flag = true ) { + $this->volatile = $flag; + } + + /** + * Get the volatile flag + * + * @return bool + */ + public function isVolatile() { + return $this->volatile; + } + + /** + * Set the TTL + * + * @param int $ttl + */ + public function setTTL( $ttl ) { + if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) { + $this->ttl = $ttl; + } + } + + /** + * Get the TTL + * + * @return int|null + */ + public function getTTL() { + return $this->ttl; + } } /** * Expansion frame with template arguments * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPTemplateFrame_DOM extends PPFrame_DOM { - var $numberedArgs, $namedArgs; + // @codingStandardsIgnoreEnd + + public $numberedArgs, $namedArgs; /** * @var PPFrame_DOM */ - var $parent; - var $numberedExpansionCache, $namedExpansionCache; + public $parent; + public $numberedExpansionCache, $namedExpansionCache; /** - * @param $preprocessor - * @param $parent PPFrame_DOM - * @param $numberedArgs array - * @param $namedArgs array - * @param $title Title + * @param Preprocessor $preprocessor + * @param bool|PPFrame_DOM $parent + * @param array $numberedArgs + * @param array $namedArgs + * @param bool|Title $title */ - function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { + public function __construct( $preprocessor, $parent = false, $numberedArgs = array(), + $namedArgs = array(), $title = false + ) { parent::__construct( $preprocessor ); $this->parent = $parent; @@ -1460,7 +1595,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { $this->numberedExpansionCache = $this->namedExpansionCache = array(); } - function __toString() { + public function __toString() { $s = 'tplframe{'; $first = true; $args = $this->numberedArgs + $this->namedArgs; @@ -1477,16 +1612,34 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { return $s; } + /** + * @throws MWException + * @param string|int $key + * @param string|PPNode_DOM|DOMDocument $root + * @param int $flags + * @return string + */ + public function cachedExpand( $key, $root, $flags = 0 ) { + if ( isset( $this->parent->childExpansionCache[$key] ) ) { + return $this->parent->childExpansionCache[$key]; + } + $retval = $this->expand( $root, $flags ); + if ( !$this->isVolatile() ) { + $this->parent->childExpansionCache[$key] = $retval; + } + return $retval; + } + /** * Returns true if there are no arguments in this frame * * @return bool */ - function isEmpty() { + public function isEmpty() { return !count( $this->numberedArgs ) && !count( $this->namedArgs ); } - function getArguments() { + public function getArguments() { $arguments = array(); foreach ( array_merge( array_keys( $this->numberedArgs ), @@ -1496,7 +1649,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { return $arguments; } - function getNumberedArguments() { + public function getNumberedArguments() { $arguments = array(); foreach ( array_keys( $this->numberedArgs ) as $key ) { $arguments[$key] = $this->getArgument( $key ); @@ -1504,7 +1657,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { return $arguments; } - function getNamedArguments() { + public function getNamedArguments() { $arguments = array(); foreach ( array_keys( $this->namedArgs ) as $key ) { $arguments[$key] = $this->getArgument( $key ); @@ -1512,18 +1665,21 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { return $arguments; } - function getNumberedArgument( $index ) { + public function getNumberedArgument( $index ) { if ( !isset( $this->numberedArgs[$index] ) ) { return false; } if ( !isset( $this->numberedExpansionCache[$index] ) ) { # No trimming for unnamed arguments - $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS ); + $this->numberedExpansionCache[$index] = $this->parent->expand( + $this->numberedArgs[$index], + PPFrame::STRIP_COMMENTS + ); } return $this->numberedExpansionCache[$index]; } - function getNamedArgument( $name ) { + public function getNamedArgument( $name ) { if ( !isset( $this->namedArgs[$name] ) ) { return false; } @@ -1535,7 +1691,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { return $this->namedExpansionCache[$name]; } - function getArgument( $name ) { + public function getArgument( $name ) { $text = $this->getNumberedArgument( $name ); if ( $text === false ) { $text = $this->getNamedArgument( $name ); @@ -1548,24 +1704,37 @@ class PPTemplateFrame_DOM extends PPFrame_DOM { * * @return bool */ - function isTemplate() { + public function isTemplate() { return true; } + + public function setVolatile( $flag = true ) { + parent::setVolatile( $flag ); + $this->parent->setVolatile( $flag ); + } + + public function setTTL( $ttl ) { + parent::setTTL( $ttl ); + $this->parent->setTTL( $ttl ); + } } /** * Expansion frame with custom arguments * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPCustomFrame_DOM extends PPFrame_DOM { - var $args; + // @codingStandardsIgnoreEnd + + public $args; - function __construct( $preprocessor, $args ) { + public function __construct( $preprocessor, $args ) { parent::__construct( $preprocessor ); $this->args = $args; } - function __toString() { + public function __toString() { $s = 'cstmframe{'; $first = true; foreach ( $this->args as $name => $value ) { @@ -1584,48 +1753,50 @@ class PPCustomFrame_DOM extends PPFrame_DOM { /** * @return bool */ - function isEmpty() { + public function isEmpty() { return !count( $this->args ); } - function getArgument( $index ) { + public function getArgument( $index ) { if ( !isset( $this->args[$index] ) ) { return false; } return $this->args[$index]; } - function getArguments() { + public function getArguments() { return $this->args; } } /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPNode_DOM implements PPNode { + // @codingStandardsIgnoreEnd /** * @var DOMElement */ - var $node; - var $xpath; + public $node; + public $xpath; - function __construct( $node, $xpath = false ) { + public function __construct( $node, $xpath = false ) { $this->node = $node; } /** * @return DOMXPath */ - function getXPath() { + public function getXPath() { if ( $this->xpath === null ) { $this->xpath = new DOMXPath( $this->node->ownerDocument ); } return $this->xpath; } - function __toString() { + public function __toString() { if ( $this->node instanceof DOMNodeList ) { $s = ''; foreach ( $this->node as $node ) { @@ -1640,37 +1811,37 @@ class PPNode_DOM implements PPNode { /** * @return bool|PPNode_DOM */ - function getChildren() { + public function getChildren() { return $this->node->childNodes ? new self( $this->node->childNodes ) : false; } /** * @return bool|PPNode_DOM */ - function getFirstChild() { + public function getFirstChild() { return $this->node->firstChild ? new self( $this->node->firstChild ) : false; } /** * @return bool|PPNode_DOM */ - function getNextSibling() { + public function getNextSibling() { return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false; } /** - * @param $type + * @param string $type * * @return bool|PPNode_DOM */ - function getChildrenOfType( $type ) { + public function getChildrenOfType( $type ) { return new self( $this->getXPath()->query( $type, $this->node ) ); } /** * @return int */ - function getLength() { + public function getLength() { if ( $this->node instanceof DOMNodeList ) { return $this->node->length; } else { @@ -1679,10 +1850,10 @@ class PPNode_DOM implements PPNode { } /** - * @param $i + * @param int $i * @return bool|PPNode_DOM */ - function item( $i ) { + public function item( $i ) { $item = $this->node->item( $i ); return $item ? new self( $item ) : false; } @@ -1690,7 +1861,7 @@ class PPNode_DOM implements PPNode { /** * @return string */ - function getName() { + public function getName() { if ( $this->node instanceof DOMNodeList ) { return '#nodelist'; } else { @@ -1707,7 +1878,7 @@ class PPNode_DOM implements PPNode { * @throws MWException * @return array */ - function splitArg() { + public function splitArg() { $xpath = $this->getXPath(); $names = $xpath->query( 'name', $this->node ); $values = $xpath->query( 'value', $this->node ); @@ -1729,7 +1900,7 @@ class PPNode_DOM implements PPNode { * @throws MWException * @return array */ - function splitExt() { + public function splitExt() { $xpath = $this->getXPath(); $names = $xpath->query( 'name', $this->node ); $attrs = $xpath->query( 'attr', $this->node ); @@ -1755,7 +1926,7 @@ class PPNode_DOM implements PPNode { * @throws MWException * @return array */ - function splitHeading() { + public function splitHeading() { if ( $this->getName() !== 'h' ) { throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); } diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php index 2fc5e118..63763967 100644 --- a/includes/parser/Preprocessor_Hash.php +++ b/includes/parser/Preprocessor_Hash.php @@ -26,39 +26,42 @@ * * attribute nodes are children * * "" nodes that aren't at the top are replaced with * @ingroup Parser + * @codingStandardsIgnoreStart */ class Preprocessor_Hash implements Preprocessor { + // @codingStandardsIgnoreEnd + /** * @var Parser */ - var $parser; + public $parser; const CACHE_VERSION = 1; - function __construct( $parser ) { + public function __construct( $parser ) { $this->parser = $parser; } /** * @return PPFrame_Hash */ - function newFrame() { + public function newFrame() { return new PPFrame_Hash( $this ); } /** - * @param $args array + * @param array $args * @return PPCustomFrame_Hash */ - function newCustomFrame( $args ) { + public function newCustomFrame( $args ) { return new PPCustomFrame_Hash( $this, $args ); } /** - * @param $values array + * @param array $values * @return PPNode_Hash_Array */ - function newPartNodeArray( $values ) { + public function newPartNodeArray( $values ) { $list = array(); foreach ( $values as $k => $val ) { @@ -89,10 +92,10 @@ class Preprocessor_Hash implements Preprocessor { * Preprocess some wikitext and return the document tree. * This is the ghost of Parser::replace_variables(). * - * @param string $text the text to parse - * @param $flags Integer: bitwise combination of: - * Parser::PTD_FOR_INCLUSION Handle "" and "" as if the text is being - * included. Default is to assume a direct page view. + * @param string $text The text to parse + * @param int $flags Bitwise combination of: + * 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. * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. @@ -108,13 +111,15 @@ class Preprocessor_Hash implements Preprocessor { * @throws MWException * @return PPNode_Hash_Tree */ - function preprocessToObj( $text, $flags = 0 ) { + public function preprocessToObj( $text, $flags = 0 ) { wfProfileIn( __METHOD__ ); // Check cache. global $wgMemc, $wgPreprocessorCacheThreshold; - $cacheable = $wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold; + $cacheable = $wgPreprocessorCacheThreshold !== false + && strlen( $text ) > $wgPreprocessorCacheThreshold; + if ( $cacheable ) { wfProfileIn( __METHOD__ . '-cacheable' ); @@ -161,7 +166,9 @@ class Preprocessor_Hash implements Preprocessor { $ignoredTags = array( 'includeonly', '/includeonly' ); $ignoredElements = array( 'noinclude' ); $xmlishElements[] = 'noinclude'; - if ( strpos( $text, '' ) !== false && strpos( $text, '' ) !== false ) { + if ( strpos( $text, '' ) !== false + && strpos( $text, '' ) !== false + ) { $enableOnlyinclude = true; } } else { @@ -177,18 +184,27 @@ class Preprocessor_Hash implements Preprocessor { $stack = new PPDStack_Hash; $searchBase = "[{<\n"; - $revText = strrev( $text ); // For fast reverse searches + // For fast reverse searches + $revText = strrev( $text ); $lengthText = strlen( $text ); - $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start - $accum =& $stack->getAccum(); # Current accumulator - $findEquals = false; # True to find equals signs in arguments - $findPipe = false; # True to take notice of pipe characters + // Input pointer, starts out pointing to a pseudo-newline before the start + $i = 0; + // Current accumulator + $accum =& $stack->getAccum(); + // True to find equals signs in arguments + $findEquals = false; + // True to take notice of pipe characters + $findPipe = false; $headingIndex = 1; - $inHeading = false; # True if $i is inside a possible heading - $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i - $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next - $fakeLineStart = true; # Do a line-start run without outputting an LF character + // True if $i is inside a possible heading + $inHeading = false; + // True if there are no more greater-than (>) signs right of $i + $noMoreGT = false; + // True to ignore all input up to the next + $findOnlyinclude = $enableOnlyinclude; + // Do a line-start run without outputting an LF character + $fakeLineStart = true; while ( true ) { //$this->memCheck(); @@ -273,7 +289,9 @@ class Preprocessor_Hash implements Preprocessor { if ( $found == 'angle' ) { $matches = false; // Handle - if ( $enableOnlyinclude && substr( $text, $i, strlen( '' ) ) == '' ) { + if ( $enableOnlyinclude + && substr( $text, $i, strlen( '' ) ) == '' + ) { $findOnlyinclude = true; continue; } @@ -326,15 +344,15 @@ class Preprocessor_Hash implements Preprocessor { // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but // it's a possible beneficial b/c break. if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" - && substr( $text, $wsEnd + 1, 1 ) == "\n" ) - { + && substr( $text, $wsEnd + 1, 1 ) == "\n" + ) { // Remove leading whitespace from the end of the accumulator // Sanity check first though $wsLength = $i - $wsStart; if ( $wsLength > 0 && $accum->lastNode instanceof PPNode_Hash_Text - && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength ) - { + && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength + ) { $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength ); } @@ -404,8 +422,8 @@ class Preprocessor_Hash implements Preprocessor { $attrEnd = $tagEndPos; // Find closing tag if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", - $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) - { + $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) + ) { $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); $i = $matches[0][1] + strlen( $matches[0][0] ); $close = $matches[0][0]; @@ -440,9 +458,7 @@ class Preprocessor_Hash implements Preprocessor { $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) ); } $accum->addNode( $extNode ); - } - - elseif ( $found == 'line-start' ) { + } elseif ( $found == 'line-start' ) { // Is this the start of a heading? // Line break belongs before the heading element in any case if ( $fakeLineStart ) { @@ -454,9 +470,10 @@ class Preprocessor_Hash implements Preprocessor { $count = strspn( $text, '=', $i, 6 ); if ( $count == 1 && $findEquals ) { - // DWIM: This looks kind of like a name/value separator - // Let's let the equals handler have it and break the potential heading - // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex. + // DWIM: This looks kind of like a name/value separator. + // Let's let the equals handler have it and break the potential + // heading. This is heuristic, but AFAICT the methods for + // completely correct disambiguation are very complex. } elseif ( $count > 0 ) { $piece = array( 'open' => "\n", @@ -474,8 +491,9 @@ class Preprocessor_Hash implements Preprocessor { // A heading must be open, otherwise \n wouldn't have been in the search list 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 + // 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", $lengthText - $i ); $searchStart = $i - $wsLength; if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { @@ -743,9 +761,12 @@ class Preprocessor_Hash implements Preprocessor { /** * Stack class to help Preprocessor::preprocessToObj() * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPDStack_Hash extends PPDStack { - function __construct() { + // @codingStandardsIgnoreEnd + + public function __construct() { $this->elementClass = 'PPDStackElement_Hash'; parent::__construct(); $this->rootAccum = new PPDAccum_Hash; @@ -754,9 +775,12 @@ class PPDStack_Hash extends PPDStack { /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPDStackElement_Hash extends PPDStackElement { - function __construct( $data = array() ) { + // @codingStandardsIgnoreENd + + public function __construct( $data = array() ) { $this->partClass = 'PPDPart_Hash'; parent::__construct( $data ); } @@ -764,9 +788,10 @@ class PPDStackElement_Hash extends PPDStackElement { /** * Get the accumulator that would result if the close is not found. * + * @param int|bool $openingCount * @return PPDAccum_Hash */ - function breakSyntax( $openingCount = false ) { + public function breakSyntax( $openingCount = false ) { if ( $this->open == "\n" ) { $accum = $this->parts[0]->out; } else { @@ -791,9 +816,12 @@ class PPDStackElement_Hash extends PPDStackElement { /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPDPart_Hash extends PPDPart { - function __construct( $out = '' ) { + // @codingStandardsIgnoreEnd + + public function __construct( $out = '' ) { $accum = new PPDAccum_Hash; if ( $out !== '' ) { $accum->addLiteral( $out ); @@ -804,18 +832,22 @@ class PPDPart_Hash extends PPDPart { /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPDAccum_Hash { - var $firstNode, $lastNode; + // @codingStandardsIgnoreEnd + + public $firstNode, $lastNode; - function __construct() { + public function __construct() { $this->firstNode = $this->lastNode = false; } /** * Append a string literal + * @param string $s */ - function addLiteral( $s ) { + public function addLiteral( $s ) { if ( $this->lastNode === false ) { $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s ); } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) { @@ -828,8 +860,9 @@ class PPDAccum_Hash { /** * Append a PPNode + * @param PPNode $node */ - function addNode( PPNode $node ) { + public function addNode( PPNode $node ) { if ( $this->lastNode === false ) { $this->firstNode = $this->lastNode = $node; } else { @@ -840,18 +873,21 @@ class PPDAccum_Hash { /** * Append a tree node with text contents + * @param string $name + * @param string $value */ - function addNodeWithText( $name, $value ) { + public function addNodeWithText( $name, $value ) { $node = PPNode_Hash_Tree::newWithText( $name, $value ); $this->addNode( $node ); } /** - * Append a PPAccum_Hash + * Append a PPDAccum_Hash * Takes over ownership of the nodes in the source argument. These nodes may * subsequently be modified, especially nextSibling. + * @param PPDAccum_Hash $accum */ - function addAccum( $accum ) { + public function addAccum( $accum ) { if ( $accum->lastNode === false ) { // nothing to add } elseif ( $this->lastNode === false ) { @@ -867,62 +903,72 @@ class PPDAccum_Hash { /** * An expansion frame, used as a context to expand the result of preprocessToObj() * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPFrame_Hash implements PPFrame { + // @codingStandardsIgnoreEnd /** * @var Parser */ - var $parser; + public $parser; /** * @var Preprocessor */ - var $preprocessor; + public $preprocessor; /** * @var Title */ - var $title; - var $titleCache; + public $title; + public $titleCache; /** * Hashtable listing templates which are disallowed for expansion in this frame, * having been encountered previously in parent frames. */ - var $loopCheckHash; + public $loopCheckHash; /** * Recursion depth of this frame, top = 0 * Note that this is NOT the same as expansion depth in expand() */ - var $depth; + public $depth; + + private $volatile = false; + private $ttl = null; + + /** + * @var array + */ + protected $childExpansionCache; /** * Construct a new preprocessor frame. - * @param $preprocessor Preprocessor: the parent preprocessor + * @param Preprocessor $preprocessor The parent preprocessor */ - function __construct( $preprocessor ) { + public function __construct( $preprocessor ) { $this->preprocessor = $preprocessor; $this->parser = $preprocessor->parser; $this->title = $this->parser->mTitle; $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); $this->loopCheckHash = array(); $this->depth = 0; + $this->childExpansionCache = array(); } /** * Create a new child frame * $args is optionally a multi-root PPNode or array containing the template arguments * - * @param array|bool|\PPNode_Hash_Array $args PPNode_Hash_Array|array - * @param $title Title|bool - * + * @param array|bool|PPNode_Hash_Array $args + * @param Title|bool $title * @param int $indexOffset * @throws MWException * @return PPTemplateFrame_Hash */ - function newChild( $args = false, $title = false, $indexOffset = 0 ) { + public function newChild( $args = false, $title = false, $indexOffset = 0 ) { $namedArgs = array(); $numberedArgs = array(); if ( $title === false ) { @@ -954,11 +1000,23 @@ class PPFrame_Hash implements PPFrame { /** * @throws MWException - * @param $root - * @param $flags int + * @param string|int $key + * @param string|PPNode $root + * @param int $flags + * @return string + */ + public function cachedExpand( $key, $root, $flags = 0 ) { + // we don't have a parent, so we don't have a cache + return $this->expand( $root, $flags ); + } + + /** + * @throws MWException + * @param string|PPNode $root + * @param int $flags * @return string */ - function expand( $root, $flags = 0 ) { + public function expand( $root, $flags = 0 ) { static $expansionDepth = 0; if ( is_string( $root ) ) { return $root; @@ -1035,7 +1093,11 @@ class PPFrame_Hash implements PPFrame { # Double-brace expansion $bits = $contextNode->splitTemplate(); if ( $flags & PPFrame::NO_TEMPLATES ) { - $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] ); + $newIterator = $this->virtualBracketedImplode( + '{{', '|', '}}', + $bits['title'], + $bits['parts'] + ); } else { $ret = $this->parser->braceSubstitution( $bits, $this ); if ( isset( $ret['object'] ) ) { @@ -1048,7 +1110,11 @@ class PPFrame_Hash implements PPFrame { # Triple-brace expansion $bits = $contextNode->splitTemplate(); if ( $flags & PPFrame::NO_ARGS ) { - $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] ); + $newIterator = $this->virtualBracketedImplode( + '{{{', '|', '}}}', + $bits['title'], + $bits['parts'] + ); } else { $ret = $this->parser->argSubstitution( $bits, $this ); if ( isset( $ret['object'] ) ) { @@ -1062,17 +1128,16 @@ class PPFrame_Hash implements PPFrame { # Remove it in HTML, pre+remove and STRIP_COMMENTS modes if ( $this->parser->ot['html'] || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) - || ( $flags & PPFrame::STRIP_COMMENTS ) ) - { + || ( $flags & PPFrame::STRIP_COMMENTS ) + ) { $out .= ''; - } - # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result - # Not in RECOVER_COMMENTS mode (extractSections) though - elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { + } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { + # Add a strip marker in PST mode so that pstPass2() can + # run some old-fashioned regexes on the result. + # Not in RECOVER_COMMENTS mode (extractSections) though. $out .= $this->parser->insertStripItem( $contextNode->firstChild->value ); - } - # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove - else { + } else { + # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove $out .= $contextNode->firstChild->value; } } elseif ( $contextNode->name == 'ignore' ) { @@ -1080,7 +1145,9 @@ class PPFrame_Hash implements PPFrame { # OT_WIKI will only respect in substed templates. # The other output types respect it unless NO_IGNORE is set. # extractSections() sets NO_IGNORE and so never respects it. - if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) { + if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) + || ( $flags & PPFrame::NO_IGNORE ) + ) { $out .= $contextNode->firstChild->value; } else { //$out .= ''; @@ -1088,7 +1155,23 @@ class PPFrame_Hash implements PPFrame { } elseif ( $contextNode->name == 'ext' ) { # Extension tag $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null ); - $out .= $this->parser->extensionSubstitution( $bits, $this ); + if ( $flags & PPFrame::NO_TAGS ) { + $s = '<' . $bits['name']->firstChild->value; + if ( $bits['attr'] ) { + $s .= $bits['attr']->firstChild->value; + } + if ( $bits['inner'] ) { + $s .= '>' . $bits['inner']->firstChild->value; + if ( $bits['close'] ) { + $s .= $bits['close']->firstChild->value; + } + } else { + $s .= '/>'; + } + $out .= $s; + } else { + $out .= $this->parser->extensionSubstitution( $bits, $this ); + } } elseif ( $contextNode->name == 'h' ) { # Heading if ( $this->parser->ot['html'] ) { @@ -1139,11 +1222,12 @@ class PPFrame_Hash implements PPFrame { } /** - * @param $sep - * @param $flags + * @param string $sep + * @param int $flags + * @param string|PPNode $args,... * @return string */ - function implodeWithFlags( $sep, $flags /*, ... */ ) { + public function implodeWithFlags( $sep, $flags /*, ... */ ) { $args = array_slice( func_get_args(), 2 ); $first = true; @@ -1170,9 +1254,11 @@ class PPFrame_Hash implements PPFrame { /** * Implode with no flags specified * This previously called implodeWithFlags but has now been inlined to reduce stack depth + * @param string $sep + * @param string|PPNode $args,... * @return string */ - function implode( $sep /*, ... */ ) { + public function implode( $sep /*, ... */ ) { $args = array_slice( func_get_args(), 1 ); $first = true; @@ -1200,9 +1286,11 @@ class PPFrame_Hash implements PPFrame { * Makes an object that, when expand()ed, will be the same as one obtained * with implode() * + * @param string $sep + * @param string|PPNode $args,... * @return PPNode_Hash_Array */ - function virtualImplode( $sep /*, ... */ ) { + public function virtualImplode( $sep /*, ... */ ) { $args = array_slice( func_get_args(), 1 ); $out = array(); $first = true; @@ -1229,9 +1317,13 @@ class PPFrame_Hash implements PPFrame { /** * Virtual implode with brackets * + * @param string $start + * @param string $sep + * @param string $end + * @param string|PPNode $args,... * @return PPNode_Hash_Array */ - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { + public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { $args = array_slice( func_get_args(), 3 ); $out = array( $start ); $first = true; @@ -1256,15 +1348,15 @@ class PPFrame_Hash implements PPFrame { return new PPNode_Hash_Array( $out ); } - function __toString() { + public function __toString() { return 'frame{}'; } /** - * @param $level bool - * @return array|bool|String + * @param bool $level + * @return array|bool|string */ - function getPDBK( $level = false ) { + public function getPDBK( $level = false ) { if ( $level === false ) { return $this->title->getPrefixedDBkey(); } else { @@ -1275,21 +1367,21 @@ class PPFrame_Hash implements PPFrame { /** * @return array */ - function getArguments() { + public function getArguments() { return array(); } /** * @return array */ - function getNumberedArguments() { + public function getNumberedArguments() { return array(); } /** * @return array */ - function getNamedArguments() { + public function getNamedArguments() { return array(); } @@ -1298,26 +1390,26 @@ class PPFrame_Hash implements PPFrame { * * @return bool */ - function isEmpty() { + public function isEmpty() { return true; } /** - * @param $name + * @param string $name * @return bool */ - function getArgument( $name ) { + public function getArgument( $name ) { return false; } /** * Returns true if the infinite loop check is OK, false if a loop is detected * - * @param $title Title + * @param Title $title * * @return bool */ - function loopCheck( $title ) { + public function loopCheck( $title ) { return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); } @@ -1326,7 +1418,7 @@ class PPFrame_Hash implements PPFrame { * * @return bool */ - function isTemplate() { + public function isTemplate() { return false; } @@ -1335,27 +1427,70 @@ class PPFrame_Hash implements PPFrame { * * @return Title */ - function getTitle() { + public function getTitle() { return $this->title; } + + /** + * Set the volatile flag + * + * @param bool $flag + */ + public function setVolatile( $flag = true ) { + $this->volatile = $flag; + } + + /** + * Get the volatile flag + * + * @return bool + */ + public function isVolatile() { + return $this->volatile; + } + + /** + * Set the TTL + * + * @param int $ttl + */ + public function setTTL( $ttl ) { + if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) { + $this->ttl = $ttl; + } + } + + /** + * Get the TTL + * + * @return int|null + */ + public function getTTL() { + return $this->ttl; + } } /** * Expansion frame with template arguments * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPTemplateFrame_Hash extends PPFrame_Hash { - var $numberedArgs, $namedArgs, $parent; - var $numberedExpansionCache, $namedExpansionCache; + // @codingStandardsIgnoreEnd + + public $numberedArgs, $namedArgs, $parent; + public $numberedExpansionCache, $namedExpansionCache; /** - * @param $preprocessor - * @param $parent - * @param $numberedArgs array - * @param $namedArgs array - * @param $title Title + * @param Preprocessor $preprocessor + * @param bool|PPFrame $parent + * @param array $numberedArgs + * @param array $namedArgs + * @param bool|Title $title */ - function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { + public function __construct( $preprocessor, $parent = false, $numberedArgs = array(), + $namedArgs = array(), $title = false + ) { parent::__construct( $preprocessor ); $this->parent = $parent; @@ -1373,7 +1508,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { $this->numberedExpansionCache = $this->namedExpansionCache = array(); } - function __toString() { + public function __toString() { $s = 'tplframe{'; $first = true; $args = $this->numberedArgs + $this->namedArgs; @@ -1389,19 +1524,38 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { $s .= '}'; return $s; } + + /** + * @throws MWException + * @param string|int $key + * @param string|PPNode $root + * @param int $flags + * @return string + */ + public function cachedExpand( $key, $root, $flags = 0 ) { + if ( isset( $this->parent->childExpansionCache[$key] ) ) { + return $this->parent->childExpansionCache[$key]; + } + $retval = $this->expand( $root, $flags ); + if ( !$this->isVolatile() ) { + $this->parent->childExpansionCache[$key] = $retval; + } + return $retval; + } + /** * Returns true if there are no arguments in this frame * * @return bool */ - function isEmpty() { + public function isEmpty() { return !count( $this->numberedArgs ) && !count( $this->namedArgs ); } /** * @return array */ - function getArguments() { + public function getArguments() { $arguments = array(); foreach ( array_merge( array_keys( $this->numberedArgs ), @@ -1414,7 +1568,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { /** * @return array */ - function getNumberedArguments() { + public function getNumberedArguments() { $arguments = array(); foreach ( array_keys( $this->numberedArgs ) as $key ) { $arguments[$key] = $this->getArgument( $key ); @@ -1425,7 +1579,7 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { /** * @return array */ - function getNamedArguments() { + public function getNamedArguments() { $arguments = array(); foreach ( array_keys( $this->namedArgs ) as $key ) { $arguments[$key] = $this->getArgument( $key ); @@ -1434,25 +1588,28 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { } /** - * @param $index + * @param int $index * @return array|bool */ - function getNumberedArgument( $index ) { + public function getNumberedArgument( $index ) { if ( !isset( $this->numberedArgs[$index] ) ) { return false; } if ( !isset( $this->numberedExpansionCache[$index] ) ) { # No trimming for unnamed arguments - $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS ); + $this->numberedExpansionCache[$index] = $this->parent->expand( + $this->numberedArgs[$index], + PPFrame::STRIP_COMMENTS + ); } return $this->numberedExpansionCache[$index]; } /** - * @param $name + * @param string $name * @return bool */ - function getNamedArgument( $name ) { + public function getNamedArgument( $name ) { if ( !isset( $this->namedArgs[$name] ) ) { return false; } @@ -1465,10 +1622,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { } /** - * @param $name + * @param string $name * @return array|bool */ - function getArgument( $name ) { + public function getArgument( $name ) { $text = $this->getNumberedArgument( $name ); if ( $text === false ) { $text = $this->getNamedArgument( $name ); @@ -1481,24 +1638,37 @@ class PPTemplateFrame_Hash extends PPFrame_Hash { * * @return bool */ - function isTemplate() { + public function isTemplate() { return true; } + + public function setVolatile( $flag = true ) { + parent::setVolatile( $flag ); + $this->parent->setVolatile( $flag ); + } + + public function setTTL( $ttl ) { + parent::setTTL( $ttl ); + $this->parent->setTTL( $ttl ); + } } /** * Expansion frame with custom arguments * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPCustomFrame_Hash extends PPFrame_Hash { - var $args; + // @codingStandardsIgnoreEnd - function __construct( $preprocessor, $args ) { + public $args; + + public function __construct( $preprocessor, $args ) { parent::__construct( $preprocessor ); $this->args = $args; } - function __toString() { + public function __toString() { $s = 'cstmframe{'; $first = true; foreach ( $this->args as $name => $value ) { @@ -1517,38 +1687,41 @@ class PPCustomFrame_Hash extends PPFrame_Hash { /** * @return bool */ - function isEmpty() { + public function isEmpty() { return !count( $this->args ); } /** - * @param $index + * @param int $index * @return bool */ - function getArgument( $index ) { + public function getArgument( $index ) { if ( !isset( $this->args[$index] ) ) { return false; } return $this->args[$index]; } - function getArguments() { + public function getArguments() { return $this->args; } } /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPNode_Hash_Tree implements PPNode { - var $name, $firstChild, $lastChild, $nextSibling; + // @codingStandardsIgnoreEnd + + public $name, $firstChild, $lastChild, $nextSibling; - function __construct( $name ) { + public function __construct( $name ) { $this->name = $name; $this->firstChild = $this->lastChild = $this->nextSibling = false; } - function __toString() { + public function __toString() { $inner = ''; $attribs = ''; for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) { @@ -1566,17 +1739,17 @@ class PPNode_Hash_Tree implements PPNode { } /** - * @param $name - * @param $text + * @param string $name + * @param string $text * @return PPNode_Hash_Tree */ - static function newWithText( $name, $text ) { + public static function newWithText( $name, $text ) { $obj = new self( $name ); $obj->addChild( new PPNode_Hash_Text( $text ) ); return $obj; } - function addChild( $node ) { + public function addChild( $node ) { if ( $this->lastChild === false ) { $this->firstChild = $this->lastChild = $node; } else { @@ -1588,7 +1761,7 @@ class PPNode_Hash_Tree implements PPNode { /** * @return PPNode_Hash_Array */ - function getChildren() { + public function getChildren() { $children = array(); for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { $children[] = $child; @@ -1596,15 +1769,15 @@ class PPNode_Hash_Tree implements PPNode { return new PPNode_Hash_Array( $children ); } - function getFirstChild() { + public function getFirstChild() { return $this->firstChild; } - function getNextSibling() { + public function getNextSibling() { return $this->nextSibling; } - function getChildrenOfType( $name ) { + public function getChildrenOfType( $name ) { $children = array(); for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { if ( isset( $child->name ) && $child->name === $name ) { @@ -1617,22 +1790,22 @@ class PPNode_Hash_Tree implements PPNode { /** * @return bool */ - function getLength() { + public function getLength() { return false; } /** - * @param $i + * @param int $i * @return bool */ - function item( $i ) { + public function item( $i ) { return false; } /** * @return string */ - function getName() { + public function getName() { return $this->name; } @@ -1645,7 +1818,7 @@ class PPNode_Hash_Tree implements PPNode { * @throws MWException * @return array */ - function splitArg() { + public function splitArg() { $bits = array(); for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { if ( !isset( $child->name ) ) { @@ -1654,8 +1827,8 @@ class PPNode_Hash_Tree implements PPNode { if ( $child->name === 'name' ) { $bits['name'] = $child; if ( $child->firstChild instanceof PPNode_Hash_Attr - && $child->firstChild->name === 'index' ) - { + && $child->firstChild->name === 'index' + ) { $bits['index'] = $child->firstChild->value; } } elseif ( $child->name === 'value' ) { @@ -1679,7 +1852,7 @@ class PPNode_Hash_Tree implements PPNode { * @throws MWException * @return array */ - function splitExt() { + public function splitExt() { $bits = array(); for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { if ( !isset( $child->name ) ) { @@ -1707,7 +1880,7 @@ class PPNode_Hash_Tree implements PPNode { * @throws MWException * @return array */ - function splitHeading() { + public function splitHeading() { if ( $this->name !== 'h' ) { throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); } @@ -1734,7 +1907,7 @@ class PPNode_Hash_Tree implements PPNode { * @throws MWException * @return array */ - function splitTemplate() { + public function splitTemplate() { $parts = array(); $bits = array( 'lineStart' => '' ); for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { @@ -1761,101 +1934,178 @@ class PPNode_Hash_Tree implements PPNode { /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPNode_Hash_Text implements PPNode { - var $value, $nextSibling; + // @codingStandardsIgnoreEnd - function __construct( $value ) { + public $value, $nextSibling; + + public function __construct( $value ) { if ( is_object( $value ) ) { throw new MWException( __CLASS__ . ' given object instead of string' ); } $this->value = $value; } - function __toString() { + public function __toString() { return htmlspecialchars( $this->value ); } - function getNextSibling() { + public function getNextSibling() { return $this->nextSibling; } - function getChildren() { return false; } - function getFirstChild() { return false; } - function getChildrenOfType( $name ) { return false; } - function getLength() { return false; } - function item( $i ) { return false; } - function getName() { return '#text'; } - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } + public function getChildren() { + return false; + } + + public function getFirstChild() { + return false; + } + + public function getChildrenOfType( $name ) { + return false; + } + + public function getLength() { + return false; + } + + public function item( $i ) { + return false; + } + + public function getName() { + return '#text'; + } + + public function splitArg() { + throw new MWException( __METHOD__ . ': not supported' ); + } + + public function splitExt() { + throw new MWException( __METHOD__ . ': not supported' ); + } + + public function splitHeading() { + throw new MWException( __METHOD__ . ': not supported' ); + } } /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPNode_Hash_Array implements PPNode { - var $value, $nextSibling; + // @codingStandardsIgnoreEnd + + public $value, $nextSibling; - function __construct( $value ) { + public function __construct( $value ) { $this->value = $value; } - function __toString() { + public function __toString() { return var_export( $this, true ); } - function getLength() { + public function getLength() { return count( $this->value ); } - function item( $i ) { + public function item( $i ) { return $this->value[$i]; } - function getName() { return '#nodelist'; } + public function getName() { + return '#nodelist'; + } - function getNextSibling() { + public function getNextSibling() { return $this->nextSibling; } - function getChildren() { return false; } - function getFirstChild() { return false; } - function getChildrenOfType( $name ) { return false; } - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } + public function getChildren() { + return false; + } + + public function getFirstChild() { + return false; + } + + public function getChildrenOfType( $name ) { + return false; + } + + public function splitArg() { + throw new MWException( __METHOD__ . ': not supported' ); + } + + public function splitExt() { + throw new MWException( __METHOD__ . ': not supported' ); + } + + public function splitHeading() { + throw new MWException( __METHOD__ . ': not supported' ); + } } /** * @ingroup Parser + * @codingStandardsIgnoreStart */ class PPNode_Hash_Attr implements PPNode { - var $name, $value, $nextSibling; + // @codingStandardsIgnoreEnd + + public $name, $value, $nextSibling; - function __construct( $name, $value ) { + public function __construct( $name, $value ) { $this->name = $name; $this->value = $value; } - function __toString() { + public function __toString() { return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "name}>"; } - function getName() { + public function getName() { return $this->name; } - function getNextSibling() { + public function getNextSibling() { return $this->nextSibling; } - function getChildren() { return false; } - function getFirstChild() { return false; } - function getChildrenOfType( $name ) { return false; } - function getLength() { return false; } - function item( $i ) { return false; } - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } + public function getChildren() { + return false; + } + + public function getFirstChild() { + return false; + } + + public function getChildrenOfType( $name ) { + return false; + } + + public function getLength() { + return false; + } + + public function item( $i ) { + return false; + } + + public function splitArg() { + throw new MWException( __METHOD__ . ': not supported' ); + } + + public function splitExt() { + throw new MWException( __METHOD__ . ': not supported' ); + } + + public function splitHeading() { + throw new MWException( __METHOD__ . ': not supported' ); + } } diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php index 5f3f18ea..5d1743e6 100644 --- a/includes/parser/StripState.php +++ b/includes/parser/StripState.php @@ -37,9 +37,9 @@ class StripState { const UNSTRIP_RECURSION_LIMIT = 20; /** - * @param $prefix string + * @param string $prefix */ - function __construct( $prefix ) { + public function __construct( $prefix ) { $this->prefix = $prefix; $this->data = array( 'nowiki' => array(), @@ -51,26 +51,26 @@ class StripState { /** * Add a nowiki strip item - * @param $marker - * @param $value + * @param string $marker + * @param string $value */ - function addNoWiki( $marker, $value ) { + public function addNoWiki( $marker, $value ) { $this->addItem( 'nowiki', $marker, $value ); } /** - * @param $marker - * @param $value + * @param string $marker + * @param string $value */ - function addGeneral( $marker, $value ) { + public function addGeneral( $marker, $value ) { $this->addItem( 'general', $marker, $value ); } /** * @throws MWException - * @param $type - * @param $marker - * @param $value + * @param string $type + * @param string $marker + * @param string $value */ protected function addItem( $type, $marker, $value ) { if ( !preg_match( $this->regex, $marker, $m ) ) { @@ -81,34 +81,34 @@ class StripState { } /** - * @param $text + * @param string $text * @return mixed */ - function unstripGeneral( $text ) { + public function unstripGeneral( $text ) { return $this->unstripType( 'general', $text ); } /** - * @param $text + * @param string $text * @return mixed */ - function unstripNoWiki( $text ) { + public function unstripNoWiki( $text ) { return $this->unstripType( 'nowiki', $text ); } /** - * @param $text + * @param string $text * @return mixed */ - function unstripBoth( $text ) { + public function unstripBoth( $text ) { $text = $this->unstripType( 'general', $text ); $text = $this->unstripType( 'nowiki', $text ); return $text; } /** - * @param $type - * @param $text + * @param string $type + * @param string $text * @return mixed */ protected function unstripType( $type, $text ) { @@ -127,7 +127,7 @@ class StripState { } /** - * @param $m array + * @param array $m * @return array */ protected function unstripCallback( $m ) { @@ -159,11 +159,11 @@ class StripState { * Get a StripState object which is sufficient to unstrip the given text. * It will contain the minimum subset of strip items necessary. * - * @param $text string + * @param string $text * * @return StripState */ - function getSubState( $text ) { + public function getSubState( $text ) { $subState = new StripState( $this->prefix ); $pos = 0; while ( true ) { @@ -195,11 +195,11 @@ class StripState { * will not be preserved. The strings in the $texts array will have their * strip markers rewritten, the resulting array of strings will be returned. * - * @param $otherState StripState - * @param $texts Array - * @return Array + * @param StripState $otherState + * @param array $texts + * @return array */ - function merge( $otherState, $texts ) { + public function merge( $otherState, $texts ) { $mergePrefix = Parser::getRandomString(); foreach ( $otherState->data as $type => $items ) { @@ -215,7 +215,7 @@ class StripState { } /** - * @param $m + * @param array $m * @return string */ protected function mergeCallback( $m ) { @@ -226,10 +226,10 @@ class StripState { /** * Remove any strip markers found in the given text. * - * @param $text Input string + * @param string $text Input string * @return string */ - function killMarkers( $text ) { + public function killMarkers( $text ) { return preg_replace( $this->regex, '', $text ); } } diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php deleted file mode 100644 index 32b16aaf..00000000 --- a/includes/parser/Tidy.php +++ /dev/null @@ -1,286 +0,0 @@ -mTokens = null; - $this->mUniqPrefix = null; - } - - /** - * @param $text string - * @return string - */ - public function getWrapped( $text ) { - $this->mTokens = new ReplacementArray; - $this->mUniqPrefix = "\x7fUNIQ" . - dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) ); - $this->mMarkerIndex = 0; - - // Replace elements with placeholders - $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX, - array( &$this, 'replaceCallback' ), $text ); - // ...and markers - $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/', - array( &$this, 'replaceCallback' ), $wrappedtext ); - - // Modify inline Microdata and elements so they say and so - // we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config - $wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '' . - 'test' . $wrappedtext . ''; - - return $wrappedtext; - } - - /** - * @param $m array - * - * @return string - */ - function replaceCallback( $m ) { - $marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX; - $this->mMarkerIndex++; - $this->mTokens->setPair( $marker, $m[0] ); - return $marker; - } - - /** - * @param $text string - * @return string - */ - public function postprocess( $text ) { - // Revert back to <{link,meta}> - $text = preg_replace( '!]*?)(/{0,1}>)!', '<$1$2$3', $text ); - - // Restore the contents of placeholder tokens - $text = $this->mTokens->replace( $text ); - - return $text; - } - -} - -/** - * Class to interact with HTML tidy - * - * Either the external tidy program or the in-process tidy extension - * will be used depending on availability. Override the default - * $wgTidyInternal setting to disable the internal if it's not working. - * - * @ingroup Parser - */ -class MWTidy { - /** - * Interface with html tidy, used if $wgUseTidy = true. - * If tidy isn't able to correct the markup, the original will be - * returned in all its glory with a warning comment appended. - * - * @param string $text hideous HTML input - * @return String: corrected HTML output - */ - public static function tidy( $text ) { - global $wgTidyInternal; - - $wrapper = new MWTidyWrapper; - $wrappedtext = $wrapper->getWrapped( $text ); - - $retVal = null; - if ( $wgTidyInternal ) { - $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal ); - } else { - $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal ); - } - - if ( $retVal < 0 ) { - wfDebug( "Possible tidy configuration error!\n" ); - return $text . "\n\n"; - } elseif ( is_null( $correctedtext ) ) { - wfDebug( "Tidy error detected!\n" ); - return $text . "\n\n"; - } - - $correctedtext = $wrapper->postprocess( $correctedtext ); // restore any hidden tokens - - return $correctedtext; - } - - /** - * Check HTML for errors, used if $wgValidateAllHtml = true. - * - * @param $text String - * @param &$errorStr String: return the error string - * @return Boolean: whether the HTML is valid - */ - public static function checkErrors( $text, &$errorStr = null ) { - global $wgTidyInternal; - - $retval = 0; - if ( $wgTidyInternal ) { - $errorStr = self::execInternalTidy( $text, true, $retval ); - } else { - $errorStr = self::execExternalTidy( $text, true, $retval ); - } - - return ( $retval < 0 && $errorStr == '' ) || $retval == 0; - } - - /** - * Spawn an external HTML tidy process and get corrected markup back from it. - * Also called in OutputHandler.php for full page validation - * - * @param string $text HTML to check - * @param $stderr Boolean: Whether to read result from STDERR rather than STDOUT - * @param &$retval int Exit code (-1 on internal error) - * @return mixed String or null - */ - private static function execExternalTidy( $text, $stderr = false, &$retval = null ) { - global $wgTidyConf, $wgTidyBin, $wgTidyOpts; - wfProfileIn( __METHOD__ ); - - $cleansource = ''; - $opts = ' -utf8'; - - if ( $stderr ) { - $descriptorspec = array( - 0 => array( 'pipe', 'r' ), - 1 => array( 'file', wfGetNull(), 'a' ), - 2 => array( 'pipe', 'w' ) - ); - } else { - $descriptorspec = array( - 0 => array( 'pipe', 'r' ), - 1 => array( 'pipe', 'w' ), - 2 => array( 'file', wfGetNull(), 'a' ) - ); - } - - $readpipe = $stderr ? 2 : 1; - $pipes = array(); - - $process = proc_open( - "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes ); - - if ( is_resource( $process ) ) { - // Theoretically, this style of communication could cause a deadlock - // here. If the stdout buffer fills up, then writes to stdin could - // block. This doesn't appear to happen with tidy, because tidy only - // writes to stdout after it's finished reading from stdin. Search - // for tidyParseStdin and tidySaveStdout in console/tidy.c - fwrite( $pipes[0], $text ); - fclose( $pipes[0] ); - while ( !feof( $pipes[$readpipe] ) ) { - $cleansource .= fgets( $pipes[$readpipe], 1024 ); - } - fclose( $pipes[$readpipe] ); - $retval = proc_close( $process ); - } else { - wfWarn( "Unable to start external tidy process" ); - $retval = -1; - } - - if ( !$stderr && $cleansource == '' && $text != '' ) { - // Some kind of error happened, so we couldn't get the corrected text. - // Just give up; we'll use the source text and append a warning. - $cleansource = null; - } - - wfProfileOut( __METHOD__ ); - return $cleansource; - } - - /** - * Use the HTML tidy extension to use the tidy library in-process, - * saving the overhead of spawning a new process. - * - * @param string $text HTML to check - * @param $stderr Boolean: Whether to read result from error status instead of output - * @param &$retval int Exit code (-1 on internal error) - * @return mixed String or null - */ - private static function execInternalTidy( $text, $stderr = false, &$retval = null ) { - global $wgTidyConf, $wgDebugTidy; - wfProfileIn( __METHOD__ ); - - if ( !class_exists( 'tidy' ) ) { - wfWarn( "Unable to load internal tidy class." ); - $retval = -1; - - wfProfileOut( __METHOD__ ); - return null; - } - - $tidy = new tidy; - $tidy->parseString( $text, $wgTidyConf, 'utf8' ); - - if ( $stderr ) { - $retval = $tidy->getStatus(); - - wfProfileOut( __METHOD__ ); - return $tidy->errorBuffer; - } - - $tidy->cleanRepair(); - $retval = $tidy->getStatus(); - if ( $retval == 2 ) { - // 2 is magic number for fatal error - // http://www.php.net/manual/en/function.tidy-get-status.php - $cleansource = null; - } else { - $cleansource = tidy_get_output( $tidy ); - if ( $wgDebugTidy && $retval > 0 ) { - $cleansource .= "', '-->', $tidy->errorBuffer ) . - "\n-->"; - } - } - - wfProfileOut( __METHOD__ ); - return $cleansource; - } -} -- cgit v1.2.2