*/
protected function buildExemptModules() {
$chunks = [];
- // Things that go after the ResourceLoaderDynamicStyles marker
- $append = [];
- // We want site, private and user styles to override dynamically added styles from
- // general modules, but we want dynamically added styles to override statically added
- // style modules. So the order has to be:
- // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
- // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
- // - ResourceLoaderDynamicStyles marker
- // - site/private/user styles
+ // Requirements:
+ // - Within modules provided by the software (core, skin, extensions),
+ // styles from skin stylesheets should be overridden by styles
+ // from modules dynamically loaded with JavaScript.
+ // - Styles from site-specific, private, and user modules should override
+ // both of the above.
+ //
+ // The effective order for stylesheets must thus be:
+ // 1. Page style modules, formatted server-side by ResourceLoaderClientHtml.
+ // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
+ // 3. Styles that are site-specific, private or from the user, formatted
+ // server-side by this function.
+ //
+ // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
+ // point #2 is.
// Add legacy styles added through addStyle()/addInlineStyle() here
$chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
- $chunks[] = Html::element(
- 'meta',
- [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
- );
-
+ // Things that go after the ResourceLoaderDynamicStyles marker
+ $append = [];
$separateReq = [ 'site.styles', 'user.styles' ];
foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
- // Combinable modules
- $chunks[] = $this->makeResourceLoaderLink(
- array_diff( $moduleNames, $separateReq ),
- ResourceLoaderModule::TYPE_STYLES
- );
-
- foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
- // These require their own dedicated request in order to support "@import"
- // syntax, which is incompatible with concatenation. (T147667, T37562)
- $chunks[] = $this->makeResourceLoaderLink( $name,
+ if ( $moduleNames ) {
+ $append[] = $this->makeResourceLoaderLink(
+ array_diff( $moduleNames, $separateReq ),
ResourceLoaderModule::TYPE_STYLES
);
+
+ foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
+ // These require their own dedicated request in order to support "@import"
+ // syntax, which is incompatible with concatenation. (T147667, T37562)
+ $append[] = $this->makeResourceLoaderLink( $name,
+ ResourceLoaderModule::TYPE_STYLES
+ );
+ }
}
}
+ if ( $append ) {
+ $chunks[] = Html::element(
+ 'meta',
+ [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
+ );
+ $chunks = array_merge( $chunks, $append );
+ }
- return self::combineWrappedStrings( array_merge( $chunks, $append ) );
+ return self::combineWrappedStrings( $chunks );
}
/**
/**
* Show the input form with optional error message
*
- * @param string|null $err Error message or null if there's no error
+ * @param string|string[]|null $err Error message or null if there's no error
*/
function show( $err = null ) {
$out = $this->mContext->getOutput();
<?php
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
/**
* A service class for fetching the wiki's current read-only mode.
/** @var ConfiguredReadOnlyMode */
private $configuredReadOnly;
- /** @var LoadBalancer */
+ /** @var ILoadBalancer */
private $loadBalancer;
- public function __construct( ConfiguredReadOnlyMode $cro, LoadBalancer $loadBalancer ) {
+ public function __construct( ConfiguredReadOnlyMode $cro, ILoadBalancer $loadBalancer ) {
$this->configuredReadOnly = $cro;
$this->loadBalancer = $loadBalancer;
}
use Wikimedia\Rdbms\DBConnRef;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
/**
* Service for looking up page revisions.
* Factory method for SlotRecords based on known slot rows.
*
* @param int $revId The revision to load slots for.
- * @param object[]|ResultWrapper $slotRows
+ * @param object[]|IResultWrapper $slotRows
* @param int $queryFlags
* @param Title $title
*
try {
$sha1 = $this->getStringField( 'content_sha1' );
} catch ( IncompleteRevisionException $ex ) {
+ $sha1 = null;
+ }
+
+ // Compute if missing. Missing could mean null or empty.
+ if ( $sha1 === null || $sha1 === '' ) {
$format = $this->hasField( 'format_name' )
? $this->getStringField( 'format_name' )
: null;
/**
* @param int $index
- * @param string[] $groups
+ * @param string[]|string $groups
* @return IDatabase
*/
private static function getDB( $index, $groups = [] ) {
*/
use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
/**
* @param int $limit The limit number of revisions to get
* @param int $offset
* @param int $direction Either self::DIR_PREV or self::DIR_NEXT
- * @return ResultWrapper
+ * @return IResultWrapper
*/
function fetchRevisions( $limit, $offset, $direction ) {
// Fail if article doesn't exist.
* @param BagOStuff $mainStash
* @param WANObjectCache $wanCache
* @return array
+ * @internal For use with service wiring
*/
public static function applyDefaultConfig(
array $lbConf,
$serversCheck = [ $lbConf['serverTemplate'] ] ?? [];
}
- self::assertValidServerConfigs( $serversCheck, $options->get( 'DBname' ),
- $options->get( 'DBprefix' ) );
+ self::assertValidServerConfigs(
+ $serversCheck,
+ $options->get( 'DBname' ),
+ $options->get( 'DBprefix' )
+ );
$lbConf = self::injectObjectCaches( $lbConf, $srvCace, $mainStash, $wanCache );
* @return array
*/
private static function getDbTypesWithSchemas() {
- return [ 'postgres', 'msssql' ];
+ return [ 'postgres', 'mssql' ];
}
/**
*
* @param array $config (e.g. $wgLBFactoryConf)
* @return string Class name
+ * @internal For use with service wiring
*/
public static function getLBFactoryClass( array $config ) {
// For configuration backward compatibility after removing
/**
* @param LBFactory $lbFactory
* @param string $dbType 'mysql', 'sqlite', etc.
+ * @internal For use with service wiring
*/
public static function setSchemaAliases( LBFactory $lbFactory, $dbType ) {
- if ( $dbType instanceof Config ) {
- // Before 1.34 this took a whole Config just to get $dbType
- wfDeprecated( __METHOD__ . ' with Config argument', '1.34' );
- $dbType = $dbType->get( 'DBtype' );
- }
if ( $dbType === 'mysql' ) {
/**
* When SQLite indexes were introduced in r45764, it was noted that
/**
* Log a database deprecation warning
* @param string $msg Deprecation message
+ * @internal For use with service wiring
*/
public static function logDeprecation( $msg ) {
global $wgDevelopmentWarnings;
* @param int[] $indexes List of backing cache indexes
* @param bool $asyncWrites
* @param string $method Method name of backing caches
- * @param array[] $args Arguments to the method of backing caches
+ * @param array $args Arguments to the method of backing caches
* @return bool
*/
protected function doWrite( $indexes, $asyncWrites, $method, array $args ) {
* not. The safest response for us is to explicitly destroy the connection
* object and let it be reopened during the next request.
* @param RedisConnRef $conn
- * @param Exception $e
+ * @param RedisException $e
*/
protected function handleException( RedisConnRef $conn, $e ) {
$this->setLastError( BagOStuff::ERR_UNEXPECTED );
/**
* @param ILoadBalancer $lb Connection manager for $conn
- * @param Database|array $conn Database or (server index, query groups, domain, flags)
+ * @param IDatabase|array $conn Database or (server index, query groups, domain, flags)
* @param int $role The type of connection asked for; one of DB_MASTER/DB_REPLICA
* @internal This method should not be called outside of LoadBalancer
*/
'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
- 'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
'wgEnableUploads' => $conf->get( 'EnableUploads' ),
return $baseModules;
}
+ /**
+ * Get the localStorage key for the entire module store. The key references
+ * $wgDBname to prevent clashes between wikis under the same web domain.
+ *
+ * @return string localStorage item key for JavaScript
+ */
+ private function getStoreKey() {
+ return 'MediaWikiModuleStore:' . $this->getConfig()->get( 'DBname' );
+ }
+
+ /**
+ * Get the key on which the JavaScript module cache (mw.loader.store) will vary.
+ *
+ * @param ResourceLoaderContext $context
+ * @return string String of concatenated vary conditions
+ */
+ private function getStoreVary( ResourceLoaderContext $context ) {
+ return implode( ':', [
+ $context->getSkin(),
+ $this->getConfig()->get( 'ResourceLoaderStorageVersion' ),
+ $context->getLanguage(),
+ ] );
+ }
+
/**
* @param ResourceLoaderContext $context
* @return string JavaScript code
'$VARS.maxQueryLength' => ResourceLoader::encodeJsonForScript(
$conf->get( 'ResourceLoaderMaxQueryLength' )
),
+ '$VARS.storeKey' => ResourceLoader::encodeJsonForScript( $this->getStoreKey() ),
+ '$VARS.storeVary' => ResourceLoader::encodeJsonForScript( $this->getStoreVary( $context ) ),
];
$profilerStubs = [
'$CODE.profileExecuteStart();' => 'mw.loader.profiler.onExecuteStart( module );',
<?php
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
/**
* Represents the site configuration of a wiki.
protected $sites = null;
/**
- * @var LoadBalancer
+ * @var ILoadBalancer
*/
private $dbLoadBalancer;
* @todo inject some kind of connection manager that is aware of the target wiki,
* instead of injecting a LoadBalancer.
*
- * @param LoadBalancer $dbLoadBalancer
+ * @param ILoadBalancer $dbLoadBalancer
*/
- public function __construct( LoadBalancer $dbLoadBalancer ) {
+ public function __construct( ILoadBalancer $dbLoadBalancer ) {
$this->dbLoadBalancer = $dbLoadBalancer;
}
) .
$this->getFooterText( $key );
- $tabPanels[] = new OOUI\TabPanelLayout( [
+ $tabPanels[] = new OOUI\TabPanelLayout( 'mw-prefsection-' . $key, [
'classes' => [ 'mw-htmlform-autoinfuse-lazy' ],
- 'name' => 'mw-prefsection-' . $key,
'label' => $label,
'content' => new OOUI\FieldsetLayout( [
'classes' => [ 'mw-prefs-section-fieldset' ],
use MediaWiki\User\UserIdentity;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
/**
* Class performing complex database queries related to WatchedItems.
const SORT_DESC = 'DESC';
/**
- * @var LoadBalancer
+ * @var ILoadBalancer
*/
private $loadBalancer;
private $watchedItemStore;
public function __construct(
- LoadBalancer $loadBalancer,
+ ILoadBalancer $loadBalancer,
CommentStore $commentStore,
ActorMigration $actorMigration,
WatchedItemStoreInterface $watchedItemStore
"edit-error-short": "Error: $1",
"edit-error-long": "Errors:\n\n$1",
"specialmute": "Mute",
- "specialmute-success": "Your mute preferences have been successfully updated. See all muted users in [[Special:Preferences]].",
+ "specialmute-success": "Your mute preferences have been updated. See all muted users in [[Special:Preferences|your preferences]].",
"specialmute-submit": "Confirm",
"specialmute-label-mute-email": "Mute emails from this user",
"specialmute-header": "Please select your mute preferences for {{BIDI:[[User:$1]]}}.",
require_once __DIR__ . '/Maintenance.php';
+use MediaWiki\MediaWikiServices;
+
/**
* Maintenance script that fills the rev_sha1 and ar_sha1 columns of revision
* and archive tables for revisions created before MW 1.19.
$this->fatalError( "archive table does not exist" );
} elseif ( !$db->fieldExists( 'revision', 'rev_sha1', __METHOD__ ) ) {
$this->output( "rev_sha1 column does not exist\n\n", true );
-
return false;
}
+ $revStore = MediaWikiServices::getInstance()->getRevisionStore();
+
$this->output( "Populating rev_sha1 column\n" );
- $rc = $this->doSha1Updates( 'revision', 'rev_id', Revision::getQueryInfo(), 'rev' );
+ $rc = $this->doSha1Updates( $revStore, 'revision', 'rev_id',
+ $revStore->getQueryInfo(), 'rev'
+ );
$this->output( "Populating ar_sha1 column\n" );
- $ac = $this->doSha1Updates( 'archive', 'ar_rev_id', Revision::getArchiveQueryInfo(), 'ar' );
+ $ac = $this->doSha1Updates( $revStore, 'archive', 'ar_rev_id',
+ $revStore->getArchiveQueryInfo(), 'ar'
+ );
$this->output( "Populating ar_sha1 column legacy rows\n" );
- $ac += $this->doSha1LegacyUpdates();
+ $ac += $this->doSha1LegacyUpdates( $revStore );
$this->output( "rev_sha1 and ar_sha1 population complete "
. "[$rc revision rows, $ac archive rows].\n" );
}
/**
+ * @param MediaWiki\Revision\RevisionStore $revStore
* @param string $table
* @param string $idCol
* @param array $queryInfo
* @param string $prefix
* @return int Rows changed
*/
- protected function doSha1Updates( $table, $idCol, $queryInfo, $prefix ) {
+ protected function doSha1Updates( $revStore, $table, $idCol, $queryInfo, $prefix ) {
$db = $this->getDB( DB_MASTER );
$batchSize = $this->getBatchSize();
$start = $db->selectField( $table, "MIN($idCol)", '', __METHOD__ );
$blockEnd = $start + $batchSize - 1;
while ( $blockEnd <= $end ) {
$this->output( "...doing $idCol from $blockStart to $blockEnd\n" );
+
$cond = "$idCol BETWEEN " . (int)$blockStart . " AND " . (int)$blockEnd .
" AND $idCol IS NOT NULL AND {$prefix}_sha1 = ''";
$res = $db->select(
$this->beginTransaction( $db, __METHOD__ );
foreach ( $res as $row ) {
- if ( $this->upgradeRow( $row, $table, $idCol, $prefix ) ) {
+ if ( $this->upgradeRow( $revStore, $row, $table, $idCol, $prefix ) ) {
$count++;
}
}
}
/**
+ * @param MediaWiki\Revision\RevisionStore $revStore
+ * @param string $emptySha1
* @return int
*/
- protected function doSha1LegacyUpdates() {
+ protected function doSha1LegacyUpdates( $revStore ) {
$count = 0;
$db = $this->getDB( DB_MASTER );
- $arQuery = Revision::getArchiveQueryInfo();
+ $arQuery = $revStore->getArchiveQueryInfo();
$res = $db->select( $arQuery['tables'], $arQuery['fields'],
[ 'ar_rev_id IS NULL', 'ar_sha1' => '' ], __METHOD__, [], $arQuery['joins'] );
$updateSize = 0;
$this->beginTransaction( $db, __METHOD__ );
foreach ( $res as $row ) {
- if ( $this->upgradeLegacyArchiveRow( $row ) ) {
+ if ( $this->upgradeLegacyArchiveRow( $revStore, $row ) ) {
++$count;
}
if ( ++$updateSize >= 100 ) {
}
/**
+ * @param MediaWiki\Revision\RevisionStore $revStore
* @param stdClass $row
* @param string $table
* @param string $idCol
* @param string $prefix
* @return bool
*/
- protected function upgradeRow( $row, $table, $idCol, $prefix ) {
+ protected function upgradeRow( $revStore, $row, $table, $idCol, $prefix ) {
$db = $this->getDB( DB_MASTER );
+
+ // Create a revision and use it to get the sha1 from the content table, if possible.
try {
$rev = ( $table === 'archive' )
- ? Revision::newFromArchiveRow( $row )
- : new Revision( $row );
- $text = $rev->getSerializedData();
+ ? $revStore->newRevisionFromArchiveRow( $row )
+ : $revStore->newRevisionFromRow( $row );
+ $sha1 = $rev->getSha1();
} catch ( Exception $e ) {
$this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" );
-
- return false; // T24624?
+ return false; // T24624? T22757?
}
- if ( !is_string( $text ) ) {
- # This should not happen, but sometimes does (T22757)
- $this->output( "Data of revision with {$idCol}={$row->$idCol} unavailable!\n" );
- return false;
- } else {
- $db->update( $table,
- [ "{$prefix}_sha1" => Revision::base36Sha1( $text ) ],
- [ $idCol => $row->$idCol ],
- __METHOD__
- );
+ $db->update( $table,
+ [ "{$prefix}_sha1" => $sha1 ],
+ [ $idCol => $row->$idCol ],
+ __METHOD__
+ );
- return true;
- }
+ return true;
}
/**
+ * @param MediaWiki\Revision\RevisionStore $revStore
* @param stdClass $row
* @return bool
*/
- protected function upgradeLegacyArchiveRow( $row ) {
+ protected function upgradeLegacyArchiveRow( $revStore, $row ) {
$db = $this->getDB( DB_MASTER );
+
+ // Create a revision and use it to get the sha1 from the content table, if possible.
try {
- $rev = Revision::newFromArchiveRow( $row );
+ $rev = $revStore->newRevisionFromArchiveRow( $row );
+ $sha1 = $rev->getSha1();
} catch ( Exception $e ) {
$this->output( "Text of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
-
- return false; // T24624?
+ return false; // T24624? T22757?
}
- $text = $rev->getSerializedData();
- if ( !is_string( $text ) ) {
- # This should not happen, but sometimes does (T22757)
- $this->output( "Data of revision with timestamp {$row->ar_timestamp} unavailable!\n" );
- return false;
- } else {
- # Archive table as no PK, but (NS,title,time) should be near unique.
- # Any duplicates on those should also have duplicated text anyway.
- $db->update( 'archive',
- [ 'ar_sha1' => Revision::base36Sha1( $text ) ],
- [
- 'ar_namespace' => $row->ar_namespace,
- 'ar_title' => $row->ar_title,
- 'ar_timestamp' => $row->ar_timestamp,
- 'ar_len' => $row->ar_len // extra sanity
- ],
- __METHOD__
- );
+ # Archive table has no PK, but (NS,title,time) should be near unique.
+ # Any duplicates on those should also have duplicated text anyway.
+ $db->update( 'archive',
+ [ 'ar_sha1' => $sha1 ],
+ [
+ 'ar_namespace' => $row->ar_namespace,
+ 'ar_title' => $row->ar_title,
+ 'ar_timestamp' => $row->ar_timestamp,
+ 'ar_len' => $row->ar_len // extra sanity
+ ],
+ __METHOD__
+ );
- return true;
- }
+ return true;
}
}
color: @colorGray5;
}
- &.oo-ui-labelElement .oo-ui-labelElement-label {
+ &.oo-ui-labelElement:not( .oo-ui-tagItemWidget-fixed ) .oo-ui-labelElement-label {
cursor: pointer;
}
* @return {string} localStorage item key
*/
getStoreKey: function () {
- return 'MediaWikiModuleStore:' + mw.config.get( 'wgDBname' );
+ return $VARS.storeKey;
},
/**
* @return {string} String of concatenated vary conditions.
*/
getVary: function () {
- return mw.config.get( 'skin' ) + ':' +
- mw.config.get( 'wgResourceLoaderStorageVersion' ) + ':' +
- mw.config.get( 'wgUserLanguage' );
+ return $VARS.storeVary;
},
/**
return [
'empty' => [
'exemptStyleModules' => [],
- '<meta name="ResourceLoaderDynamicStyles" content=""/>',
+ '',
],
'empty sets' => [
'exemptStyleModules' => [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ],
- '<meta name="ResourceLoaderDynamicStyles" content=""/>',
+ '',
],
'default logged-out' => [
'exemptStyleModules' => [ 'site' => [ 'site.styles' ] ],
$this->assertFalse( $record->isInherited() );
$this->assertSame( 'A', $record->getContent()->getText() );
$this->assertSame( 1, $record->getSize() );
- $this->assertNotNull( $record->getSha1() );
+ $this->assertNotEmpty( $record->getSha1() );
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
$this->assertSame( 2, $record->getRevision() );
$this->assertSame( 2, $record->getRevision() );
$this->assertFalse( $record->hasOrigin() );
$this->assertSame( 'A', $record->getContent()->getText() );
$this->assertSame( 1, $record->getSize() );
- $this->assertNotNull( $record->getSha1() );
+ $this->assertNotEmpty( $record->getSha1() );
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
$this->assertSame( 'myRole', $record->getRole() );
}
$this->assertSame( $hash, $record->getSha1() );
}
+ public function testHashComputed() {
+ $row = $this->makeRow();
+ $row->content_sha1 = '';
+
+ $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+ $this->assertNotEmpty( $rec->getSha1() );
+ }
+
public function testNewWithSuppressedContent() {
$input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
$output = SlotRecord::newWithSuppressedContent( $input );