summaryrefslogtreecommitdiff
path: root/includes/parser
diff options
context:
space:
mode:
Diffstat (limited to 'includes/parser')
-rw-r--r--includes/parser/CacheTime.php26
-rw-r--r--includes/parser/CoreParserFunctions.php92
-rw-r--r--includes/parser/CoreTagHooks.php27
-rw-r--r--includes/parser/DateFormatter.php15
-rw-r--r--includes/parser/LinkHolderArray.php25
-rw-r--r--includes/parser/MWTidy.php76
-rw-r--r--includes/parser/Parser.php686
-rw-r--r--includes/parser/ParserCache.php7
-rw-r--r--includes/parser/ParserOptions.php97
-rw-r--r--includes/parser/ParserOutput.php141
-rw-r--r--includes/parser/Preprocessor_DOM.php30
-rw-r--r--includes/parser/Preprocessor_Hash.php20
-rw-r--r--includes/parser/StripState.php2
13 files changed, 710 insertions, 534 deletions
diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php
index 94abc266..950c0d46 100644
--- a/includes/parser/CacheTime.php
+++ b/includes/parser/CacheTime.php
@@ -34,8 +34,7 @@ class CacheTime {
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}}
+ $mCacheExpiry = null, # Seconds after which the object should expire, use 0 for uncacheable. Used in ParserCache.
$mCacheRevisionId = null; # Revision ID that was parsed
/**
@@ -46,21 +45,6 @@ class CacheTime {
}
/**
- * @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 does not control expiry, see updateCacheExpiry() for that!
* @param string $t
@@ -123,7 +107,7 @@ class CacheTime {
if ( $this->mCacheTime < 0 ) {
return 0;
- } // old-style marker for "not cachable"
+ } // old-style marker for "not cacheable"
$expire = $this->mCacheExpiry;
@@ -133,12 +117,8 @@ class CacheTime {
$expire = min( $expire, $wgParserCacheExpireTime );
}
- if ( $this->containsOldMagic() ) { //compatibility hack
- $expire = min( $expire, 3600 ); # 1 hour
- }
-
if ( $expire <= 0 ) {
- return 0; // not cachable
+ return 0; // not cacheable
} else {
return $expire;
}
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index eacbecd4..830a68fc 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -36,7 +36,7 @@ class CoreParserFunctions {
# 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:...}}
+ # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
# instead of {{#int:...}})
$noHashFunctions = array(
'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
@@ -44,7 +44,7 @@ class CoreParserFunctions {
'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural',
'numberofpages', 'numberofusers', 'numberofactiveusers',
'numberofarticles', 'numberoffiles', 'numberofadmins',
- 'numberingroup', 'numberofedits', 'numberofviews', 'language',
+ 'numberingroup', 'numberofedits', 'language',
'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
'pagesincategory', 'pagesize', 'protectionlevel',
'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
@@ -57,24 +57,24 @@ class CoreParserFunctions {
'revisiontimestamp', 'revisionuser', 'cascadingsources',
);
foreach ( $noHashFunctions as $func ) {
- $parser->setFunctionHook( $func, array( __CLASS__, $func ), SFH_NO_HASH );
+ $parser->setFunctionHook( $func, array( __CLASS__, $func ), Parser::SFH_NO_HASH );
}
- $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), Parser::SFH_NO_HASH );
+ $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), Parser::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( 'tag', array( __CLASS__, 'tagObj' ), Parser::SFH_OBJECT_ARGS );
$parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) );
if ( $wgAllowDisplayTitle ) {
- $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), Parser::SFH_NO_HASH );
}
if ( $wgAllowSlowParserFunctions ) {
$parser->setFunctionHook(
'pagesinnamespace',
array( __CLASS__, 'pagesinnamespace' ),
- SFH_NO_HASH
+ Parser::SFH_NO_HASH
);
}
}
@@ -111,7 +111,7 @@ class CoreParserFunctions {
$pref = $parser->getOptions()->getDateFormat();
- // Specify a different default date format other than the the normal default
+ // Specify a different default date format other than the normal default
// if the user has 'default' for their setting
if ( $pref == 'default' && $defaultPref ) {
$pref = $defaultPref;
@@ -309,15 +309,12 @@ class CoreParserFunctions {
* @return string
*/
public static function gender( $parser, $username ) {
- wfProfileIn( __METHOD__ );
$forms = array_slice( func_get_args(), 2 );
// Some shortcuts to avoid loading user data unnecessarily
if ( count( $forms ) === 0 ) {
- wfProfileOut( __METHOD__ );
return '';
} elseif ( count( $forms ) === 1 ) {
- wfProfileOut( __METHOD__ );
return $forms[0];
}
@@ -341,7 +338,6 @@ class CoreParserFunctions {
$gender = GenderCache::singleton()->getGenderOf( $parser->getOptions()->getUser(), __METHOD__ );
}
$ret = $parser->getFunctionLang()->gender( $gender, $forms );
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -379,8 +375,7 @@ class CoreParserFunctions {
$text = $parser->doQuotes( $text );
// remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
- $text = preg_replace( '/' . preg_quote( $parser->uniqPrefix(), '/' ) . '.*?'
- . preg_quote( Parser::MARKER_SUFFIX, '/' ) . '/', '', $text );
+ $text = $parser->killMarkers( $text );
// list of disallowed tags for DISPLAYTITLE
// these will be escaped even though they are allowed in normal wiki text
@@ -489,10 +484,6 @@ class CoreParserFunctions {
public static function numberofedits( $parser, $raw = null ) {
return self::formatRaw( SiteStats::edits(), $raw );
}
- public static function numberofviews( $parser, $raw = null ) {
- global $wgDisableCounters;
- return !$wgDisableCounters ? self::formatRaw( SiteStats::views(), $raw ) : '';
- }
public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw );
}
@@ -902,9 +893,17 @@ 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}}.
+ *
+ * @param Parser $parser
+ * @param string $name
+ * @param string $argA
+ * @param string $argB
+ * @return array|string
+ */
public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
$file = wfFindFile( $name );
@@ -943,7 +942,7 @@ class CoreParserFunctions {
* Parser function to extension tag adaptor
* @param Parser $parser
* @param PPFrame $frame
- * @param array $args
+ * @param PPNode[] $args
* @return string
*/
public static function tagObj( $parser, $frame, $args ) {
@@ -958,13 +957,6 @@ class CoreParserFunctions {
$inner = null;
}
- $stripList = $parser->getStripList();
- if ( !in_array( $tagName, $stripList ) ) {
- return '<span class="error">' .
- wfMessage( 'unknown_extension_tag', $tagName )->inContentLanguage()->text() .
- '</span>';
- }
-
$attributes = array();
foreach ( $args as $arg ) {
$bits = $arg->splitArg();
@@ -978,6 +970,19 @@ class CoreParserFunctions {
}
}
+ $stripList = $parser->getStripList();
+ if ( !in_array( $tagName, $stripList ) ) {
+ // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
+ $attrText = '';
+ foreach ( $attributes as $name => $value ) {
+ $attrText .= ' ' . htmlspecialchars( $name ) . '="' . htmlspecialchars( $value ) . '"';
+ }
+ if ( $inner === null ) {
+ return "<$tagName$attrText/>";
+ }
+ return "<$tagName$attrText>$inner</$tagName>";
+ }
+
$params = array(
'name' => $tagName,
'inner' => $inner,
@@ -1000,11 +1005,6 @@ class CoreParserFunctions {
* @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;
}
@@ -1024,22 +1024,18 @@ class CoreParserFunctions {
// Normalize name for cache
$page = $title->getPrefixedDBkey();
- if ( $cache->has( $page ) ) { // cache contains null values
- return $cache->get( $page );
+ if ( !( $parser->currentRevisionCache && $parser->currentRevisionCache->has( $page ) )
+ && !$parser->incrementExpensiveFunctionCount() ) {
+ return null;
}
- 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
+ $rev = $parser->fetchCurrentRevisionOfTitle( $title );
+ $pageID = $rev ? $rev->getPage() : 0;
+ $revID = $rev ? $rev->getId() : 0;
- // Register dependency in templatelinks
- $parser->getOutput()->addTemplate( $title, $pageID, $revID );
+ // Register dependency in templatelinks
+ $parser->getOutput()->addTemplate( $title, $pageID, $revID );
- return $rev;
- }
- $cache->set( $page, null );
- return null;
+ return $rev;
}
/**
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index 85920cc1..9755ea93 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -35,6 +35,7 @@ class CoreTagHooks {
$parser->setHook( 'pre', array( __CLASS__, 'pre' ) );
$parser->setHook( 'nowiki', array( __CLASS__, 'nowiki' ) );
$parser->setHook( 'gallery', array( __CLASS__, 'gallery' ) );
+ $parser->setHook( 'indicator', array( __CLASS__, 'indicator' ) );
if ( $wgRawHtml ) {
$parser->setHook( 'html', array( __CLASS__, 'html' ) );
}
@@ -119,4 +120,30 @@ class CoreTagHooks {
public static function gallery( $content, $attributes, $parser ) {
return $parser->renderImageGallery( $content, $attributes );
}
+
+ /**
+ * XML-style tag for page status indicators: icons (or short text snippets) usually displayed in
+ * the top-right corner of the page, outside of the main content.
+ *
+ * @param string $content
+ * @param array $attributes
+ * @param Parser $parser
+ * @param PPFrame $frame
+ * @return string
+ * @since 1.25
+ */
+ public static function indicator( $content, array $attributes, Parser $parser, PPFrame $frame ) {
+ if ( !isset( $attributes['name'] ) || trim( $attributes['name'] ) === '' ) {
+ return '<span class="error">' .
+ wfMessage( 'invalid-indicator-name' )->inContentLanguage()->parse() .
+ '</span>';
+ }
+
+ $parser->getOutput()->setIndicator(
+ trim( $attributes['name'] ),
+ Parser::stripOuterParagraph( $parser->recursiveTagParseFully( $content, $frame ) )
+ );
+
+ return '';
+ }
}
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index 82f0e9d4..ef295ab2 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -33,7 +33,7 @@ class DateFormatter {
public $regexes, $pDays, $pMonths, $pYears;
public $rules, $xMonths, $preferences;
- protected $lang;
+ protected $lang, $mLinked;
const ALL = -1;
const NONE = 0;
@@ -315,8 +315,8 @@ class DateFormatter {
}
/**
- * @todo document
- * @return string
+ * Return a regex that can be used to find month names in string
+ * @return string regex to find the months with
*/
public function getMonthRegex() {
$names = array();
@@ -338,7 +338,7 @@ class DateFormatter {
}
/**
- * @todo document
+ * Make an ISO year from a year name, for instance: '-1199' from '1200 BC'
* @param string $year Year name
* @return string ISO year name
*/
@@ -356,9 +356,10 @@ class DateFormatter {
}
/**
- * @todo document
- * @param string $iso
- * @return int|string
+ * Make a year one from an ISO year, for instance: '400 BC' from '-0399'.
+ * @param string $iso ISO year
+ * @return int|string int representing year number in case of AD dates, or string containing
+ * year number and 'BC' at the end otherwise.
*/
public function makeNormalYear( $iso ) {
if ( $iso[0] == '-' ) {
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index 7794fae4..7026c5ce 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -229,7 +229,6 @@ class LinkHolderArray {
* @return string
*/
public function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
- wfProfileIn( __METHOD__ );
if ( !is_object( $nt ) ) {
# Fail gracefully
$retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
@@ -259,7 +258,6 @@ class LinkHolderArray {
}
$this->size++;
}
- wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -267,17 +265,12 @@ class LinkHolderArray {
* Replace <!--LINK--> link placeholders with actual links, in the buffer
*
* @param string $text
- * @return array Array of link CSS classes, indexed by PDBK.
*/
public function replace( &$text ) {
- wfProfileIn( __METHOD__ );
- /** @todo FIXME: replaceInternal doesn't return a value */
- $colours = $this->replaceInternal( $text );
+ $this->replaceInternal( $text );
$this->replaceInterwiki( $text );
- wfProfileOut( __METHOD__ );
- return $colours;
}
/**
@@ -289,14 +282,12 @@ class LinkHolderArray {
return;
}
- wfProfileIn( __METHOD__ );
global $wgContLang, $wgContentHandlerUseDB;
$colours = array();
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
- wfProfileIn( __METHOD__ . '-check' );
$dbr = wfGetDB( DB_SLAVE );
$threshold = $this->parent->getOptions()->getStubThreshold();
@@ -380,9 +371,8 @@ class LinkHolderArray {
}
if ( count( $linkcolour_ids ) ) {
//pass an array of page_ids to an extension
- wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ Hooks::run( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
}
- wfProfileOut( __METHOD__ . '-check' );
# Do a second query for different language variants of links and categories
if ( $wgContLang->hasVariants() ) {
@@ -390,7 +380,6 @@ class LinkHolderArray {
}
# Construct search and replace arrays
- wfProfileIn( __METHOD__ . '-construct' );
$replacePairs = array();
foreach ( $this->internals as $ns => $entries ) {
foreach ( $entries as $index => $entry ) {
@@ -426,18 +415,14 @@ class LinkHolderArray {
}
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
- wfProfileOut( __METHOD__ . '-construct' );
# Do the thing
- wfProfileIn( __METHOD__ . '-replace' );
$text = preg_replace_callback(
'/(<!--LINK .*?-->)/',
$replacer->cb(),
$text
);
- wfProfileOut( __METHOD__ . '-replace' );
- wfProfileOut( __METHOD__ );
}
/**
@@ -449,7 +434,6 @@ class LinkHolderArray {
return;
}
- wfProfileIn( __METHOD__ );
# Make interwiki link HTML
$output = $this->parent->getOutput();
$replacePairs = array();
@@ -463,7 +447,6 @@ class LinkHolderArray {
'/<!--IWLINK (.*?)-->/',
$replacer->cb(),
$text );
- wfProfileOut( __METHOD__ );
}
/**
@@ -617,7 +600,7 @@ class LinkHolderArray {
}
}
}
- wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ Hooks::run( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
// rebuild the categories in original order (if there are replacements)
if ( count( $varCategories ) > 0 ) {
@@ -644,14 +627,12 @@ class LinkHolderArray {
* @return string
*/
public function replaceText( $text ) {
- wfProfileIn( __METHOD__ );
$text = preg_replace_callback(
'/<!--(LINK|IWLINK) (.*?)-->/',
array( &$this, 'replaceTextCallback' ),
$text );
- wfProfileOut( __METHOD__ );
return $text;
}
diff --git a/includes/parser/MWTidy.php b/includes/parser/MWTidy.php
index b310862f..d446ccf6 100644
--- a/includes/parser/MWTidy.php
+++ b/includes/parser/MWTidy.php
@@ -127,17 +127,11 @@ class MWTidy {
* @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 );
- }
+ $correctedtext = self::clean( $wrappedtext, false, $retVal );
if ( $retVal < 0 ) {
wfDebug( "Possible tidy configuration error!\n" );
@@ -160,16 +154,34 @@ class MWTidy {
* @return bool Whether the HTML is valid
*/
public static function checkErrors( $text, &$errorStr = null ) {
+ $retval = 0;
+ $errorStr = self::clean( $text, true, $retval );
+ return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
+ }
+
+ /**
+ * Perform a clean/repair operation
+ * @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 null|string
+ * @throws MWException
+ */
+ private static function clean( $text, $stderr = false, &$retval = null ) {
global $wgTidyInternal;
- $retval = 0;
if ( $wgTidyInternal ) {
- $errorStr = self::execInternalTidy( $text, true, $retval );
+ if ( wfIsHHVM() ) {
+ if ( $stderr ) {
+ throw new MWException( __METHOD__ . ": error text return from HHVM tidy is not supported" );
+ }
+ return self::hhvmClean( $text, $retval );
+ } else {
+ return self::phpClean( $text, $stderr, $retval );
+ }
} else {
- $errorStr = self::execExternalTidy( $text, true, $retval );
+ return self::externalClean( $text, $stderr, $retval );
}
-
- return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
}
/**
@@ -181,9 +193,8 @@ class MWTidy {
* @param int &$retval Exit code (-1 on internal error)
* @return string|null
*/
- private static function execExternalTidy( $text, $stderr = false, &$retval = null ) {
+ private static function externalClean( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
- wfProfileIn( __METHOD__ );
$cleansource = '';
$opts = ' -utf8';
@@ -235,7 +246,6 @@ class MWTidy {
$cleansource = null;
}
- wfProfileOut( __METHOD__ );
return $cleansource;
}
@@ -248,15 +258,15 @@ class MWTidy {
* @param int &$retval Exit code (-1 on internal error)
* @return string|null
*/
- private static function execInternalTidy( $text, $stderr = false, &$retval = null ) {
+ private static function phpClean( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgDebugTidy;
- wfProfileIn( __METHOD__ );
- if ( !class_exists( 'tidy' ) ) {
+ if ( ( !wfIsHHVM() && !class_exists( 'tidy' ) ) ||
+ ( wfIsHHVM() && !function_exists( 'tidy_repair_string' ) )
+ ) {
wfWarn( "Unable to load internal tidy class." );
$retval = -1;
- wfProfileOut( __METHOD__ );
return null;
}
@@ -265,8 +275,6 @@ class MWTidy {
if ( $stderr ) {
$retval = $tidy->getStatus();
-
- wfProfileOut( __METHOD__ );
return $tidy->errorBuffer;
}
@@ -285,7 +293,31 @@ class MWTidy {
}
}
- wfProfileOut( __METHOD__ );
+ return $cleansource;
+ }
+
+ /**
+ * Use the tidy extension for HHVM from
+ * https://github.com/wikimedia/mediawiki-php-tidy
+ *
+ * This currently does not support the object-oriented interface, but
+ * tidy_repair_string() can be used for the most common tasks.
+ *
+ * @param string $text HTML to check
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return string|null
+ */
+ private static function hhvmClean( $text, &$retval ) {
+ global $wgTidyConf;
+
+ $cleansource = tidy_repair_string( $text, $wgTidyConf, 'utf8' );
+ if ( $cleansource === false ) {
+ $cleansource = null;
+ $retval = -1;
+ } else {
+ $retval = 0;
+ }
+
return $cleansource;
}
}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 84bb2243..ace63a09 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -35,18 +35,18 @@
*
* - Parser::parse()
* produces HTML output
- * - Parser::preSaveTransform().
- * produces altered wiki markup.
+ * - Parser::preSaveTransform()
+ * produces altered wiki markup
* - Parser::preprocess()
* removes HTML comments and expands templates
* - Parser::cleanSig() and Parser::cleanSigInSig()
- * Cleans a signature before saving it to preferences
+ * cleans a signature before saving it to preferences
* - Parser::getSection()
- * Return the content of a section from an article for section editing
+ * return the content of a section from an article for section editing
* - Parser::replaceSection()
- * Replaces a section by number inside an article
+ * replaces a section by number inside an article
* - Parser::getPreloadText()
- * Removes <noinclude> sections, and <includeonly> tags.
+ * removes <noinclude> sections and <includeonly> tags
*
* Globals used:
* object: $wgContLang
@@ -79,7 +79,6 @@ class Parser {
const HALF_PARSED_VERSION = 2;
# Flags for Parser::setFunctionHook
- # Also available as global constants from Defines.php
const SFH_NO_HASH = 1;
const SFH_OBJECT_ARGS = 2;
@@ -91,6 +90,9 @@ class Parser {
const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
\\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
+ # Regular expression for a non-newline space
+ const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
+
# State constants for the definition list colon extraction
const COLON_STATE_TEXT = 0;
const COLON_STATE_TAG = 1;
@@ -144,7 +146,12 @@ class Parser {
* @var MagicWordArray
*/
public $mSubstWords;
- public $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
+ # Initialised in constructor
+ public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
+
+ # Initialized in getPreprocessor()
+ /** @var Preprocessor */
+ public $mPreprocessor;
# Cleared with clearState():
/**
@@ -211,11 +218,22 @@ class Parser {
public $mLangLinkLanguages;
/**
+ * @var MapCacheLRU|null
+ * @since 1.24
+ *
+ * A cache of the current revisions of titles. Keys are $title->getPrefixedDbKey()
+ */
+ public $currentRevisionCache;
+
+ /**
* @var bool Recursive call protection.
* This variable should be treated as if it were private.
*/
public $mInParse = false;
+ /** @var SectionProfiler */
+ protected $mProfiler;
+
/**
* @param array $conf
*/
@@ -258,7 +276,22 @@ class Parser {
*/
public function __clone() {
$this->mInParse = false;
- wfRunHooks( 'ParserCloned', array( $this ) );
+
+ // Bug 56226: When you create a reference "to" an object field, that
+ // makes the object field itself be a reference too (until the other
+ // reference goes out of scope). When cloning, any field that's a
+ // reference is copied as a reference in the new object. Both of these
+ // are defined PHP5 behaviors, as inconvenient as it is for us when old
+ // hooks from PHP4 days are passing fields by reference.
+ foreach ( array( 'mStripState', 'mVarCache' ) as $k ) {
+ // Make a non-reference copy of the field, then rebind the field to
+ // reference the new copy.
+ $tmp = $this->$k;
+ $this->$k =& $tmp;
+ unset( $tmp );
+ }
+
+ Hooks::run( 'ParserCloned', array( $this ) );
}
/**
@@ -270,14 +303,11 @@ class Parser {
}
$this->mFirstCall = false;
- wfProfileIn( __METHOD__ );
-
CoreParserFunctions::register( $this );
CoreTagHooks::register( $this );
$this->initialiseVariables();
- wfRunHooks( 'ParserFirstCallInit', array( &$this ) );
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'ParserFirstCallInit', array( &$this ) );
}
/**
@@ -286,7 +316,6 @@ class Parser {
* @private
*/
public function clearState() {
- wfProfileIn( __METHOD__ );
if ( $this->mFirstCall ) {
$this->firstCallInit();
}
@@ -305,6 +334,7 @@ class Parser {
$this->mVarCache = array();
$this->mUser = null;
$this->mLangLinkLanguages = array();
+ $this->currentRevisionCache = null;
/**
* Prefix for temporary replacement strings for the multipass parser.
@@ -341,8 +371,9 @@ class Parser {
$this->mPreprocessor = null;
}
- wfRunHooks( 'ParserClearState', array( &$this ) );
- wfProfileOut( __METHOD__ );
+ $this->mProfiler = new SectionProfiler();
+
+ Hooks::run( 'ParserClearState', array( &$this ) );
}
/**
@@ -365,10 +396,7 @@ class Parser {
* to internalParse() which does all the real work.
*/
- global $wgUseTidy, $wgAlwaysUseTidy, $wgShowHostnames;
- $fname = __METHOD__ . '-' . wfGetCaller();
- wfProfileIn( __METHOD__ );
- wfProfileIn( $fname );
+ global $wgShowHostnames;
if ( $clearState ) {
$magicScopeVariable = $this->lock();
@@ -376,6 +404,7 @@ class Parser {
$this->startParse( $title, $options, self::OT_HTML, $clearState );
+ $this->currentRevisionCache = null;
$this->mInputSize = strlen( $text );
if ( $this->mOptions->getEnableLimitReport() ) {
$this->mOutput->resetParseStartTime();
@@ -399,46 +428,13 @@ class Parser {
$this->mRevisionSize = null;
}
- wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
# No more strip!
- wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->internalParse( $text );
- wfRunHooks( 'ParserAfterParse', array( &$this, &$text, &$this->mStripState ) );
-
- $text = $this->mStripState->unstripGeneral( $text );
+ Hooks::run( 'ParserAfterParse', array( &$this, &$text, &$this->mStripState ) );
- # Clean up special characters, only run once, next-to-last before doBlockLevels
- $fixtags = array(
- # french spaces, last one Guillemet-left
- # only if there is something before the space
- '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
- # french spaces, Guillemet-right
- '/(\\302\\253) /' => '\\1&#160;',
- '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
- );
- $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
-
- $text = $this->doBlockLevels( $text, $linestart );
-
- $this->replaceLinkHolders( $text );
-
- /**
- * The input doesn't get language converted if
- * a) It's disabled
- * b) Content isn't converted
- * c) It's a conversion table
- * d) it is an interface message (which is in the user language)
- */
- if ( !( $options->getDisableContentConversion()
- || 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
- # is the <nowiki> mark.
- $text = $this->getConverterLanguage()->convert( $text );
- }
- }
+ $text = $this->internalParseHalfParsed( $text, true, $linestart );
/**
* A converted title will be provided in the output object if title and
@@ -461,45 +457,6 @@ class Parser {
}
}
- $text = $this->mStripState->unstripNoWiki( $text );
-
- wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) );
-
- $text = $this->replaceTransparentTags( $text );
- $text = $this->mStripState->unstripGeneral( $text );
-
- $text = Sanitizer::normalizeCharReferences( $text );
-
- if ( ( $wgUseTidy && $this->mOptions->getTidy() ) || $wgAlwaysUseTidy ) {
- $text = MWTidy::tidy( $text );
- } else {
- # attempt to sanitize at least some nesting problems
- # (bug #2702 and quite a few others)
- $tidyregs = array(
- # ''Something [http://www.cool.com cool''] -->
- # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
- '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
- '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
- # fix up an anchor inside another anchor, only
- # at least for a single single nested link (bug 3695)
- '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
- '\\1\\2</a>\\3</a>\\1\\4</a>',
- # fix div inside inline elements- doBlockLevels won't wrap a line which
- # contains a div, so fix it up here; replace
- # div with escaped text
- '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
- '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
- # remove empty italic or bold tag pairs, some
- # introduced by rules above
- '/<([bi])><\/\\1>/' => '',
- );
-
- $text = preg_replace(
- array_keys( $tidyregs ),
- array_values( $tidyregs ),
- $text );
- }
-
if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
$this->limitationWarn( 'expensive-parserfunction',
$this->mExpensiveFunctionCount,
@@ -507,8 +464,6 @@ class Parser {
);
}
- wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
-
# Information on include size limits, for the benefit of users who try to skirt them
if ( $this->mOptions->getEnableLimitReport() ) {
$max = $this->mOptions->getMaxIncludeSize();
@@ -543,14 +498,14 @@ class Parser {
$this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
array( $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() )
);
- wfRunHooks( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
+ Hooks::run( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
$limitReport = "NewPP limit report\n";
if ( $wgShowHostnames ) {
$limitReport .= 'Parsed by ' . wfHostname() . "\n";
}
foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
- if ( wfRunHooks( 'ParserLimitReportFormat',
+ if ( Hooks::run( 'ParserLimitReportFormat',
array( $key, &$value, &$limitReport, false, false )
) ) {
$keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
@@ -568,13 +523,26 @@ class Parser {
// Since we're not really outputting HTML, decode the entities and
// then re-encode the things that need hiding inside HTML comments.
$limitReport = htmlspecialchars_decode( $limitReport );
- wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
+ Hooks::run( 'ParserLimitReport', array( $this, &$limitReport ) );
// Sanitize for comment. Note '‐' in the replacement is U+2010,
// which looks much like the problematic '-'.
$limitReport = str_replace( array( '-', '&' ), array( '‐', '&amp;' ), $limitReport );
$text .= "\n<!-- \n$limitReport-->\n";
+ // Add on template profiling data
+ $dataByFunc = $this->mProfiler->getFunctionStats();
+ uasort( $dataByFunc, function ( $a, $b ) {
+ return $a['real'] < $b['real']; // descending order
+ } );
+ $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n";
+ foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
+ $profileReport .= sprintf( "%6.2f%% %8.3f %6d - %s\n",
+ $item['%real'], $item['real'], $item['calls'],
+ htmlspecialchars( $item['name'] ) );
+ }
+ $text .= "\n<!-- \n$profileReport-->\n";
+
if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
$this->mTitle->getPrefixedDBkey() );
@@ -588,29 +556,60 @@ class Parser {
$this->mRevisionUser = $oldRevisionUser;
$this->mRevisionSize = $oldRevisionSize;
$this->mInputSize = false;
- wfProfileOut( $fname );
- wfProfileOut( __METHOD__ );
+ $this->currentRevisionCache = null;
return $this->mOutput;
}
/**
- * Recursive parser entry point that can be called from an extension tag
- * hook.
+ * Half-parse wikitext to half-parsed HTML. This recursive parser entry point
+ * can be called from an extension tag hook.
+ *
+ * The output of this function IS NOT SAFE PARSED HTML; it is "half-parsed"
+ * instead, which means that lists and links have not been fully parsed yet,
+ * and strip markers are still present.
*
- * If $frame is not provided, then template variables (e.g., {{{1}}}) within $text are not expanded
+ * Use recursiveTagParseFully() to fully parse wikitext to output-safe HTML.
+ *
+ * Use this function if you're a parser tag hook and you want to parse
+ * wikitext before or after applying additional transformations, and you
+ * intend to *return the result as hook output*, which will cause it to go
+ * through the rest of parsing process automatically.
+ *
+ * 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 bool|PPFrame $frame The frame to use for expanding any template variables
- *
- * @return string
+ * @return string UNSAFE half-parsed HTML
*/
public function recursiveTagParse( $text, $frame = false ) {
- wfProfileIn( __METHOD__ );
- wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
- wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->internalParse( $text, false, $frame );
- wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * Fully parse wikitext to fully parsed HTML. This recursive parser entry
+ * point can be called from an extension tag hook.
+ *
+ * The output of this function is fully-parsed HTML that is safe for output.
+ * If you're a parser tag hook, you might want to use recursiveTagParse()
+ * instead.
+ *
+ * If $frame is not provided, then template variables (e.g., {{{1}}}) within
+ * $text are not expanded
+ *
+ * @since 1.25
+ *
+ * @param string $text Text extension wants to have parsed
+ * @param bool|PPFrame $frame The frame to use for expanding any template variables
+ * @return string Fully parsed HTML
+ */
+ public function recursiveTagParseFully( $text, $frame = false ) {
+ $text = $this->recursiveTagParse( $text, $frame );
+ $text = $this->internalParseHalfParsed( $text, false );
return $text;
}
@@ -625,18 +624,18 @@ class Parser {
* @param bool|PPFrame $frame
* @return mixed|string
*/
- public function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null, $frame = false ) {
- wfProfileIn( __METHOD__ );
+ public function preprocess( $text, Title $title = null,
+ ParserOptions $options, $revid = null, $frame = false
+ ) {
$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 ) );
+ Hooks::run( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->replaceVariables( $text, $frame );
$text = $this->mStripState->unstripBoth( $text );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -650,10 +649,8 @@ class Parser {
* @since 1.19
*/
public function recursivePreprocess( $text, $frame = false ) {
- wfProfileIn( __METHOD__ );
$text = $this->replaceVariables( $text, $frame );
$text = $this->mStripState->unstripBoth( $text );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -1008,7 +1005,6 @@ class Parser {
* @return string
*/
public function doTableStuff( $text ) {
- wfProfileIn( __METHOD__ );
$lines = StringUtils::explode( "\n", $text );
$out = '';
@@ -1195,31 +1191,27 @@ class Parser {
$out = '';
}
- wfProfileOut( __METHOD__ );
-
return $out;
}
/**
- * Helper function for parse() that transforms wiki markup into
+ * Helper function for parse() that transforms wiki markup into half-parsed
* HTML. Only called for $mOutputType == self::OT_HTML.
*
* @private
*
* @param string $text
* @param bool $isMain
- * @param bool $frame
+ * @param PPFrame|bool $frame
*
* @return string
*/
public function internalParse( $text, $isMain = true, $frame = false ) {
- wfProfileIn( __METHOD__ );
$origText = $text;
# Hook to suspend the parser in this state
- if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
- wfProfileOut( __METHOD__ );
+ if ( !Hooks::run( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
return $text;
}
@@ -1239,14 +1231,14 @@ class Parser {
$text = $this->replaceVariables( $text );
}
- wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
$text = Sanitizer::removeHTMLtags(
$text,
array( &$this, 'attributeStripCallback' ),
false,
array_keys( $this->mTransparentTagHooks )
);
- wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
# Tables need to come after variable replacement for things to work
# properly; putting them before other transformations should keep
@@ -1270,7 +1262,101 @@ class Parser {
$text = $this->doMagicLinks( $text );
$text = $this->formatHeadings( $text, $origText, $isMain );
- wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * Helper function for parse() that transforms half-parsed HTML into fully
+ * parsed HTML.
+ *
+ * @param string $text
+ * @param bool $isMain
+ * @param bool $linestart
+ * @return string
+ */
+ private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
+ global $wgUseTidy, $wgAlwaysUseTidy;
+
+ $text = $this->mStripState->unstripGeneral( $text );
+
+ # Clean up special characters, only run once, next-to-last before doBlockLevels
+ $fixtags = array(
+ # french spaces, last one Guillemet-left
+ # only if there is something before the space
+ '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
+ # french spaces, Guillemet-right
+ '/(\\302\\253) /' => '\\1&#160;',
+ '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
+ );
+ $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
+
+ $text = $this->doBlockLevels( $text, $linestart );
+
+ $this->replaceLinkHolders( $text );
+
+ /**
+ * The input doesn't get language converted if
+ * a) It's disabled
+ * b) Content isn't converted
+ * c) It's a conversion table
+ * d) it is an interface message (which is in the user language)
+ */
+ if ( !( $this->mOptions->getDisableContentConversion()
+ || 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
+ # is the <nowiki> mark.
+ $text = $this->getConverterLanguage()->convert( $text );
+ }
+ }
+
+ $text = $this->mStripState->unstripNoWiki( $text );
+
+ if ( $isMain ) {
+ Hooks::run( 'ParserBeforeTidy', array( &$this, &$text ) );
+ }
+
+ $text = $this->replaceTransparentTags( $text );
+ $text = $this->mStripState->unstripGeneral( $text );
+
+ $text = Sanitizer::normalizeCharReferences( $text );
+
+ if ( ( $wgUseTidy && $this->mOptions->getTidy() ) || $wgAlwaysUseTidy ) {
+ $text = MWTidy::tidy( $text );
+ } else {
+ # attempt to sanitize at least some nesting problems
+ # (bug #2702 and quite a few others)
+ $tidyregs = array(
+ # ''Something [http://www.cool.com cool''] -->
+ # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
+ '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
+ '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
+ # fix up an anchor inside another anchor, only
+ # at least for a single single nested link (bug 3695)
+ '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
+ '\\1\\2</a>\\3</a>\\1\\4</a>',
+ # fix div inside inline elements- doBlockLevels won't wrap a line which
+ # contains a div, so fix it up here; replace
+ # div with escaped text
+ '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
+ '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
+ # remove empty italic or bold tag pairs, some
+ # introduced by rules above
+ '/<([bi])><\/\\1>/' => '',
+ );
+
+ $text = preg_replace(
+ array_keys( $tidyregs ),
+ array_values( $tidyregs ),
+ $text );
+ }
+
+ if ( $isMain ) {
+ Hooks::run( 'ParserAfterTidy', array( &$this, &$text ) );
+ }
+
return $text;
}
@@ -1286,22 +1372,24 @@ class Parser {
* @return string
*/
public function doMagicLinks( $text ) {
- wfProfileIn( __METHOD__ );
$prots = wfUrlProtocolsWithoutProtRel();
$urlChar = self::EXT_LINK_URL_CLASS;
+ $space = self::SPACE_NOT_NL; # non-newline space
+ $spdash = "(?:-|$space)"; # a dash or a non-newline space
+ $spaces = "$space++"; # possessive match of 1 or more spaces
$text = preg_replace_callback(
'!(?: # Start cases
(<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
(<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
- (\\b(?i:$prots)$urlChar+) | # m[3]: Free external links" . '
- (?:RFC|PMID)\s+([0-9]+) | # m[4]: RFC or PMID, capture number
- ISBN\s+(\b # m[5]: ISBN, capture number
- (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix
- (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
+ (\b(?i:$prots)$urlChar+) | # m[3]: Free external links
+ \b(?:RFC|PMID) $spaces # m[4]: RFC or PMID, capture number
+ ([0-9]+)\b |
+ \bISBN $spaces ( # m[5]: ISBN, capture number
+ (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
+ (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
[0-9Xx] # check digit
- \b)
- )!xu', array( &$this, 'magicLinkCallback' ), $text );
- wfProfileOut( __METHOD__ );
+ )\b
+ )!xu", array( &$this, 'magicLinkCallback' ), $text );
return $text;
}
@@ -1341,6 +1429,8 @@ class Parser {
} elseif ( isset( $m[5] ) && $m[5] !== '' ) {
# ISBN
$isbn = $m[5];
+ $space = self::SPACE_NOT_NL; # non-newline space
+ $isbn = preg_replace( "/$space/", ' ', $isbn );
$num = strtr( $isbn, array(
'-' => '',
' ' => '',
@@ -1364,7 +1454,6 @@ class Parser {
* @private
*/
public function makeFreeExternalLink( $url ) {
- wfProfileIn( __METHOD__ );
$trail = '';
@@ -1384,7 +1473,20 @@ class Parser {
$sep .= ')';
}
- $numSepChars = strspn( strrev( $url ), $sep );
+ $urlRev = strrev( $url );
+ $numSepChars = strspn( $urlRev, $sep );
+ # Don't break a trailing HTML entity by moving the ; into $trail
+ # This is in hot code, so use substr_compare to avoid having to
+ # create a new string object for the comparison
+ if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0) {
+ # more optimization: instead of running preg_match with a $
+ # anchor, which can be slow, do the match on the reversed
+ # string starting at the desired offset.
+ # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
+ if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
+ $numSepChars--;
+ }
+ }
if ( $numSepChars ) {
$trail = substr( $url, -$numSepChars ) . $trail;
$url = substr( $url, 0, -$numSepChars );
@@ -1405,7 +1507,6 @@ class Parser {
$pasteurized = self::normalizeLinkUrl( $url );
$this->mOutput->addExternalLink( $pasteurized );
}
- wfProfileOut( __METHOD__ );
return $text . $trail;
}
@@ -1419,12 +1520,10 @@ class Parser {
* @return string
*/
public function doHeadings( $text ) {
- wfProfileIn( __METHOD__ );
for ( $i = 6; $i >= 1; --$i ) {
$h = str_repeat( '=', $i );
$text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -1437,14 +1536,12 @@ class Parser {
* @return string The altered text
*/
public function doAllQuotes( $text ) {
- wfProfileIn( __METHOD__ );
$outtext = '';
$lines = StringUtils::explode( "\n", $text );
foreach ( $lines as $line ) {
$outtext .= $this->doQuotes( $line ) . "\n";
}
$outtext = substr( $outtext, 0, -1 );
- wfProfileOut( __METHOD__ );
return $outtext;
}
@@ -1646,11 +1743,9 @@ class Parser {
* @return string
*/
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" );
}
@@ -1714,7 +1809,6 @@ class Parser {
$this->mOutput->addExternalLink( $pasteurized );
}
- wfProfileOut( __METHOD__ );
return $s;
}
@@ -1912,9 +2006,7 @@ class Parser {
*/
public function replaceInternalLinks2( &$s ) {
global $wgExtraInterlanguageLinkPrefixes;
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-setup' );
static $tc = false, $e1, $e1_img;
# the % is needed to support urlencoded titles as well
if ( !$tc ) {
@@ -1946,8 +2038,6 @@ class Parser {
}
if ( is_null( $this->mTitle ) ) {
- wfProfileOut( __METHOD__ . '-setup' );
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
}
$nottalk = !$this->mTitle->isTalkPage();
@@ -1964,7 +2054,6 @@ class Parser {
}
$useSubpages = $this->areSubpagesAllowed();
- wfProfileOut( __METHOD__ . '-setup' );
// @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
# Loop for each link
@@ -1980,7 +2069,6 @@ class Parser {
}
if ( $useLinkPrefixExtension ) {
- wfProfileIn( __METHOD__ . '-prefixhandling' );
if ( preg_match( $e2, $s, $m ) ) {
$prefix = $m[2];
$s = $m[1];
@@ -1992,12 +2080,10 @@ class Parser {
$prefix = $first_prefix;
$first_prefix = false;
}
- wfProfileOut( __METHOD__ . '-prefixhandling' );
}
$might_be_img = false;
- wfProfileIn( __METHOD__ . "-e1" );
if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
$text = $m[2];
# If we get a ] at the beginning of $m[3] that means we have a link that's something like:
@@ -2031,11 +2117,8 @@ class Parser {
$trail = "";
} else { # Invalid form; output directly
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__ . "-e1" );
continue;
}
- wfProfileOut( __METHOD__ . "-e1" );
- wfProfileIn( __METHOD__ . "-misc" );
$origLink = $m[1];
@@ -2044,7 +2127,6 @@ class Parser {
# should be external links.
if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__ . "-misc" );
continue;
}
@@ -2061,21 +2143,16 @@ class Parser {
$link = substr( $link, 1 );
}
- wfProfileOut( __METHOD__ . "-misc" );
- wfProfileIn( __METHOD__ . "-title" );
$nt = Title::newFromText( $this->mStripState->unstripNoWiki( $link ) );
if ( $nt === null ) {
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__ . "-title" );
continue;
}
$ns = $nt->getNamespace();
$iw = $nt->getInterwiki();
- wfProfileOut( __METHOD__ . "-title" );
if ( $might_be_img ) { # if this is actually an invalid link
- wfProfileIn( __METHOD__ . "-might_be_img" );
if ( $ns == NS_FILE && $noforce ) { # but might be an image
$found = false;
while ( true ) {
@@ -2107,16 +2184,13 @@ class Parser {
$holders->merge( $this->replaceInternalLinks2( $text ) );
$s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
- wfProfileOut( __METHOD__ . "-might_be_img" );
continue;
}
} else { # it's not an image, so output it raw
$s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
- wfProfileOut( __METHOD__ . "-might_be_img" );
continue;
}
- wfProfileOut( __METHOD__ . "-might_be_img" );
}
$wasblank = ( $text == '' );
@@ -2133,7 +2207,6 @@ class Parser {
# Link not escaped by : , create the various objects
if ( $noforce && !$nt->wasLocalInterwiki() ) {
# Interwikis
- wfProfileIn( __METHOD__ . "-interwiki" );
if (
$iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
Language::fetchLanguageName( $iw, null, 'mw' ) ||
@@ -2148,13 +2221,10 @@ class Parser {
$s = rtrim( $s . $prefix );
$s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
- wfProfileOut( __METHOD__ . "-interwiki" );
continue;
}
- wfProfileOut( __METHOD__ . "-interwiki" );
if ( $ns == NS_FILE ) {
- wfProfileIn( __METHOD__ . "-image" );
if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
if ( $wasblank ) {
# if no parameters were passed, $text
@@ -2175,12 +2245,10 @@ class Parser {
} else {
$s .= $prefix . $trail;
}
- wfProfileOut( __METHOD__ . "-image" );
continue;
}
if ( $ns == NS_CATEGORY ) {
- wfProfileIn( __METHOD__ . "-category" );
$s = rtrim( $s . "\n" ); # bug 87
if ( $wasblank ) {
@@ -2198,7 +2266,6 @@ class Parser {
*/
$s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
- wfProfileOut( __METHOD__ . "-category" );
continue;
}
}
@@ -2214,22 +2281,19 @@ class Parser {
# NS_MEDIA is a pseudo-namespace for linking directly to a file
# @todo FIXME: Should do batch file existence checks, see comment below
if ( $ns == NS_MEDIA ) {
- wfProfileIn( __METHOD__ . "-media" );
# Give extensions a chance to select the file revision for us
$options = array();
$descQuery = false;
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this, $nt, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
# Cloak with NOPARSE to avoid replacement in replaceExternalLinks
$s .= $prefix . $this->armorLinks(
Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
- wfProfileOut( __METHOD__ . "-media" );
continue;
}
- wfProfileIn( __METHOD__ . "-always_known" );
# Some titles, such as valid special pages or files in foreign repos, should
# be shown as bluelinks even though they're not included in the page table
#
@@ -2242,9 +2306,7 @@ class Parser {
# Links will be added to the output link list after checking
$s .= $holders->makeHolder( $nt, $text, array(), $trail, $prefix );
}
- wfProfileOut( __METHOD__ . "-always_known" );
}
- wfProfileOut( __METHOD__ );
return $holders;
}
@@ -2443,7 +2505,6 @@ class Parser {
* @return string The lists rendered as HTML
*/
public function doBlockLevels( $text, $linestart ) {
- wfProfileIn( __METHOD__ );
# Parsing through the text line by line. The main thing
# happening here is handling of block-level elements p, pre,
@@ -2552,7 +2613,6 @@ class Parser {
# If we have no prefixes, go to paragraph mode.
if ( 0 == $prefixLength ) {
- 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(
@@ -2568,11 +2628,11 @@ class Parser {
$t
);
- if ( $openmatch or $closematch ) {
+ if ( $openmatch || $closematch ) {
$paragraphStack = false;
# @todo bug 5718: paragraph closed
$output .= $this->closeParagraph();
- if ( $preOpenMatch and !$preCloseMatch ) {
+ if ( $preOpenMatch && !$preCloseMatch ) {
$this->mInPre = true;
}
$bqOffset = 0;
@@ -2621,7 +2681,6 @@ class Parser {
}
}
}
- wfProfileOut( __METHOD__ . "-paragraph" );
}
# somewhere above we forget to get out of pre block (bug 785)
if ( $preCloseMatch && $this->mInPre ) {
@@ -2646,7 +2705,6 @@ class Parser {
$this->mLastSection = '';
}
- wfProfileOut( __METHOD__ );
return $output;
}
@@ -2661,12 +2719,10 @@ class Parser {
* @return string The position of the ':', or false if none found
*/
public function findColonNoLinks( $str, &$before, &$after ) {
- wfProfileIn( __METHOD__ );
$pos = strpos( $str, ':' );
if ( $pos === false ) {
# Nothing to find!
- wfProfileOut( __METHOD__ );
return false;
}
@@ -2675,7 +2731,6 @@ class Parser {
# Easy; no tag nesting to worry about
$before = substr( $str, 0, $pos );
$after = substr( $str, $pos + 1 );
- wfProfileOut( __METHOD__ );
return $pos;
}
@@ -2699,7 +2754,6 @@ class Parser {
# We found it!
$before = substr( $str, 0, $i );
$after = substr( $str, $i + 1 );
- wfProfileOut( __METHOD__ );
return $i;
}
# Embedded in a tag; don't break it.
@@ -2709,7 +2763,6 @@ class Parser {
$colon = strpos( $str, ':', $i );
if ( $colon === false ) {
# Nothing else interesting
- wfProfileOut( __METHOD__ );
return false;
}
$lt = strpos( $str, '<', $i );
@@ -2718,7 +2771,6 @@ class Parser {
# We found it!
$before = substr( $str, 0, $colon );
$after = substr( $str, $colon + 1 );
- wfProfileOut( __METHOD__ );
return $i;
}
}
@@ -2769,7 +2821,6 @@ class Parser {
$stack--;
if ( $stack < 0 ) {
wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
- wfProfileOut( __METHOD__ );
return false;
}
$state = self::COLON_STATE_TEXT;
@@ -2804,16 +2855,13 @@ class Parser {
}
break;
default:
- wfProfileOut( __METHOD__ );
throw new MWException( "State machine error in " . __METHOD__ );
}
}
if ( $stack > 0 ) {
wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
- wfProfileOut( __METHOD__ );
return false;
}
- wfProfileOut( __METHOD__ );
return false;
}
@@ -2845,14 +2893,14 @@ class Parser {
* Some of these require message or data lookups and can be
* expensive to check many times.
*/
- if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
+ if ( Hooks::run( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
if ( isset( $this->mVarCache[$index] ) ) {
return $this->mVarCache[$index];
}
}
$ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
- wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
+ Hooks::run( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
$pageLang = $this->getFunctionLang();
@@ -3129,10 +3177,6 @@ class Parser {
case 'numberofedits':
$value = $pageLang->formatNum( SiteStats::edits() );
break;
- case 'numberofviews':
- global $wgDisableCounters;
- $value = !$wgDisableCounters ? $pageLang->formatNum( SiteStats::views() ) : '';
- break;
case 'currenttimestamp':
$value = wfTimestamp( TS_MW, $ts );
break;
@@ -3164,7 +3208,7 @@ class Parser {
break;
default:
$ret = null;
- wfRunHooks(
+ Hooks::run(
'ParserGetVariableValueSwitch',
array( &$this, &$this->mVarCache, &$index, &$ret, &$frame )
);
@@ -3185,13 +3229,11 @@ class Parser {
* @private
*/
public function initialiseVariables() {
- wfProfileIn( __METHOD__ );
$variableIDs = MagicWord::getVariableIDs();
$substIDs = MagicWord::getSubstIDs();
$this->mVariables = new MagicWordArray( $variableIDs );
$this->mSubstWords = new MagicWordArray( $substIDs );
- wfProfileOut( __METHOD__ );
}
/**
@@ -3266,7 +3308,6 @@ class Parser {
if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
return $text;
}
- wfProfileIn( __METHOD__ );
if ( $frame === false ) {
$frame = $this->getPreprocessor()->newFrame();
@@ -3280,7 +3321,6 @@ class Parser {
$flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
$text = $frame->expand( $dom, $flags );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -3358,8 +3398,6 @@ class Parser {
* @return string The text of the template
*/
public function braceSubstitution( $piece, $frame ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-setup' );
// Flags
@@ -3392,12 +3430,10 @@ class Parser {
# @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' );
- $titleProfileIn = null; // profile templates
+ $profileSection = null; // profile templates
# SUBST
- wfProfileIn( __METHOD__ . '-modifiers' );
if ( !$found ) {
$substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
@@ -3454,11 +3490,9 @@ class Parser {
$forceRawInterwiki = true;
}
}
- wfProfileOut( __METHOD__ . '-modifiers' );
# Parser functions
if ( !$found ) {
- wfProfileIn( __METHOD__ . '-pfunc' );
$colonPos = strpos( $part1, ':' );
if ( $colonPos !== false ) {
@@ -3470,8 +3504,6 @@ class Parser {
try {
$result = $this->callParserFunction( $frame, $func, $funcArgs );
} catch ( Exception $ex ) {
- wfProfileOut( __METHOD__ . '-pfunc' );
- wfProfileOut( __METHOD__ );
throw $ex;
}
@@ -3480,7 +3512,6 @@ class Parser {
# here.
extract( $result );
}
- wfProfileOut( __METHOD__ . '-pfunc' );
}
# Finish mangling title and then check for loops.
@@ -3515,12 +3546,7 @@ class Parser {
# Load from database
if ( !$found && $title ) {
- if ( !Profiler::instance()->isPersistent() ) {
- # Too many unique items can kill profiling DBs/collectors
- $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey();
- wfProfileIn( $titleProfileIn ); // template in
- }
- wfProfileIn( __METHOD__ . '-loadtpl' );
+ $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
if ( !$title->isExternal() ) {
if ( $title->isSpecialPage()
&& $this->mOptions->getAllowSpecialInclusion()
@@ -3594,17 +3620,15 @@ class Parser {
. '</span>';
wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
}
- wfProfileOut( __METHOD__ . '-loadtpl' );
}
# If we haven't found text to substitute by now, we're done
# Recover the source wikitext and return it
if ( !$found ) {
$text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
- if ( $titleProfileIn ) {
- wfProfileOut( $titleProfileIn ); // template out
+ if ( $profileSection ) {
+ $this->mProfiler->scopedProfileOut( $profileSection );
}
- wfProfileOut( __METHOD__ );
return array( 'object' => $text );
}
@@ -3628,8 +3652,8 @@ class Parser {
$isLocalObj = false;
}
- if ( $titleProfileIn ) {
- wfProfileOut( $titleProfileIn ); // template out
+ if ( $profileSection ) {
+ $this->mProfiler->scopedProfileOut( $profileSection );
}
# Replace raw HTML by a placeholder
@@ -3670,7 +3694,6 @@ class Parser {
$ret = array( 'text' => $text );
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -3696,7 +3719,6 @@ class Parser {
public function callParserFunction( $frame, $function, array $args = array() ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
# Case sensitive functions
if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
@@ -3707,23 +3729,19 @@ class Parser {
if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
$function = $this->mFunctionSynonyms[0][$function];
} else {
- wfProfileOut( __METHOD__ );
return array( 'found' => false );
}
}
- wfProfileIn( __METHOD__ . '-pfunc-' . $function );
list( $callback, $flags ) = $this->mFunctionHooks[$function];
# Workaround for PHP bug 35229 and similar
if ( !is_callable( $callback ) ) {
- wfProfileOut( __METHOD__ . '-pfunc-' . $function );
- wfProfileOut( __METHOD__ );
throw new MWException( "Tag hook for $function is not callable\n" );
}
$allArgs = array( &$this );
- if ( $flags & SFH_OBJECT_ARGS ) {
+ if ( $flags & self::SFH_OBJECT_ARGS ) {
# Convert arguments to PPNodes and collect for appending to $allArgs
$funcArgs = array();
foreach ( $args as $k => $v ) {
@@ -3783,8 +3801,6 @@ class Parser {
$result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
$result['isChildObj'] = true;
}
- wfProfileOut( __METHOD__ . '-pfunc-' . $function );
- wfProfileOut( __METHOD__ );
return $result;
}
@@ -3830,6 +3846,44 @@ class Parser {
}
/**
+ * Fetch the current revision of a given title. Note that the revision
+ * (and even the title) may not exist in the database, so everything
+ * contributing to the output of the parser should use this method
+ * where possible, rather than getting the revisions themselves. This
+ * method also caches its results, so using it benefits performance.
+ *
+ * @since 1.24
+ * @param Title $title
+ * @return Revision
+ */
+ public function fetchCurrentRevisionOfTitle( $title ) {
+ $cacheKey = $title->getPrefixedDBkey();
+ if ( !$this->currentRevisionCache ) {
+ $this->currentRevisionCache = new MapCacheLRU( 100 );
+ }
+ if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
+ $this->currentRevisionCache->set( $cacheKey,
+ // Defaults to Parser::statelessFetchRevision()
+ call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
+ );
+ }
+ return $this->currentRevisionCache->get( $cacheKey );
+ }
+
+ /**
+ * Wrapper around Revision::newFromTitle to allow passing additional parameters
+ * without passing them on to it.
+ *
+ * @since 1.24
+ * @param Title $title
+ * @param Parser|bool $parser
+ * @return Revision
+ */
+ public static function statelessFetchRevision( $title, $parser = false ) {
+ return Revision::newFromTitle( $title );
+ }
+
+ /**
* Fetch the unparsed text of a template and register a reference to it.
* @param Title $title
* @return array ( string or false, Title )
@@ -3881,7 +3935,7 @@ class Parser {
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
# Give extensions a chance to select the revision instead
$id = false; # Assume current
- wfRunHooks( 'BeforeParserFetchTemplateAndtitle',
+ Hooks::run( 'BeforeParserFetchTemplateAndtitle',
array( $parser, $title, &$skip, &$id ) );
if ( $skip ) {
@@ -3894,9 +3948,13 @@ class Parser {
break;
}
# Get the revision
- $rev = $id
- ? Revision::newFromId( $id )
- : Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
+ if ( $id ) {
+ $rev = Revision::newFromId( $id );
+ } elseif ( $parser ) {
+ $rev = $parser->fetchCurrentRevisionOfTitle( $title );
+ } else {
+ $rev = Revision::newFromTitle( $title );
+ }
$rev_id = $rev ? $rev->getId() : 0;
# If there is no current revision, there is no page
if ( $id === false && !$rev ) {
@@ -4041,7 +4099,7 @@ class Parser {
return $obj->tc_contents;
}
- $req = MWHttpRequest::factory( $url );
+ $req = MWHttpRequest::factory( $url, array(), __METHOD__ );
$status = $req->execute(); // Status object
if ( $status->isOK() ) {
$text = $req->getContent();
@@ -4072,7 +4130,6 @@ class Parser {
* @return array
*/
public function argSubstitution( $piece, $frame ) {
- wfProfileIn( __METHOD__ );
$error = false;
$parts = $piece['parts'];
@@ -4107,7 +4164,6 @@ class Parser {
$ret = array( 'text' => $text );
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -4238,7 +4294,6 @@ class Parser {
* @return string
*/
public function doDoubleUnderscore( $text ) {
- wfProfileIn( __METHOD__ );
# The position of __TOC__ needs to be recorded
$mw = MagicWord::get( 'toc' );
@@ -4286,45 +4341,16 @@ class Parser {
$this->mOutput->setProperty( $key, '' );
}
- wfProfileOut( __METHOD__ );
return $text;
}
/**
- * Add a tracking category, getting the title from a system message,
- * or print a debug message if the title is invalid.
- *
- * Please add any message that you use with this function to
- * $wgTrackingCategories. That way they will be listed on
- * Special:TrackingCategories.
- *
+ * @see ParserOutput::addTrackingCategory()
* @param string $msg Message key
* @return bool Whether the addition was successful
*/
public function addTrackingCategory( $msg ) {
- if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
- wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
- return false;
- }
- // Important to parse with correct title (bug 31469)
- $cat = wfMessage( $msg )
- ->title( $this->getTitle() )
- ->inContentLanguage()
- ->text();
-
- # Allow tracking categories to be disabled by setting them to "-"
- if ( $cat === '-' ) {
- return false;
- }
-
- $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
- if ( $containerCategory ) {
- $this->mOutput->addCategory( $containerCategory->getDBkey(), $this->getDefaultSort() );
- return true;
- } else {
- wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
- return false;
- }
+ return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
}
/**
@@ -4361,7 +4387,7 @@ class Parser {
# links - this is for later, but we need the number of headlines right now
$matches = array();
$numMatches = preg_match_all(
- '/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
+ '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
$text,
$matches
);
@@ -4508,14 +4534,15 @@ class Parser {
# * <sup> and <sub> (bug 8393)
# * <i> (bug 26375)
# * <b> (r105284)
+ # * <bdi> (bug 72884)
# * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
#
# We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
# to allow setting directionality in toc items.
$tocline = preg_replace(
array(
- '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#',
- '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#'
+ '#<(?!/?(span|sup|sub|bdi|i|b)(?: [^>]*)?>).*?>#',
+ '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b))(?: .*?)?>#'
),
array( '', '<$1>' ),
$safeHeadline
@@ -4523,7 +4550,7 @@ class Parser {
$tocline = trim( $tocline );
# For the anchor, strip out HTML-y stuff period
- $safeHeadline = preg_replace( '/<.*?' . '>/', '', $safeHeadline );
+ $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
$safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
# Save headline for section edit hint before it's escaped
@@ -4556,7 +4583,7 @@ class Parser {
# HTML names must be case-insensitively unique (bug 10721).
# This does not apply to Unicode characters per
- # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison
+ # http://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
# @todo FIXME: We may be changing them depending on the current locale.
$arrayKey = strtolower( $safeHeadline );
if ( $legacyHeadline === false ) {
@@ -4565,16 +4592,22 @@ class Parser {
$legacyArrayKey = strtolower( $legacyHeadline );
}
- # count how many in assoc. array so we can track dupes in anchors
+ # Create the anchor for linking from the TOC to the section
+ $anchor = $safeHeadline;
+ $legacyAnchor = $legacyHeadline;
if ( isset( $refers[$arrayKey] ) ) {
- $refers[$arrayKey]++;
+ for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
+ $anchor .= "_$i";
+ $refers["${arrayKey}_$i"] = true;
} else {
- $refers[$arrayKey] = 1;
+ $refers[$arrayKey] = true;
}
- if ( isset( $refers[$legacyArrayKey] ) ) {
- $refers[$legacyArrayKey]++;
+ if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
+ for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
+ $legacyAnchor .= "_$i";
+ $refers["${legacyArrayKey}_$i"] = true;
} else {
- $refers[$legacyArrayKey] = 1;
+ $refers[$legacyArrayKey] = true;
}
# Don't number the heading if it is the only one (looks silly)
@@ -4587,15 +4620,6 @@ class Parser {
) . ' ' . $headline;
}
- # Create the anchor for linking from the TOC to the section
- $anchor = $safeHeadline;
- $legacyAnchor = $legacyHeadline;
- if ( $refers[$arrayKey] > 1 ) {
- $anchor .= '_' . $refers[$arrayKey];
- }
- if ( $legacyHeadline !== false && $refers[$legacyArrayKey] > 1 ) {
- $legacyAnchor .= '_' . $refers[$legacyArrayKey];
- }
if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
$toc .= Linker::tocLine( $anchor, $tocline,
$numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
@@ -4691,7 +4715,7 @@ class Parser {
}
# split up and insert constructed headlines
- $blocks = preg_split( '/<H[1-6].*?' . '>[\s\S]*?<\/H[1-6]>/i', $text );
+ $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
$i = 0;
// build an array of document sections
@@ -4714,7 +4738,7 @@ class Parser {
* &$sectionContent : ref to the content of the section
* $showEditLinks : boolean describing whether this section has an edit link
*/
- wfRunHooks( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
+ Hooks::run( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
$i++;
}
@@ -4736,7 +4760,7 @@ class Parser {
/**
* Transform wiki markup when saving a page by doing "\r\n" -> "\n"
- * conversion, substitting signatures, {{subst:}} templates, etc.
+ * conversion, substituting signatures, {{subst:}} templates, etc.
*
* @param string $text The text to transform
* @param Title $title The Title object for the current article
@@ -4756,6 +4780,7 @@ class Parser {
$pairs = array(
"\r\n" => "\n",
+ "\r" => "\n",
);
$text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
if ( $options->getPreSaveTransform() ) {
@@ -4920,7 +4945,7 @@ class Parser {
/**
* Clean up signature text
*
- * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig
+ * 1) Strip 3, 4 or 5 tildes out of signatures @see cleanSigInSig
* 2) Substitute all transclusions
*
* @param string $text
@@ -4959,7 +4984,7 @@ class Parser {
}
/**
- * Strip ~~~, ~~~~ and ~~~~~ out of signatures
+ * Strip 3, 4 or 5 tildes out of signatures.
*
* @param string $text
* @return string Signature text with /~{3,5}/ removed
@@ -5018,7 +5043,6 @@ class Parser {
}
$executing = true;
- wfProfileIn( __METHOD__ );
if ( !$title ) {
global $wgTitle;
$title = $wgTitle;
@@ -5027,7 +5051,6 @@ class Parser {
$text = $this->preprocess( $text, $title, $options );
$executing = false;
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -5111,7 +5134,7 @@ class Parser {
* The callback function should have the form:
* function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... }
*
- * Or with SFH_OBJECT_ARGS:
+ * Or with Parser::SFH_OBJECT_ARGS:
* function myParserFunction( $parser, $frame, $args ) { ... }
*
* The callback may either return the text result of the function, or an array with the text
@@ -5125,10 +5148,10 @@ class Parser {
* @param string $id The magic word ID
* @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:...}}
+ * Parser::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
- * allows for conditional expansion of the parse tree, allowing you to eliminate dead
+ * Parser::SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text.
+ * This allows for conditional expansion of the parse tree, allowing you to eliminate dead
* branches and thus speed up parsing. It is also possible to analyse the parse tree of
* the arguments, and to control the way they are expanded.
*
@@ -5170,7 +5193,7 @@ class Parser {
$syn = $wgContLang->lc( $syn );
}
# Add leading hash
- if ( !( $flags & SFH_NO_HASH ) ) {
+ if ( !( $flags & self::SFH_NO_HASH ) ) {
$syn = '#' . $syn;
}
# Remove trailing colon
@@ -5224,11 +5247,9 @@ class Parser {
*
* @param string $text
* @param int $options
- *
- * @return array Array of link CSS classes, indexed by PDBK.
*/
public function replaceLinkHolders( &$text, $options = 0 ) {
- return $this->mLinkHolders->replace( $text );
+ $this->mLinkHolders->replace( $text );
}
/**
@@ -5256,7 +5277,6 @@ class Parser {
* @return string HTML
*/
public function renderImageGallery( $text, $params ) {
- wfProfileIn( __METHOD__ );
$mode = false;
if ( isset( $params['mode'] ) ) {
@@ -5265,7 +5285,7 @@ class Parser {
try {
$ig = ImageGalleryBase::factory( $mode );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// If invalid type set, fallback to default.
$ig = ImageGalleryBase::factory( false );
}
@@ -5299,7 +5319,7 @@ class Parser {
}
$ig->setAdditionalOptions( $params );
- wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
+ Hooks::run( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
$lines = StringUtils::explode( "\n", $text );
foreach ( $lines as $line ) {
@@ -5326,13 +5346,12 @@ class Parser {
# file (which potentially could be of a different type and have different handler).
$options = array();
$descQuery = false;
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this, $title, &$options, &$descQuery ) );
# Don't register it now, as ImageGallery does that later.
$file = $this->fetchFileNoRegister( $title, $options );
$handler = $file ? $file->getHandler() : false;
- wfProfileIn( __METHOD__ . '-getMagicWord' );
$paramMap = array(
'img_alt' => 'gallery-internal-alt',
'img_link' => 'gallery-internal-link',
@@ -5345,7 +5364,6 @@ class Parser {
}
$mwArray = new MagicWordArray( array_keys( $paramMap ) );
- wfProfileOut( __METHOD__ . '-getMagicWord' );
$label = '';
$alt = '';
@@ -5407,13 +5425,12 @@ class Parser {
$ig->add( $title, $label, $alt, $link, $handlerOptions );
}
$html = $ig->toHTML();
- wfRunHooks( 'AfterParserFetchFileAndTitle', array( $this, $ig, &$html ) );
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'AfterParserFetchFileAndTitle', array( $this, $ig, &$html ) );
return $html;
}
/**
- * @param string $handler
+ * @param MediaHandler $handler
* @return array
*/
public function getImageParams( $handler ) {
@@ -5496,7 +5513,7 @@ class Parser {
# Give extensions a chance to select the file revision for us
$options = array();
$descQuery = false;
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this, $title, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
@@ -5660,7 +5677,7 @@ class Parser {
$params['frame']['title'] = $this->stripAltText( $caption, $holders );
}
- wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) );
+ Hooks::run( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) );
# Linker does the rest
$time = isset( $options['time'] ) ? $options['time'] : false;
@@ -5969,7 +5986,19 @@ class Parser {
return null;
}
- $this->mRevisionObject = Revision::newFromId( $this->mRevisionId );
+ $rev = call_user_func(
+ $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
+ );
+
+ # If the parse is for a new revision, then the callback should have
+ # already been set to force the object and should match mRevisionId.
+ # If not, try to fetch by mRevisionId for sanity.
+ if ( $rev && $rev->getId() != $this->mRevisionId ) {
+ $rev = Revision::newFromId( $this->mRevisionId );
+ }
+
+ $this->mRevisionObject = $rev;
+
return $this->mRevisionObject;
}
@@ -5980,8 +6009,6 @@ class Parser {
*/
public function getRevisionTimestamp() {
if ( is_null( $this->mRevisionTimestamp ) ) {
- wfProfileIn( __METHOD__ );
-
global $wgContLang;
$revObject = $this->getRevisionObject();
@@ -5995,7 +6022,6 @@ class Parser {
# it needs to be consistent for all visitors.
$this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
- wfProfileOut( __METHOD__ );
}
return $this->mRevisionTimestamp;
}
@@ -6250,14 +6276,12 @@ class Parser {
* @return array
*/
public function serializeHalfParsedText( $text ) {
- wfProfileIn( __METHOD__ );
$data = array(
'text' => $text,
'version' => self::HALF_PARSED_VERSION,
'stripState' => $this->mStripState->getSubState( $text ),
'linkHolders' => $this->mLinkHolders->getSubArray( $text )
);
- wfProfileOut( __METHOD__ );
return $data;
}
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 79523003..bc8e4a69 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -184,12 +184,10 @@ class ParserCache {
*/
public function get( $article, $popts, $useOutdated = false ) {
global $wgCacheEpoch;
- wfProfileIn( __METHOD__ );
$canCache = $article->checkTouched();
if ( !$canCache ) {
// It's a redirect now
- wfProfileOut( __METHOD__ );
return false;
}
@@ -198,7 +196,6 @@ class ParserCache {
$parserOutputKey = $this->getKey( $article, $popts, $useOutdated );
if ( $parserOutputKey === false ) {
wfIncrStats( 'pcache_miss_absent' );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -206,7 +203,6 @@ class ParserCache {
if ( !$value ) {
wfDebug( "ParserOutput cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -233,7 +229,6 @@ class ParserCache {
wfIncrStats( "pcache_hit" );
}
- wfProfileOut( __METHOD__ );
return $value;
}
@@ -262,8 +257,6 @@ class ParserCache {
$optionsKey->setCacheRevisionId( $revId );
$parserOutput->setCacheRevisionId( $revId );
- $optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
-
$parserOutputKey = $this->getParserOutputKey( $page,
$popts->optionsHash( $optionsKey->mUsedOptions, $page->getTitle() ) );
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 7e4059b8..100656d1 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -25,7 +25,7 @@
* @brief Set options of the Parser
*
* All member variables are supposed to be private in theory, although in
- * practise this is not the case.
+ * practice this is not the case.
*
* @ingroup Parser
*/
@@ -117,6 +117,12 @@ class ParserOptions {
public $mRemoveComments = true;
/**
+ * Callback for current revision fetching. Used as first argument to call_user_func().
+ */
+ public $mCurrentRevisionCallback =
+ array( 'Parser', 'statelessFetchRevision' );
+
+ /**
* Callback for template fetching. Used as first argument to call_user_func().
*/
public $mTemplateCallback =
@@ -139,9 +145,7 @@ class ParserOptions {
/**
* Clean up signature texts?
- *
- * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures
- * 2) Substitute all transclusions
+ * @see Parser::cleanSig
*/
public $mCleanSignatures;
@@ -289,6 +293,11 @@ class ParserOptions {
return $this->mRemoveComments;
}
+ /* @since 1.24 */
+ public function getCurrentRevisionCallback() {
+ return $this->mCurrentRevisionCallback;
+ }
+
public function getTemplateCallback() {
return $this->mTemplateCallback;
}
@@ -462,6 +471,11 @@ class ParserOptions {
return wfSetVar( $this->mRemoveComments, $x );
}
+ /* @since 1.24 */
+ public function setCurrentRevisionCallback( $x ) {
+ return wfSetVar( $this->mCurrentRevisionCallback, $x );
+ }
+
public function setTemplateCallback( $x ) {
return wfSetVar( $this->mTemplateCallback, $x );
}
@@ -623,8 +637,7 @@ class ParserOptions {
$wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
$wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion;
- wfProfileIn( __METHOD__ );
-
+ // *UPDATE* ParserOptions::matches() if any of this changes as needed
$this->mInterwikiMagic = $wgInterwikiMagic;
$this->mAllowExternalImages = $wgAllowExternalImages;
$this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
@@ -647,7 +660,33 @@ class ParserOptions {
$this->mStubThreshold = $user->getStubThreshold();
$this->mUserLang = $lang;
- wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Check if these options match that of another options set
+ *
+ * This ignores report limit settings that only affect HTML comments
+ *
+ * @param ParserOptions $other
+ * @return bool
+ * @since 1.25
+ */
+ public function matches( ParserOptions $other ) {
+ $fields = array_keys( get_class_vars( __CLASS__ ) );
+ $fields = array_diff( $fields, array(
+ 'mEnableLimitReport', // only effects HTML comments
+ 'onAccessCallback', // only used for ParserOutput option tracking
+ ) );
+ foreach ( $fields as $field ) {
+ if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
+ return false;
+ }
+ }
+ // Check the object and lazy-loaded options
+ return (
+ $this->mUserLang->getCode() === $other->mUserLang->getCode() &&
+ $this->getDateFormat() === $other->getDateFormat()
+ );
}
/**
@@ -768,11 +807,53 @@ 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, $this->getUser(), &$forOptions ) );
+ Hooks::run( 'PageRenderingHash', array( &$confstr, $this->getUser(), &$forOptions ) );
// Make it a valid memcached key fragment
$confstr = str_replace( ' ', '_', $confstr );
return $confstr;
}
+
+ /**
+ * Sets a hook to force that a page exists, and sets a current revision callback to return a
+ * revision with custom content when the current revision of the page is requested.
+ *
+ * @since 1.25
+ * @param Title $title
+ * @param Content $content
+ * @param User $user The user that the fake revision is attributed to
+ * @return ScopedCallback to unset the hook
+ */
+ public function setupFakeRevision( $title, $content, $user ) {
+ $oldCallback = $this->setCurrentRevisionCallback( function ( $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback ) {
+ if ( $titleToCheck->equals( $title ) ) {
+ return new Revision( array(
+ 'page' => $title->getArticleID(),
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
+ 'parent_id' => $title->getLatestRevId(),
+ 'title' => $title,
+ 'content' => $content
+ ) );
+ } else {
+ return call_user_func( $oldCallback, $titleToCheck, $parser );
+ }
+ } );
+ global $wgHooks;
+ $wgHooks['TitleExists'][] =
+ function ( $titleToCheck, &$exists ) use ( $title ) {
+ if ( $titleToCheck->equals( $title ) ) {
+ $exists = true;
+ }
+ };
+ end( $wgHooks['TitleExists'] );
+ $key = key( $wgHooks['TitleExists'] );
+ LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
+ return new ScopedCallback( function () use ( $title, $key ) {
+ global $wgHooks;
+ unset( $wgHooks['TitleExists'][$key] );
+ LinkCache::singleton()->clearLink( $title );
+ } );
+ }
}
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index 5037ce18..65b527c8 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -25,6 +25,7 @@ class ParserOutput extends CacheTime {
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
+ $mIndicators = array(), # Page status indicators, usually displayed in top-right corner
$mTitleText, # title text of the chosen language variant
$mLinks = array(), # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken.
$mTemplates = array(), # 2-D map of NS/DBK to ID for the template references. ID=zero for broken.
@@ -52,27 +53,25 @@ class ParserOutput extends CacheTime {
$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
+ private $mFlags = array(); # Generic flags
const EDITSECTION_REGEX =
'#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
public function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(),
- $containsOldMagic = false, $titletext = ''
+ $unused = false, $titletext = ''
) {
$this->mText = $text;
$this->mLanguageLinks = $languageLinks;
$this->mCategories = $categoryLinks;
- $this->mContainsOldMagic = $containsOldMagic;
$this->mTitleText = $titletext;
}
public function getText() {
- wfProfileIn( __METHOD__ );
$text = $this->mText;
if ( $this->mEditSectionTokens ) {
$text = preg_replace_callback(
@@ -110,7 +109,6 @@ class ParserOutput extends CacheTime {
$text
);
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -130,6 +128,13 @@ class ParserOutput extends CacheTime {
return $this->mCategories;
}
+ /**
+ * @since 1.25
+ */
+ public function getIndicators() {
+ return $this->mIndicators;
+ }
+
public function getTitleText() {
return $this->mTitleText;
}
@@ -267,6 +272,13 @@ class ParserOutput extends CacheTime {
$this->mCategories[$c] = $sort;
}
+ /**
+ * @since 1.25
+ */
+ public function setIndicator( $id, $content ) {
+ $this->mIndicators[$id] = $content;
+ }
+
public function addLanguageLink( $t ) {
$this->mLanguageLinks[] = $t;
}
@@ -472,6 +484,47 @@ class ParserOutput extends CacheTime {
}
/**
+ * Add a tracking category, getting the title from a system message,
+ * or print a debug message if the title is invalid.
+ *
+ * Any message used with this function should be registered so it will
+ * show up on Special:TrackingCategories. Core messages should be added
+ * to SpecialTrackingCategories::$coreTrackingCategories, and extensions
+ * should add to "TrackingCategories" in their extension.json.
+ *
+ * @param string $msg Message key
+ * @param Title $title title of the page which is being tracked
+ * @return bool Whether the addition was successful
+ * @since 1.25
+ */
+ public function addTrackingCategory( $msg, $title ) {
+ if ( $title->getNamespace() === NS_SPECIAL ) {
+ wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
+ return false;
+ }
+
+ // Important to parse with correct title (bug 31469)
+ $cat = wfMessage( $msg )
+ ->title( $title )
+ ->inContentLanguage()
+ ->text();
+
+ # Allow tracking categories to be disabled by setting them to "-"
+ if ( $cat === '-' ) {
+ return false;
+ }
+
+ $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
+ if ( $containerCategory ) {
+ $this->addCategory( $containerCategory->getDBkey(), $this->getProperty( 'defaultsort' ) ?: '' );
+ return true;
+ } else {
+ wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
+ return false;
+ }
+ }
+
+ /**
* Override the title to be used for display
* -- this is assumed to have been validated
* (check equal normalisation, etc.)
@@ -622,43 +675,57 @@ class ParserOutput extends CacheTime {
}
/**
- * 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.
+ * @deprecated since 1.25. Instead, store any relevant data using setExtensionData,
+ * and implement Content::getSecondaryDataUpdates() if possible, or use the
+ * 'SecondaryDataUpdates' hook to construct the necessary update objects.
*
- * @since 1.20
+ * @note Hard deprecation and removal without long deprecation period, since there are no
+ * known users, but known conceptual issues.
+ *
+ * @todo remove in 1.26
*
* @param DataUpdate $update
+ *
+ * @throws MWException
*/
public function addSecondaryDataUpdate( DataUpdate $update ) {
- $this->mSecondaryDataUpdates[] = $update;
+ wfDeprecated( __METHOD__, '1.25' );
+ throw new MWException( 'ParserOutput::addSecondaryDataUpdate() is no longer supported. Override Content::getSecondaryDataUpdates() or use the SecondaryDataUpdates hook instead.' );
}
/**
- * Returns any DataUpdate jobs to be executed in order to store secondary information
- * extracted from the page's content, including a LinksUpdate object for all links stored in
- * this ParserOutput object.
+ * @deprecated since 1.25.
*
- * @note Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates()
- * instead! The content handler may provide additional update objects.
+ * @note Hard deprecation and removal without long deprecation period, since there are no
+ * known users, but known conceptual issues.
*
- * @since 1.20
+ * @todo remove in 1.26
*
- * @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 bool false (since 1.25)
+ */
+ public function hasCustomDataUpdates() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return false;
+ }
+
+ /**
+ * @deprecated since 1.25. Instead, store any relevant data using setExtensionData,
+ * and implement Content::getSecondaryDataUpdates() if possible, or use the
+ * 'SecondaryDataUpdates' hook to construct the necessary update objects.
+ *
+ * @note Hard deprecation and removal without long deprecation period, since there are no
+ * known users, but known conceptual issues.
+ *
+ * @todo remove in 1.26
+ *
+ * @param Title $title
+ * @param bool $recursive
*
* @return array An array of instances of DataUpdate
*/
public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) {
- if ( is_null( $title ) ) {
- $title = Title::newFromText( $this->getTitleText() );
- }
-
- $linksUpdate = new LinksUpdate( $title, $this, $recursive );
-
- return array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
+ wfDeprecated( __METHOD__, '1.25' );
+ return array();
}
/**
@@ -795,6 +862,22 @@ class ParserOutput extends CacheTime {
}
/**
+ * Check whether the cache TTL was lowered due to dynamic content
+ *
+ * When content is determined by more than hard state (e.g. page edits),
+ * such as template/file transclusions based on the current timestamp or
+ * extension tags that generate lists based on queries, this return true.
+ *
+ * @return bool
+ * @since 1.25
+ */
+ public function hasDynamicContent() {
+ global $wgParserCacheExpireTime;
+
+ return $this->getCacheExpiry() < $wgParserCacheExpireTime;
+ }
+
+ /**
* Get or set the prevent-clickjacking flag
*
* @since 1.24
@@ -806,13 +889,13 @@ class ParserOutput extends CacheTime {
}
/**
- * Save space for for serialization by removing useless values
+ * Save space for serialization by removing useless values
* @return array
*/
public function __sleep() {
return array_diff(
array_keys( get_object_vars( $this ) ),
- array( 'mSecondaryDataUpdates', 'mParseStartTime' )
+ array( 'mParseStartTime' )
);
}
}
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 2edb79a2..0351f2a8 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -68,6 +68,7 @@ class Preprocessor_DOM implements Preprocessor {
/**
* @param array $values
* @return PPNode_DOM
+ * @throws MWException
*/
public function newPartNodeArray( $values ) {
//NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
@@ -85,19 +86,17 @@ class Preprocessor_DOM implements Preprocessor {
$xml .= "</list>";
- wfProfileIn( __METHOD__ . '-loadXML' );
$dom = new DOMDocument();
wfSuppressWarnings();
$result = $dom->loadXML( $xml );
wfRestoreWarnings();
if ( !$result ) {
// Try running the XML through UtfNormal to get rid of invalid characters
- $xml = UtfNormal::cleanUp( $xml );
+ $xml = UtfNormal\Validator::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' );
@@ -149,14 +148,12 @@ class Preprocessor_DOM implements Preprocessor {
* @return PPNode_DOM
*/
public function preprocessToObj( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
global $wgMemc, $wgPreprocessorCacheThreshold;
$xml = false;
$cacheable = ( $wgPreprocessorCacheThreshold !== false
&& strlen( $text ) > $wgPreprocessorCacheThreshold );
if ( $cacheable ) {
- wfProfileIn( __METHOD__ . '-cacheable' );
$cacheKey = wfMemcKey( 'preprocess-xml', md5( $text ), $flags );
$cacheValue = $wgMemc->get( $cacheKey );
@@ -169,11 +166,9 @@ class Preprocessor_DOM implements Preprocessor {
}
}
if ( $xml === false ) {
- wfProfileIn( __METHOD__ . '-cache-miss' );
$xml = $this->preprocessToXml( $text, $flags );
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . $xml;
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__ . '-cache-miss' );
wfDebugLog( "Preprocessor", "Saved preprocessor XML to memcached (key $cacheKey)" );
}
} else {
@@ -186,20 +181,17 @@ class Preprocessor_DOM implements Preprocessor {
$max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
if ( $this->parser->mGeneratedPPNodeCount > $max ) {
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': generated node count limit exceeded' );
}
- wfProfileIn( __METHOD__ . '-loadXML' );
$dom = new DOMDocument;
wfSuppressWarnings();
$result = $dom->loadXML( $xml );
wfRestoreWarnings();
if ( !$result ) {
// Try running the XML through UtfNormal to get rid of invalid characters
- $xml = UtfNormal::cleanUp( $xml );
+ $xml = UtfNormal\Validator::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 );
@@ -207,14 +199,10 @@ class Preprocessor_DOM implements Preprocessor {
if ( $result ) {
$obj = new PPNode_DOM( $dom->documentElement );
}
- wfProfileOut( __METHOD__ . '-loadXML' );
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
-
if ( !$result ) {
throw new MWException( __METHOD__ . ' generated invalid XML' );
}
@@ -227,7 +215,6 @@ class Preprocessor_DOM implements Preprocessor {
* @return string
*/
public function preprocessToXml( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
$rules = array(
'{' => array(
'end' => '}',
@@ -764,8 +751,6 @@ class Preprocessor_DOM implements Preprocessor {
$stack->rootAccum .= '</root>';
$xml = $stack->rootAccum;
- wfProfileOut( __METHOD__ );
-
return $xml;
}
}
@@ -1043,11 +1028,17 @@ class PPFrame_DOM implements PPFrame {
// Numbered parameter
$index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
$index = $index - $indexOffset;
+ if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$numberedArgs[$index] = $value->item( 0 );
unset( $namedArgs[$index] );
} else {
// Named parameter
$name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
+ if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$namedArgs[$name] = $value->item( 0 );
unset( $numberedArgs[$name] );
}
@@ -1095,7 +1086,6 @@ class PPFrame_DOM implements PPFrame {
);
return '<span class="error">Expansion depth limit exceeded</span>';
}
- wfProfileIn( __METHOD__ );
++$expansionDepth;
if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
$this->parser->mHighestExpansionDepth = $expansionDepth;
@@ -1284,7 +1274,6 @@ class PPFrame_DOM implements PPFrame {
$newIterator = $contextNode->childNodes;
}
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': Invalid parameter type' );
}
@@ -1308,7 +1297,6 @@ class PPFrame_DOM implements PPFrame {
}
}
--$expansionDepth;
- wfProfileOut( __METHOD__ );
return $outStack[0];
}
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index 63763967..af91ad47 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -112,7 +112,6 @@ class Preprocessor_Hash implements Preprocessor {
* @return PPNode_Hash_Tree
*/
public function preprocessToObj( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
// Check cache.
global $wgMemc, $wgPreprocessorCacheThreshold;
@@ -121,7 +120,6 @@ class Preprocessor_Hash implements Preprocessor {
&& strlen( $text ) > $wgPreprocessorCacheThreshold;
if ( $cacheable ) {
- wfProfileIn( __METHOD__ . '-cacheable' );
$cacheKey = wfMemcKey( 'preprocess-hash', md5( $text ), $flags );
$cacheValue = $wgMemc->get( $cacheKey );
@@ -132,12 +130,9 @@ class Preprocessor_Hash implements Preprocessor {
// From the cache
wfDebugLog( "Preprocessor",
"Loaded preprocessor hash from memcached (key $cacheKey)" );
- wfProfileOut( __METHOD__ . '-cacheable' );
- wfProfileOut( __METHOD__ );
return $hash;
}
}
- wfProfileIn( __METHOD__ . '-cache-miss' );
}
$rules = array(
@@ -637,18 +632,12 @@ class Preprocessor_Hash implements Preprocessor {
}
if ( !$node ) {
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cache-miss' );
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': eqpos not found' );
}
if ( $node->name !== 'equals' ) {
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cache-miss' );
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': eqpos is not equals' );
}
$equalsNode = $node;
@@ -748,12 +737,9 @@ class Preprocessor_Hash implements Preprocessor {
if ( $cacheable ) {
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__ . '-cache-miss' );
- wfProfileOut( __METHOD__ . '-cacheable' );
wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
}
- wfProfileOut( __METHOD__ );
return $rootNode;
}
}
@@ -985,11 +971,17 @@ class PPFrame_Hash implements PPFrame {
if ( $bits['index'] !== '' ) {
// Numbered parameter
$index = $bits['index'] - $indexOffset;
+ if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$numberedArgs[$index] = $bits['value'];
unset( $namedArgs[$index] );
} else {
// Named parameter
$name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
+ if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$namedArgs[$name] = $bits['value'];
unset( $numberedArgs[$name] );
}
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
index 5d1743e6..51ae42dc 100644
--- a/includes/parser/StripState.php
+++ b/includes/parser/StripState.php
@@ -117,12 +117,10 @@ class StripState {
return $text;
}
- wfProfileIn( __METHOD__ );
$oldType = $this->tempType;
$this->tempType = $type;
$text = preg_replace_callback( $this->regex, array( $this, 'unstripCallback' ), $text );
$this->tempType = $oldType;
- wfProfileOut( __METHOD__ );
return $text;
}