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
', $text ); - $text = $this->stripToc( $text ); - $this->stripNoGallery( $text ); + $text = $this->doDoubleUnderscore( $text ); $text = $this->doHeadings( $text ); if($this->mOptions->getUseDynamicDates()) { $df =& DateFormatter::getInstance(); @@ -3302,32 +3260,11 @@ class Parser } /** - * Detect __NOGALLERY__ magic word and set a placeholder + * Strip double-underscore items like __NOGALLERY__ and __NOTOC__ + * Fills $this->mDoubleUnderscores, returns the modified text */ - function stripNoGallery( &$text ) { - # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML, - # do not add TOC - $mw = MagicWord::get( 'nogallery' ); - $this->mOutput->mNoGallery = $mw->matchAndRemove( $text ) ; - } - - /** - * Find the first __TOC__ magic word and set a - * placeholder that will then be replaced by the real TOC in - * ->formatHeadings, this works because at this points real - * comments will have already been discarded by the sanitizer. - * - * Any additional __TOC__ magic words left over will be discarded - * as there can only be one TOC on the page. - */ - function stripToc( $text ) { - # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, - # do not add TOC - $mw = MagicWord::get( 'notoc' ); - if( $mw->matchAndRemove( $text ) ) { - $this->mShowToc = false; - } - + function doDoubleUnderscore( $text ) { + // The position of __TOC__ needs to be recorded $mw = MagicWord::get( 'toc' ); if( $mw->match( $text ) ) { $this->mShowToc = true; @@ -3339,6 +3276,20 @@ class Parser // Only keep the first one. $text = $mw->replace( '', $text ); } + + // Now match and remove the rest of them + $mwa = MagicWord::getDoubleUnderscoreArray(); + $this->mDoubleUnderscores = $mwa->matchAndRemove( $text ); + + if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) { + $this->mOutput->mNoGallery = true; + } + if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) { + $this->mShowToc = false; + } + if ( isset( $this->mDoubleUnderscores['hiddencat'] ) ) { + $this->mOutput->setProperty( 'hiddencat', 'y' ); + } return $text; } @@ -3367,8 +3318,7 @@ class Parser } # Inhibit editsection links if requested in the page - $esw =& MagicWord::get( 'noeditsection' ); - if( $esw->matchAndRemove( $text ) ) { + if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) { $showEditLink = 0; } @@ -3384,14 +3334,13 @@ class Parser # Allow user to stipulate that a page should have a "new section" # link added via __NEWSECTIONLINK__ - $mw =& MagicWord::get( 'newsectionlink' ); - if( $mw->matchAndRemove( $text ) ) + if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) { $this->mOutput->setNewSection( true ); + } # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, # override above conditions and always show TOC above first header - $mw =& MagicWord::get( 'forcetoc' ); - if ($mw->matchAndRemove( $text ) ) { + if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) { $this->mShowToc = true; $enoughToc = true; } diff --git a/includes/ParserOutput.php b/includes/ParserOutput.php index 9b3c12c1fed..bed56836e19 100644 --- a/includes/ParserOutput.php +++ b/includes/ParserOutput.php @@ -22,8 +22,9 @@ class ParserOutput $mHeadItems, # Items to put in the section $mOutputHooks, # Hook tags as per $wgParserOutputHooks $mWarnings, # Warning text to be returned to the user. Wikitext formatted. - $mSections; # Table of contents - + $mSections, # Table of contents + $mProperties; # Name/value pairs to be cached in the DB + /** * Overridden title for display */ @@ -50,6 +51,7 @@ class ParserOutput $this->mTemplateIds = array(); $this->mOutputHooks = array(); $this->mWarnings = array(); + $this->mProperties = array(); } function getText() { return $this->mText; } @@ -183,7 +185,24 @@ class ParserOutput public function getFlag( $flag ) { return isset( $this->mFlags[$flag] ); } - + + /** + * Set a property to be cached in the DB + */ + public function setProperty( $name, $value ) { + $this->mProperties[$name] = $value; + } + + public function getProperty( $name ){ + return isset( $this->mProperties[$name] ) ? $this->mProperties[$name] : false; + } + + public function getProperties() { + if ( !isset( $this->mProperties ) ) { + $this->mProperties = array(); + } + return $this->mProperties; + } } diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 574cdcb8c4e..6aaff1d9d6e 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -337,6 +337,7 @@ $magicWords = array( 'defaultsort' => array( 1, 'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ), 'filepath' => array( 0, 'FILEPATH:' ), 'tag' => array( 0, 'tag' ), + 'hiddencat' => array( 1, '__HIDDENCAT__' ), ); /** diff --git a/maintenance/archives/patch-page_props.sql b/maintenance/archives/patch-page_props.sql new file mode 100644 index 00000000000..774e0e275ea --- /dev/null +++ b/maintenance/archives/patch-page_props.sql @@ -0,0 +1,9 @@ +-- Name/value pairs indexed by page_id +CREATE TABLE /*$wgDBprefix*/page_props ( + pp_page int NOT NULL, + pp_propname varbinary(60) NOT NULL, + pp_value blob NOT NULL, + + PRIMARY KEY (pp_page,pp_propname) +); + diff --git a/maintenance/tables.sql b/maintenance/tables.sql index b26498b2904..b99ec62bf9e 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -1184,4 +1184,13 @@ CREATE TABLE /*$wgDBprefix*/protected_titles ( KEY pt_timestamp (pt_timestamp) ) /*$wgDBTableOptions*/; +-- Name/value pairs indexed by page_id +CREATE TABLE /*$wgDBprefix*/page_props ( + pp_page int NOT NULL, + pp_propname varbinary(60) NOT NULL, + pp_value blob NOT NULL, + + PRIMARY KEY (pp_page,pp_propname) +); + -- vim: sw=2 sts=2 et diff --git a/maintenance/updaters.inc b/maintenance/updaters.inc index c628216056b..ef55e63142b 100644 --- a/maintenance/updaters.inc +++ b/maintenance/updaters.inc @@ -39,6 +39,7 @@ $wgNewTables = array( array( 'querycachetwo', 'patch-querycachetwo.sql' ), array( 'redirect', 'patch-redirect.sql' ), array( 'protected_titles', 'patch-protected_titles.sql' ), + array( 'page_props', 'patch-page_props.sql' ), ); $wgNewFields = array(