summaryrefslogtreecommitdiff
path: root/includes/resourceloader/ResourceLoaderWikiModule.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/resourceloader/ResourceLoaderWikiModule.php')
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php198
1 files changed, 107 insertions, 91 deletions
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index 7b44cc67..0023de27 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -26,15 +26,30 @@
* Abstraction for resource loader modules which pull from wiki pages
*
* This can only be used for wiki pages in the MediaWiki and User namespaces,
- * because of its dependence on the functionality of
- * Title::isCssJsSubpage.
+ * because of its dependence on the functionality of Title::isCssJsSubpage.
+ *
+ * This module supports being used as a placeholder for a module on a remote wiki.
+ * To do so, getDB() must be overloaded to return a foreign database object that
+ * allows local wikis to query page metadata.
+ *
+ * Safe for calls on local wikis are:
+ * - Option getters:
+ * - getGroup()
+ * - getPosition()
+ * - getPages()
+ * - Basic methods that strictly involve the foreign database
+ * - getDB()
+ * - isKnownEmpty()
+ * - getTitleInfo()
*/
class ResourceLoaderWikiModule extends ResourceLoaderModule {
+ /** @var string Position on the page to load this module at */
+ protected $position = 'bottom';
// Origin defaults to users with sitewide authority
protected $origin = self::ORIGIN_USER_SITEWIDE;
- // In-object cache for title info
+ // In-process cache for title info
protected $titleInfo = array();
// List of page names that contain CSS
@@ -50,14 +65,21 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @param array $options For back-compat, this can be omitted in favour of overwriting getPages.
*/
public function __construct( array $options = null ) {
- if ( isset( $options['styles'] ) ) {
- $this->styles = $options['styles'];
+ if ( is_null( $options ) ) {
+ return;
}
- if ( isset( $options['scripts'] ) ) {
- $this->scripts = $options['scripts'];
- }
- if ( isset( $options['group'] ) ) {
- $this->group = $options['group'];
+
+ foreach ( $options as $member => $option ) {
+ switch ( $member ) {
+ case 'position':
+ $this->isPositionDefined = true;
+ // Don't break since we need the member set as well
+ case 'styles':
+ case 'scripts':
+ case 'group':
+ $this->{$member} = $option;
+ break;
+ }
}
}
@@ -107,13 +129,13 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
/**
- * Get the Database object used in getTitleMTimes(). Defaults to the local slave DB
- * but subclasses may want to override this to return a remote DB object, or to return
- * null if getTitleMTimes() shouldn't access the DB at all.
+ * Get the Database object used in getTitleInfo().
+ *
+ * Defaults to the local slave DB. Subclasses may want to override this to return a foreign
+ * database object, or null if getTitleInfo() shouldn't access the database.
*
- * NOTE: This ONLY works for getTitleMTimes() and getModifiedTime(), NOT FOR ANYTHING ELSE.
- * In particular, it doesn't work for getting the content of JS and CSS pages. That functionality
- * will use the local DB irrespective of the return value of this method.
+ * NOTE: This ONLY works for getTitleInfo() and isKnownEmpty(), NOT FOR ANYTHING ELSE.
+ * In particular, it doesn't work for getContent() or getScript() etc.
*
* @return IDatabase|null
*/
@@ -122,10 +144,15 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
/**
- * @param Title $title
+ * @param string $title
* @return null|string
*/
- protected function getContent( $title ) {
+ protected function getContent( $titleText ) {
+ $title = Title::newFromText( $titleText );
+ if ( !$title ) {
+ return null;
+ }
+
$handler = ContentHandler::getForTitle( $title );
if ( $handler->isSupportedFormat( CONTENT_FORMAT_CSS ) ) {
$format = CONTENT_FORMAT_CSS;
@@ -160,11 +187,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( $options['type'] !== 'script' ) {
continue;
}
- $title = Title::newFromText( $titleText );
- if ( !$title || $title->isRedirect() ) {
- continue;
- }
- $script = $this->getContent( $title );
+ $script = $this->getContent( $titleText );
if ( strval( $script ) !== '' ) {
$script = $this->validateScriptFile( $titleText, $script );
$scripts .= ResourceLoader::makeComment( $titleText ) . $script . "\n";
@@ -183,12 +206,8 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( $options['type'] !== 'style' ) {
continue;
}
- $title = Title::newFromText( $titleText );
- if ( !$title || $title->isRedirect() ) {
- continue;
- }
$media = isset( $options['media'] ) ? $options['media'] : 'all';
- $style = $this->getContent( $title );
+ $style = $this->getContent( $titleText );
if ( strval( $style ) === '' ) {
continue;
}
@@ -206,37 +225,31 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
/**
- * @param ResourceLoaderContext $context
- * @return int
+ * Disable module content versioning.
+ *
+ * This class does not support generating content outside of a module
+ * request due to foreign database support.
+ *
+ * See getDefinitionSummary() for meta-data versioning.
+ *
+ * @return bool
*/
- public function getModifiedTime( ResourceLoaderContext $context ) {
- $modifiedTime = 1;
- $titleInfo = $this->getTitleInfo( $context );
- if ( count( $titleInfo ) ) {
- $mtimes = array_map( function ( $value ) {
- return $value['timestamp'];
- }, $titleInfo );
- $modifiedTime = max( $modifiedTime, max( $mtimes ) );
- }
- $modifiedTime = max(
- $modifiedTime,
- $this->getMsgBlobMtime( $context->getLanguage() ),
- $this->getDefinitionMtime( $context )
- );
- return $modifiedTime;
+ public function enableModuleContentVersion() {
+ return false;
}
/**
- * Get the definition summary for this module.
- *
* @param ResourceLoaderContext $context
* @return array
*/
public function getDefinitionSummary( ResourceLoaderContext $context ) {
- return array(
- 'class' => get_class( $this ),
+ $summary = parent::getDefinitionSummary( $context );
+ $summary[] = array(
'pages' => $this->getPages( $context ),
+ // Includes SHA1 of content
+ 'titleInfo' => $this->getTitleInfo( $context ),
);
+ return $summary;
}
/**
@@ -244,33 +257,29 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @return bool
*/
public function isKnownEmpty( ResourceLoaderContext $context ) {
- $titleInfo = $this->getTitleInfo( $context );
- // Bug 68488: For modules in the "user" group, we should actually
- // check that the pages are empty (page_len == 0), but for other
- // groups, just check the pages exist so that we don't end up
- // caching temporarily-blank pages without the appropriate
- // <script> or <link> tag.
- if ( $this->getGroup() !== 'user' ) {
- return count( $titleInfo ) === 0;
- }
+ $revisions = $this->getTitleInfo( $context );
- foreach ( $titleInfo as $info ) {
- if ( $info['length'] !== 0 ) {
- // At least one non-0-lenth page, not empty
- return false;
+ // For user modules, don't needlessly load if there are no non-empty pages
+ if ( $this->getGroup() === 'user' ) {
+ foreach ( $revisions as $revision ) {
+ if ( $revision['rev_len'] > 0 ) {
+ // At least one non-empty page, module should be loaded
+ return false;
+ }
}
+ return true;
}
- // All pages are 0-length, so it's empty
- return true;
+ // Bug 68488: For other modules (i.e. ones that are called in cached html output) only check
+ // page existance. This ensures that, if some pages in a module are temporarily blanked,
+ // we don't end omit the module's script or link tag on some pages.
+ return count( $revisions ) === 0;
}
/**
- * Get the modification times of all titles that would be loaded for
- * a given context.
- * @param ResourceLoaderContext $context Context object
- * @return array Keyed by page dbkey. Value is an array with 'length' and 'timestamp'
- * keys, where the timestamp is a UNIX timestamp
+ * Get the information about the wiki pages for a given context.
+ * @param ResourceLoaderContext $context
+ * @return array Keyed by page name. Contains arrays with 'rev_len' and 'rev_sha1' keys
*/
protected function getTitleInfo( ResourceLoaderContext $context ) {
$dbr = $this->getDB();
@@ -279,31 +288,38 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
return array();
}
- $hash = $context->getHash();
- if ( isset( $this->titleInfo[$hash] ) ) {
- return $this->titleInfo[$hash];
- }
-
- $this->titleInfo[$hash] = array();
- $batch = new LinkBatch;
- foreach ( $this->getPages( $context ) as $titleText => $options ) {
- $batch->addObj( Title::newFromText( $titleText ) );
- }
+ $pages = $this->getPages( $context );
+ $key = implode( '|', array_keys( $pages ) );
+ if ( !isset( $this->titleInfo[$key] ) ) {
+ $this->titleInfo[$key] = array();
+ $batch = new LinkBatch;
+ foreach ( $pages as $titleText => $options ) {
+ $batch->addObj( Title::newFromText( $titleText ) );
+ }
- if ( !$batch->isEmpty() ) {
- $res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title', 'page_touched', 'page_len' ),
- $batch->constructSet( 'page', $dbr ),
- __METHOD__
- );
- foreach ( $res as $row ) {
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $this->titleInfo[$hash][$title->getPrefixedDBkey()] = array(
- 'timestamp' => wfTimestamp( TS_UNIX, $row->page_touched ),
- 'length' => $row->page_len,
+ if ( !$batch->isEmpty() ) {
+ $res = $dbr->select( array( 'page', 'revision' ),
+ array( 'page_namespace', 'page_title', 'rev_len', 'rev_sha1' ),
+ $batch->constructSet( 'page', $dbr ),
+ __METHOD__,
+ array(),
+ array( 'revision' => array( 'INNER JOIN', array( 'page_latest=rev_id' ) ) )
);
+ foreach ( $res as $row ) {
+ // Avoid including ids or timestamps of revision/page tables so
+ // that versions are not wasted
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $this->titleInfo[$key][$title->getPrefixedText()] = array(
+ 'rev_len' => $row->rev_len,
+ 'rev_sha1' => $row->rev_sha1,
+ );
+ }
}
}
- return $this->titleInfo[$hash];
+ return $this->titleInfo[$key];
+ }
+
+ public function getPosition() {
+ return $this->position;
}
}