diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php index 61dbafe5513..594f018e2e9 100644 --- a/includes/CoreParserFunctions.php +++ b/includes/CoreParserFunctions.php @@ -5,6 +5,52 @@ * @addtogroup Parser */ class CoreParserFunctions { + static function register( $parser ) { + global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions; + + # Syntax for arguments (see self::setFunctionHook): + # "name for lookup in localized magic words array", + # function callback, + # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...} + # instead of {{#int:...}}) + + $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'ns', array( __CLASS__, 'ns' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'urlencode', array( __CLASS__, 'urlencode' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'lcfirst', array( __CLASS__, 'lcfirst' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'ucfirst', array( __CLASS__, 'ucfirst' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'lc', array( __CLASS__, 'lc' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'uc', array( __CLASS__, 'uc' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'localurl', array( __CLASS__, 'localurl' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'localurle', array( __CLASS__, 'localurle' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'fullurl', array( __CLASS__, 'fullurl' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'fullurle', array( __CLASS__, 'fullurle' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'formatnum', array( __CLASS__, 'formatnum' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'grammar', array( __CLASS__, 'grammar' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'plural', array( __CLASS__, 'plural' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberofpages', array( __CLASS__, 'numberofpages' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberofusers', array( __CLASS__, 'numberofusers' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberofarticles', array( __CLASS__, 'numberofarticles' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberoffiles', array( __CLASS__, 'numberoffiles' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberofadmins', array( __CLASS__, 'numberofadmins' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberofedits', array( __CLASS__, 'numberofedits' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'language', array( __CLASS__, 'language' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'padleft', array( __CLASS__, 'padleft' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'padright', array( __CLASS__, 'padright' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'anchorencode', array( __CLASS__, 'anchorencode' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) ); + $parser->setFunctionHook( 'defaultsort', array( __CLASS__, 'defaultsort' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'filepath', array( __CLASS__, 'filepath' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS ); + + if ( $wgAllowDisplayTitle ) { + $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH ); + } + if ( $wgAllowSlowParserFunctions ) { + $parser->setFunctionHook( 'pagesinnamespace', array( __CLASS__, 'pagesinnamespace' ), SFH_NO_HASH ); + } + } + static function intFunction( $parser, $part1 = '' /*, ... */ ) { if ( strval( $part1 ) !== '' ) { $args = array_slice( func_get_args(), 2 ); diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 89943ae2d4e..f30d34ad01a 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2937,3 +2937,11 @@ $wgParserConf = array( * Hooks should return strings or false */ $wgExceptionHooks = array(); + +/** + * Page property link table invalidation lists. + * Should only be set by extensions. + */ +$wgPagePropLinkInvalidations = array( + 'hiddencat' => 'categorylinks', +); diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php index db1114c9387..c5c5721eafe 100644 --- a/includes/LinkBatch.php +++ b/includes/LinkBatch.php @@ -73,12 +73,18 @@ class LinkBatch { * Return an array mapping PDBK to ID */ function executeInto( &$cache ) { - $fname = 'LinkBatch::executeInto'; - wfProfileIn( $fname ); - // Do query + wfProfileIn( __METHOD__ ); $res = $this->doQuery(); + $ids = $this->addResultToCache( $cache, $res ); + wfProfileOut( __METHOD__ ); + return $ids; + } + + /** + * Add a ResultWrapper containing IDs and titles to a LinkCache object + */ + function addResultToCache( $cache, $res ) { if ( !$res ) { - wfProfileOut( $fname ); return array(); } @@ -92,7 +98,6 @@ class LinkBatch { $ids[$title->getPrefixedDBkey()] = $row->page_id; unset( $remaining[$row->page_namespace][$row->page_title] ); } - $res->free(); // The remaining links in $data are bad links, register them as such foreach ( $remaining as $ns => $dbkeys ) { @@ -102,7 +107,6 @@ class LinkBatch { $ids[$title->getPrefixedDBkey()] = 0; } } - wfProfileOut( $fname ); return $ids; } @@ -110,12 +114,10 @@ class LinkBatch { * Perform the existence test query, return a ResultWrapper with page_id fields */ function doQuery() { - $fname = 'LinkBatch::doQuery'; - if ( $this->isEmpty() ) { return false; } - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); // Construct query // This is very similar to Parser::replaceLinkHolders @@ -123,22 +125,21 @@ class LinkBatch { $page = $dbr->tableName( 'page' ); $set = $this->constructSet( 'page', $dbr ); if ( $set === false ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return false; } $sql = "SELECT page_id, page_namespace, page_title FROM $page WHERE $set"; // Do query - $res = new ResultWrapper( $dbr, $dbr->query( $sql, $fname ) ); - wfProfileOut( $fname ); + $res = new ResultWrapper( $dbr, $dbr->query( $sql, __METHOD__ ) ); + wfProfileOut( __METHOD__ ); return $res; } /** * Construct a WHERE clause which will match all the given titles. - * Give the appropriate table's field name prefix ('page', 'pl', etc). * - * @param $prefix String: ?? + * @param string $prefix the appropriate table's field name prefix ('page', 'pl', etc) * @return string * @public */ diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php index a52414c39bf..152058ac5ac 100644 --- a/includes/LinksUpdate.php +++ b/includes/LinksUpdate.php @@ -17,6 +17,7 @@ class LinksUpdate { $mExternals, //!< URLs of external links, array key only $mCategories, //!< Map of category names to sort keys $mInterlangs, //!< Map of language codes to titles + $mProperties, //!< Map of arbitrary name to value $mDb, //!< Database connection reference $mOptions, //!< SELECT options to be used (array) $mRecursive; //!< Whether to queue jobs for recursive updates @@ -51,6 +52,7 @@ class LinksUpdate { $this->mTemplates = $parserOutput->getTemplates(); $this->mExternals = $parserOutput->getExternalLinks(); $this->mCategories = $parserOutput->getCategories(); + $this->mProperties = $parserOutput->getProperties(); # Convert the format of the interlanguage links # I didn't want to change it in the ParserOutput, because that array is passed all @@ -85,8 +87,7 @@ class LinksUpdate { } function doIncrementalUpdate() { - $fname = 'LinksUpdate::doIncrementalUpdate'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); # Page links $existing = $this->getExistingLinks(); @@ -126,13 +127,22 @@ class LinksUpdate { $categoryUpdates = array_diff_assoc( $existing, $this->mCategories ) + array_diff_assoc( $this->mCategories, $existing ); $this->invalidateCategories( $categoryUpdates ); + # Page properties + $existing = $this->getExistingProperties(); + $this->incrTableUpdate( 'page_props', 'pp', $this->getPropertyDeletions( $existing ), + $this->getPropertyInsertions( $existing ) ); + + # Invalidate the necessary pages + $changed = array_diff_assoc( $existing, $this->mProperties ) + array_diff_assoc( $this->mProperties, $existing ); + $this->invalidateProperties( $changed ); + # Refresh links of all pages including this page # This will be in a separate transaction if ( $this->mRecursive ) { $this->queueRecursiveJobs(); } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } /** @@ -141,8 +151,7 @@ class LinksUpdate { * Also useful where link table corruption needs to be repaired, e.g. in refreshLinks.php */ function doDumbUpdate() { - $fname = 'LinksUpdate::doDumbUpdate'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); # Refresh category pages and image description pages $existing = $this->getExistingCategories(); @@ -156,6 +165,7 @@ class LinksUpdate { $this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' ); $this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' ); $this->dumbTableUpdate( 'langlinks', $this->getInterlangInsertions(), 'll_from' ); + $this->dumbTableUpdate( 'page_props', $this->getPropertyInsertions(), 'pp_page' ); # Update the cache of all the category pages and image description pages which were changed $this->invalidateCategories( $categoryUpdates ); @@ -167,7 +177,7 @@ class LinksUpdate { $this->queueRecursiveJobs(); } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } function queueRecursiveJobs() { @@ -209,8 +219,6 @@ class LinksUpdate { * @param array $dbkeys */ function invalidatePages( $namespace, $dbkeys ) { - $fname = 'LinksUpdate::invalidatePages'; - if ( !count( $dbkeys ) ) { return; } @@ -227,7 +235,7 @@ class LinksUpdate { 'page_namespace' => $namespace, 'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')', 'page_touched < ' . $this->mDb->addQuotes( $now ) - ), $fname + ), __METHOD__ ); while ( $row = $this->mDb->fetchObject( $res ) ) { $ids[] = $row->page_id; @@ -245,7 +253,7 @@ class LinksUpdate { array( 'page_id IN (' . $this->mDb->makeList( $ids ) . ')', 'page_touched < ' . $this->mDb->addQuotes( $now ) - ), $fname + ), __METHOD__ ); } @@ -258,13 +266,12 @@ class LinksUpdate { } function dumbTableUpdate( $table, $insertions, $fromField ) { - $fname = 'LinksUpdate::dumbTableUpdate'; - $this->mDb->delete( $table, array( $fromField => $this->mId ), $fname ); + $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ ); if ( count( $insertions ) ) { # The link array was constructed without FOR UPDATE, so there may be collisions # This may cause minor link table inconsistencies, which is better than # crippling the site with lock contention. - $this->mDb->insert( $table, $insertions, $fname, array( 'IGNORE' ) ); + $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) ); } } @@ -285,8 +292,12 @@ class LinksUpdate { * @private */ function incrTableUpdate( $table, $prefix, $deletions, $insertions ) { - $fname = 'LinksUpdate::incrTableUpdate'; - $where = array( "{$prefix}_from" => $this->mId ); + if ( $table == 'page_props' ) { + $fromField = 'pp_page'; + } else { + $fromField = "{$prefix}_from"; + } + $where = array( $fromField => $this->mId ); if ( $table == 'pagelinks' || $table == 'templatelinks' ) { $clause = $this->makeWhereFrom2d( $deletions, $prefix ); if ( $clause ) { @@ -297,6 +308,8 @@ class LinksUpdate { } else { if ( $table == 'langlinks' ) { $toField = 'll_lang'; + } elseif ( $table == 'page_props' ) { + $toField = 'pp_propname'; } else { $toField = $prefix . '_to'; } @@ -307,10 +320,10 @@ class LinksUpdate { } } if ( $where ) { - $this->mDb->delete( $table, $where, $fname ); + $this->mDb->delete( $table, $where, __METHOD__ ); } if ( count( $insertions ) ) { - $this->mDb->insert( $table, $insertions, $fname, 'IGNORE' ); + $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' ); } } @@ -428,6 +441,23 @@ class LinksUpdate { return $arr; } + /** + * Get an array of page property insertions + */ + function getPropertyInsertions( $existing = array() ) { + $diffs = array_diff_assoc( $this->mProperties, $existing ); + $arr = array(); + foreach ( $diffs as $name => $value ) { + $arr[] = array( + 'pp_page' => $this->mId, + 'pp_propname' => $name, + 'pp_value' => $value, + ); + } + return $arr; + } + + /** * Given an array of existing links, returns those links which are not in $this * and thus should be deleted. @@ -498,14 +528,21 @@ class LinksUpdate { return array_diff_assoc( $existing, $this->mInterlangs ); } + /** + * Get array of properties which should be deleted. + * @private + */ + function getPropertyDeletions( $existing ) { + return array_diff_assoc( $existing, $this->mProperties ); + } + /** * Get an array of existing links, as a 2-D array * @private */ function getExistingLinks() { - $fname = 'LinksUpdate::getExistingLinks'; $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ), - array( 'pl_from' => $this->mId ), $fname, $this->mOptions ); + array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions ); $arr = array(); while ( $row = $this->mDb->fetchObject( $res ) ) { if ( !isset( $arr[$row->pl_namespace] ) ) { @@ -522,9 +559,8 @@ class LinksUpdate { * @private */ function getExistingTemplates() { - $fname = 'LinksUpdate::getExistingTemplates'; $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ), - array( 'tl_from' => $this->mId ), $fname, $this->mOptions ); + array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions ); $arr = array(); while ( $row = $this->mDb->fetchObject( $res ) ) { if ( !isset( $arr[$row->tl_namespace] ) ) { @@ -541,9 +577,8 @@ class LinksUpdate { * @private */ function getExistingImages() { - $fname = 'LinksUpdate::getExistingImages'; $res = $this->mDb->select( 'imagelinks', array( 'il_to' ), - array( 'il_from' => $this->mId ), $fname, $this->mOptions ); + array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions ); $arr = array(); while ( $row = $this->mDb->fetchObject( $res ) ) { $arr[$row->il_to] = 1; @@ -557,9 +592,8 @@ class LinksUpdate { * @private */ function getExistingExternals() { - $fname = 'LinksUpdate::getExistingExternals'; $res = $this->mDb->select( 'externallinks', array( 'el_to' ), - array( 'el_from' => $this->mId ), $fname, $this->mOptions ); + array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions ); $arr = array(); while ( $row = $this->mDb->fetchObject( $res ) ) { $arr[$row->el_to] = 1; @@ -573,9 +607,8 @@ class LinksUpdate { * @private */ function getExistingCategories() { - $fname = 'LinksUpdate::getExistingCategories'; $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey' ), - array( 'cl_from' => $this->mId ), $fname, $this->mOptions ); + array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions ); $arr = array(); while ( $row = $this->mDb->fetchObject( $res ) ) { $arr[$row->cl_to] = $row->cl_sortkey; @@ -590,15 +623,30 @@ class LinksUpdate { * @private */ function getExistingInterlangs() { - $fname = 'LinksUpdate::getExistingInterlangs'; $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ), - array( 'll_from' => $this->mId ), $fname, $this->mOptions ); + array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions ); $arr = array(); while ( $row = $this->mDb->fetchObject( $res ) ) { $arr[$row->ll_lang] = $row->ll_title; } return $arr; } + + /** + * Get an array of existing categories, with the name in the key and sort key in the value. + * @private + */ + function getExistingProperties() { + $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ), + array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions ); + $arr = array(); + while ( $row = $this->mDb->fetchObject( $res ) ) { + $arr[$row->pp_propname] = $row->pp_value; + } + $this->mDb->freeResult( $res ); + return $arr; + } + /** * Return the title object of the page being updated @@ -606,5 +654,25 @@ class LinksUpdate { function getTitle() { return $this->mTitle; } + + /** + * Invalidate any necessary link lists related to page property changes + */ + function invalidateProperties( $changed ) { + global $wgPagePropLinkInvalidations; + + foreach ( $changed as $name => $value ) { + if ( isset( $wgPagePropLinkInvalidations[$name] ) ) { + $inv = $wgPagePropLinkInvalidations[$name]; + if ( !is_array( $inv ) ) { + $inv = array( $inv ); + } + foreach ( $inv as $table ) { + $update = new HTMLCacheUpdate( $this->mTitle, $table ); + $update->doUpdate(); + } + } + } + } } diff --git a/includes/MagicWord.php b/includes/MagicWord.php index 18c931c590c..abdd749d0f4 100644 --- a/includes/MagicWord.php +++ b/includes/MagicWord.php @@ -140,7 +140,19 @@ class MagicWord { 'numberofadmins' => 3600, ); + static public $mDoubleUnderscoreIDs = array( + 'notoc', + 'nogallery', + 'forcetoc', + 'toc', + 'noeditsection', + 'newsectionlink', + 'hiddencat', + ); + + static public $mObjects = array(); + static public $mDoubleUnderscoreArray = null; /**#@-*/ @@ -197,7 +209,14 @@ class MagicWord { return -1; } } - + + /** Get a MagicWordArray of double-underscore entities */ + static function getDoubleUnderscoreArray() { + if ( is_null( self::$mDoubleUnderscoreArray ) ) { + self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs ); + } + return self::$mDoubleUnderscoreArray; + } # Initialises this object with an ID function load( $id ) { @@ -449,6 +468,7 @@ class MagicWordArray { var $names = array(); var $hash; var $baseRegex, $regex; + var $matches; function __construct( $names = array() ) { $this->names = $names; @@ -555,6 +575,8 @@ class MagicWordArray { /** * Parse a match array from preg_match + * Returns array(magic word ID, parameter value) + * If there is no parameter value, that element will be false. */ function parseMatch( $m ) { reset( $m ); @@ -613,4 +635,25 @@ class MagicWordArray { } return false; } + + /** + * Returns an associative array, ID => param value, for all items that match + * Removes the matched items from the input string (passed by reference) + */ + public function matchAndRemove( &$text ) { + $found = array(); + $regexes = $this->getRegex(); + foreach ( $regexes as $regex ) { + if ( $regex === '' ) { + continue; + } + preg_match_all( $regex, $text, $matches, PREG_SET_ORDER ); + foreach ( $matches as $m ) { + list( $name, $param ) = $this->parseMatch( $m ); + $found[$name] = $param; + } + $text = preg_replace( $regex, '', $text ); + } + return $found; + } } diff --git a/includes/OutputPage.php b/includes/OutputPage.php index d29ba35ef46..41e42eabf49 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -270,18 +270,40 @@ class OutputPage { /** * Add an array of categories, with names in the keys */ - public function addCategoryLinks($categories) { + public function addCategoryLinks( $categories ) { global $wgUser, $wgContLang; if ( !is_array( $categories ) ) { return; } - # Add the links to the link cache in a batch + if ( count( $categories ) == 0 ) { + return; + } + # Add the links to a LinkBatch $arr = array( NS_CATEGORY => $categories ); $lb = new LinkBatch; $lb->setArray( $arr ); - $lb->execute(); + # Fetch existence plus the hiddencat property + $dbr = wfGetDB( DB_SLAVE ); + $pageTable = $dbr->tableName( 'page' ); + $propsTable = $dbr->tableName( 'page_props' ); + $where = $lb->constructSet( 'page', $dbr ); + $sql = "SELECT page_id, page_namespace, page_title, pp_value FROM $pageTable LEFT JOIN $propsTable " . + " ON pp_page=page_id WHERE ($where) AND pp_propname='hiddencat'"; + $res = $dbr->query( $sql, __METHOD__ ); + + # Add the results to the link cache + $lb->addResultToCache( LinkCache::singleton(), $res ); + + # Remove categories with hiddencat + foreach ( $res as $row ) { + if ( isset( $row->pp_value ) ) { + unset( $categories[$row->page_title] ); + } + } + + # Add the remaining categories to the skin $sk = $wgUser->getSkin(); foreach ( $categories as $category => $unused ) { $title = Title::makeTitleSafe( NS_CATEGORY, $category ); @@ -389,11 +411,11 @@ class OutputPage { // Versioning... $this->mTemplateIds += (array)$parserOutput->mTemplateIds; - # Display title + // Display title if( ( $dt = $parserOutput->getDisplayTitle() ) !== false ) $this->setPageTitle( $dt ); - # Hooks registered in the object + // Hooks registered in the object global $wgParserOutputHooks; foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { list( $hookName, $data ) = $hookInfo; diff --git a/includes/Parser.php b/includes/Parser.php index 41eabe4f54b..65c1c02b558 100644 --- a/includes/Parser.php +++ b/includes/Parser.php @@ -98,7 +98,7 @@ class Parser var $mInterwikiLinkHolders, $mLinkHolders; var $mIncludeSizes, $mPPNodeCount, $mDefaultSort; var $mTplExpandCache; // empty-frame expansion cache - var $mTplRedirCache, $mTplDomCache, $mHeadings; + var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores; # Temporary # These are variables reset at least once per parse regardless of $clearState @@ -147,51 +147,9 @@ class Parser $this->mFirstCall = false; wfProfileIn( __METHOD__ ); - global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions; $this->setHook( 'pre', array( $this, 'renderPreTag' ) ); - - # Syntax for arguments (see self::setFunctionHook): - # "name for lookup in localized magic words array", - # function callback, - # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...} - # instead of {{#int:...}}) - $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH ); - $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH ); - $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH ); - $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH ); - $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH ); - $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH ); - $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH ); - $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH ); - $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH ); - $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH ); - $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH ); - $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH ); - $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH ); - $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH ); - $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH ); - $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH ); - $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH ); - $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH ); - $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) ); - $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH ); - $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH ); - $this->setFunctionHook( 'tag', array( 'CoreParserFunctions', 'tagObj' ), SFH_OBJECT_ARGS ); - - if ( $wgAllowDisplayTitle ) { - $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH ); - } - if ( $wgAllowSlowParserFunctions ) { - $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH ); - } - + CoreParserFunctions::register( $this ); $this->initialiseVariables(); wfRunHooks( 'ParserFirstCallInit', array( &$this ) ); @@ -256,6 +214,7 @@ class Parser $this->mPPNodeCount = 0; $this->mDefaultSort = false; $this->mHeadings = array(); + $this->mDoubleUnderscores = array(); # Fix cloning if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) { @@ -991,8 +950,7 @@ class Parser $text = preg_replace( '/(^|\n)-----*/', '\\1