From 269a91037b7e47e92197fed5c3a16ecc2567f4e3 Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Wed, 20 Feb 2008 08:53:12 +0000 Subject: [PATCH] * Added __HIDDENCAT__ feature, to hide categories from the box at the bottom of the member pages depending on special text on the category page. * Added page_props backend, generic parser-driven page properties which, when changed, invalidate the cache of backlinked pages on any links table. Could be used to add overlays to images depending on their deletion status, or to add icons to articles based on category membership. * Refactored double-underscore handling in the parser. * Moved CoreParserFunctions registration to CoreParserFunctions. --- includes/CoreParserFunctions.php | 46 ++++++++ includes/DefaultSettings.php | 8 ++ includes/LinkBatch.php | 29 ++--- includes/LinksUpdate.php | 126 +++++++++++++++++----- includes/MagicWord.php | 45 +++++++- includes/OutputPage.php | 32 +++++- includes/Parser.php | 103 +++++------------- includes/ParserOutput.php | 25 ++++- languages/messages/MessagesEn.php | 1 + maintenance/archives/patch-page_props.sql | 9 ++ maintenance/tables.sql | 9 ++ maintenance/updaters.inc | 1 + 12 files changed, 305 insertions(+), 129 deletions(-) create mode 100644 maintenance/archives/patch-page_props.sql diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php index 61dbafe551..594f018e2e 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 89943ae2d4..f30d34ad01 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 db1114c938..c5c5721eaf 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 a52414c39b..152058ac5a 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 18c931c590..abdd749d0f 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 d29ba35ef4..41e42eabf4 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 41eabe4f54..65c1c02b55 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 9b3c12c1fe..bed56836e1 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 574cdcb8c4..6aaff1d9d6 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 0000000000..774e0e275e --- /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 b26498b290..b99ec62bf9 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 c628216056..ef55e63142 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( -- 2.20.1