summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE-NOTES-1.382
-rw-r--r--docs/extension.schema.v1.json35
-rw-r--r--docs/extension.schema.v2.json35
-rw-r--r--includes/Linker.php17
-rw-r--r--includes/OutputPage.php12
-rw-r--r--includes/Settings/Cache/CacheableSource.php14
-rw-r--r--includes/Settings/Cache/CachedSource.php82
-rw-r--r--includes/Settings/Source/FileSource.php11
-rw-r--r--includes/actions/HistoryAction.php4
-rw-r--r--includes/actions/pagers/HistoryPager.php12
-rw-r--r--includes/export/XmlDumpWriter.php4
-rw-r--r--includes/libs/MapCacheLRU.php14
-rw-r--r--includes/libs/rdbms/database/DatabasePostgres.php4
-rw-r--r--includes/libs/rdbms/loadbalancer/LoadBalancer.php22
-rw-r--r--includes/session/SessionManager.php9
-rw-r--r--includes/skins/Skin.php1
-rw-r--r--languages/i18n/en.json2
-rw-r--r--languages/i18n/qqq.json2
-rw-r--r--languages/messages/MessagesSq.php109
-rw-r--r--maintenance/deleteAutoPatrolLogs.php4
-rw-r--r--maintenance/generateSchemaChangeSql.php8
-rw-r--r--maintenance/generateSchemaSql.php8
-rw-r--r--maintenance/resetPageRandom.php4
-rwxr-xr-xmaintenance/update.php2
-rw-r--r--tests/phpunit/MediaWikiIntegrationTestCase.php4
-rw-r--r--tests/phpunit/includes/OutputPageTest.php36
-rw-r--r--tests/phpunit/includes/Revision/ContributionsLookupTest.php21
-rw-r--r--tests/phpunit/includes/Storage/NameTableStoreTest.php2
-rw-r--r--tests/phpunit/includes/Storage/SqlBlobStoreTest.php2
-rw-r--r--tests/phpunit/includes/api/ApiBaseTest.php3
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php3
-rw-r--r--tests/phpunit/includes/parser/ParserCacheSerializationTestCases.php4
-rw-r--r--tests/phpunit/includes/parser/ParserOutputTest.php8
-rw-r--r--tests/phpunit/includes/search/PrefixSearchTest.php14
-rw-r--r--tests/phpunit/includes/user/UserTest.php3
-rw-r--r--tests/phpunit/integration/includes/export/WikiExporterFactoryTest.php9
-rw-r--r--tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php3
-rw-r--r--tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php2
-rw-r--r--tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php2
-rw-r--r--tests/phpunit/unit/includes/Settings/Cache/CachedSourceTest.php47
-rw-r--r--tests/phpunit/unit/includes/Settings/Source/FileSourceTest.php5
-rw-r--r--tests/phpunit/unit/includes/libs/MapCacheLRUTest.php24
42 files changed, 401 insertions, 208 deletions
diff --git a/RELEASE-NOTES-1.38 b/RELEASE-NOTES-1.38
index 714da17f46c6..86d287eb6c23 100644
--- a/RELEASE-NOTES-1.38
+++ b/RELEASE-NOTES-1.38
@@ -342,6 +342,8 @@ because of Phabricator reports.
- ::pendingMasterChangeCallers() since 1.37
- ::forEachOpenMasterConnection() since 1.37
- ::waitForMasterPos() since 1.37
+* LoadBalancer::safeGetLag() which has been soft-deprecated since 1.34 has been
+ removed.
* Special:ListFiles and Special:NewFiles no longer allows to search for parts
of file names (option was not available for $wgMiserMode = true)
* …
diff --git a/docs/extension.schema.v1.json b/docs/extension.schema.v1.json
index b654843870be..f2bfc571e611 100644
--- a/docs/extension.schema.v1.json
+++ b/docs/extension.schema.v1.json
@@ -1118,27 +1118,38 @@
},
{
"type": "object",
- "description": "An object factory specification for a class implementing Wikimedia\\Parsoid\\Ext\\ExtensionModule",
+ "description": "Object factory specification for a class implementing Wikimedia\\Parsoid\\Ext\\ExtensionModule",
"properties": {
"factory": {
"type": ["string", "array"],
"description": "A factory function to be called to create an ExtensionModule. This should be omitted if a class is specified."
- },
+ }
+ },
+ "required": [ "factory" ]
+ },
+ {
+ "type": "object",
+ "description": "Object factory specification for a class implementing Wikimedia\\Parsoid\\Ext\\ExtensionModule",
+ "properties": {
"class": {
"type": "string",
"description": "The fully-qualified class name of a class implemeting Wikimedia\\Parsoid\\Ext\\ExtensionModule. This should be omitted if a factory is specified."
- },
- "args": {
- "type": "array",
- "description": "The arguments passed to the ExtensionModule constructor or factory"
}
},
- "additionalProperties": false
+ "required": [ "class" ]
},
{
"type": "object",
"description": "A Parsoid extension module configuration array",
"properties": {
+ "class": {
+ "type": "null",
+ "description": "This property must not be present in a configuration array"
+ },
+ "factory": {
+ "type": "null",
+ "description": "This property must not be present in a configuration array"
+ },
"domProcessors": {
"type": "array",
"items": {
@@ -1152,6 +1163,13 @@
"description": "Additional ResourceLoader styles to include"
}
},
+ "annotations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "description": "The name of an annotation tag"
+ }
+ },
"tags": {
"type": "array",
"items": {
@@ -1173,8 +1191,7 @@
"required": ["name"]
}
}
- },
- "additionalProperties": false
+ }
}
]
}
diff --git a/docs/extension.schema.v2.json b/docs/extension.schema.v2.json
index 95ca01ded6d5..e2438d3d954d 100644
--- a/docs/extension.schema.v2.json
+++ b/docs/extension.schema.v2.json
@@ -1182,27 +1182,38 @@
},
{
"type": "object",
- "description": "An object factory specification for a class implementing Wikimedia\\Parsoid\\Ext\\ExtensionModule",
+ "description": "Object factory specification for a class implementing Wikimedia\\Parsoid\\Ext\\ExtensionModule",
"properties": {
"factory": {
"type": ["string", "array"],
"description": "A factory function to be called to create an ExtensionModule. This should be omitted if a class is specified."
- },
+ }
+ },
+ "required": [ "factory" ]
+ },
+ {
+ "type": "object",
+ "description": "Object factory specification for a class implementing Wikimedia\\Parsoid\\Ext\\ExtensionModule",
+ "properties": {
"class": {
"type": "string",
"description": "The fully-qualified class name of a class implemeting Wikimedia\\Parsoid\\Ext\\ExtensionModule. This should be omitted if a factory is specified."
- },
- "args": {
- "type": "array",
- "description": "The arguments passed to the ExtensionModule constructor or factory"
}
},
- "additionalProperties": false
+ "required": [ "class" ]
},
{
"type": "object",
"description": "A Parsoid extension module configuration array",
"properties": {
+ "class": {
+ "type": "null",
+ "description": "This property must not be present in a configuration array"
+ },
+ "factory": {
+ "type": "null",
+ "description": "This property must not be present in a configuration array"
+ },
"domProcessors": {
"type": "array",
"items": {
@@ -1216,6 +1227,13 @@
"description": "Additional ResourceLoader styles to include"
}
},
+ "annotations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "description": "The name of an annotation tag"
+ }
+ },
"tags": {
"type": "array",
"items": {
@@ -1237,8 +1255,7 @@
"required": ["name"]
}
}
- },
- "additionalProperties": false
+ }
}
]
}
diff --git a/includes/Linker.php b/includes/Linker.php
index 44ad1a66a79a..cc4019e2a1fe 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -1983,14 +1983,17 @@ class Linker {
$editCount = self::getRollbackEditCount( $revRecord, false );
}
- if ( $editCount > $showRollbackEditCount ) {
- $html = $context->msg( 'rollbacklinkcount-morethan' )
- ->numParams( $showRollbackEditCount )->parse();
- } else {
- $html = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
- }
+ // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
+ if ( $editCount > 0 ) {
+ if ( $editCount > $showRollbackEditCount ) {
+ $html = $context->msg( 'rollbacklinkcount-morethan' )
+ ->numParams( $showRollbackEditCount )->parse();
+ } else {
+ $html = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
+ }
- return self::link( $title, $html, $attrs, $query, $options );
+ return self::link( $title, $html, $attrs, $query, $options );
+ }
}
$html = $context->msg( 'rollbacklink' )->escaped();
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 63653aaff770..321c70e7b1cd 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -20,6 +20,7 @@
* @file
*/
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
@@ -27,6 +28,7 @@ use MediaWiki\Page\PageRecord;
use MediaWiki\Page\PageReference;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Session\SessionManager;
+use MediaWiki\Tidy\RemexDriver;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\RelPath;
use Wikimedia\WrappedString;
@@ -989,7 +991,15 @@ class OutputPage extends ContextSource {
# change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
# but leave "<i>foobar</i>" alone
- $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
+ # == start explicit tidy ==
+ # T299722/T298401: The explicit tidy here can be removed once
+ # Sanitizer::removeHTMLtags() is made more robust.
+ $tidy = new RemexDriver( new ServiceOptions( [ 'TidyConfig' ], [
+ 'TidyConfig' => [ 'pwrap' => false ],
+ ] ) );
+ $nameWithTags = $tidy->tidy( $name, [ Sanitizer::class, 'armorFrenchSpaces' ] );
+ # == end explicit tidy ==
+ $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $nameWithTags ) );
$this->mPageTitle = $nameWithTags;
# change "<i>foo&amp;bar</i>" to "foo&bar"
diff --git a/includes/Settings/Cache/CacheableSource.php b/includes/Settings/Cache/CacheableSource.php
index c6d6e293f019..408667d23b1e 100644
--- a/includes/Settings/Cache/CacheableSource.php
+++ b/includes/Settings/Cache/CacheableSource.php
@@ -13,6 +13,20 @@ use MediaWiki\Settings\Source\SettingsSource;
*/
interface CacheableSource extends SettingsSource {
/**
+ * Allow the caching layer to attempt to return stale results in the event
+ * that loading from the original source fails.
+ *
+ * Note that allowing stale results will result in cache items being
+ * stored indefinitely regardless of the {@link getExpiryTtl()} value, and
+ * since there is currently no pruning of cache items, it is advised that
+ * sources allowing stale results also implement an immutable
+ * `getHashKey()` based only on constructor arguments.
+ *
+ * @return bool
+ */
+ public function allowsStaleLoad(): bool;
+
+ /**
* Returns the cache TTL (in seconds) for this source.
*
* @return int
diff --git a/includes/Settings/Cache/CachedSource.php b/includes/Settings/Cache/CachedSource.php
index 7e1cf55f29d1..1430f9e94f86 100644
--- a/includes/Settings/Cache/CachedSource.php
+++ b/includes/Settings/Cache/CachedSource.php
@@ -3,6 +3,7 @@
namespace MediaWiki\Settings\Cache;
use BagOStuff;
+use MediaWiki\Settings\SettingsBuilderException;
use MediaWiki\Settings\Source\SettingsIncludeLocator;
use MediaWiki\Settings\Source\SettingsSource;
@@ -38,6 +39,10 @@ class CachedSource implements SettingsSource, SettingsIncludeLocator {
* Queries cache for source contents and performs loading/caching of the
* source contents on miss.
*
+ * If the load fails but the source implements {@link
+ * CacheableSource::allowsStaleLoad()} as <code>true</code>, stale results
+ * may be returned if still present in the cache store.
+ *
* @return array
*/
public function load(): array {
@@ -46,19 +51,25 @@ class CachedSource implements SettingsSource, SettingsIncludeLocator {
$this->source->getHashKey()
);
$item = $this->cache->get( $key );
-
- $miss =
- $item === false ||
- $this->expiresEarly( $item, $this->source->getExpiryWeight() );
-
- if ( $miss ) {
- $item = $this->loadWithMetadata();
- $this->cache->set( $key, $item, $this->source->getExpiryTtl() );
- }
-
- // This shouldn't be possible but let's make phan happy
- if ( !is_array( $item ) || !array_key_exists( 'value', $item ) ) {
- return [];
+ $miss = $this->isMiss( $item );
+
+ if ( $miss || $this->isExpired( $item ) ) {
+ $allowsStale = $this->source->allowsStaleLoad();
+
+ try {
+ $item = $this->loadWithMetadata();
+ $this->cache->set(
+ $key,
+ $item,
+ $allowsStale ? BagOStuff::TTL_INDEFINITE : $this->source->getExpiryTtl()
+ );
+ } catch ( SettingsBuilderException $e ) {
+ if ( $miss || !$allowsStale ) {
+ throw $e;
+ }
+ // Return the stale results since the source allows it and
+ // this was only a soft miss
+ }
}
return $item['value'];
@@ -74,10 +85,50 @@ class CachedSource implements SettingsSource, SettingsIncludeLocator {
}
/**
+ * Whether the given cache item is considered a real miss, in other words:
+ * - it is a falsey value
+ * - it has the wrong type or structure for this cache implementation
+ *
+ * @param mixed $item Cache item.
+ *
+ * @return bool
+ */
+ private function isMiss( $item ): bool {
+ return !$item ||
+ !is_array( $item ) ||
+ !isset( $item['expiry'] ) ||
+ !isset( $item['generation'] ) ||
+ !isset( $item['value'] );
+ }
+
+ /**
+ * Whether the given cache item is considered expired, in other words:
+ * - its expiry timestamp has passed
+ * - it is deemed to expire early so as to mitigate cache stampedes
+ *
+ * @param array $item Cache item.
+ *
+ * @return bool
+ */
+ private function isExpired( $item ): bool {
+ return $item['expiry'] < microtime( true ) ||
+ $this->expiresEarly( $item, $this->source->getExpiryWeight() );
+ }
+
+ /**
* Decide whether the cached source should be expired early according to a
* probabilistic calculation that becomes more likely as the normal expiry
* approaches.
*
+ * In other words, we're going to pretend we're a bit further into the
+ * future than we are so that we might expire and regenerate the cached
+ * settings before other threads attempt to the do the same. The number of
+ * threads that will pretend to be far into the future (and thus will
+ * concurrently reload/cache the settings) will most probably be so
+ * exponentially fewer than the number of threads pretending to be near
+ * into the future that it will approach optimal stampede protection
+ * without the use of an exclusive lock.
+ *
* @param array $item Cached source with expiry metadata.
* @param float $weight Coefficient used to increase/decrease the
* likelihood of early expiration.
@@ -87,10 +138,13 @@ class CachedSource implements SettingsSource, SettingsIncludeLocator {
* @return bool
*/
private function expiresEarly( array $item, float $weight ): bool {
- if ( !isset( $item['expiry'] ) || !isset( $item['generation'] ) ) {
+ if ( $weight == 0 || !isset( $item['expiry'] ) || !isset( $item['generation'] ) ) {
return false;
}
+ // Calculate a negative expiry offset using generation time, expiry
+ // weight, and a random number within the exponentially distributed
+ // range of log n where n: (0, 1] (which is always negative)
$expiryOffset =
$item['generation'] *
$weight *
diff --git a/includes/Settings/Source/FileSource.php b/includes/Settings/Source/FileSource.php
index 7e05dbc59037..22af9c202b13 100644
--- a/includes/Settings/Source/FileSource.php
+++ b/includes/Settings/Source/FileSource.php
@@ -90,6 +90,17 @@ class FileSource implements CacheableSource, SettingsIncludeLocator {
}
/**
+ * Disallow stale results from file sources in the case of load failure as
+ * failing to read from disk would be quite catastrophic and worthy of
+ * propagation.
+ *
+ * @return bool
+ */
+ public function allowsStaleLoad(): bool {
+ return false;
+ }
+
+ /**
* Loads contents from the file and decodes them using the first format
* to claim support for the file's extension.
*
diff --git a/includes/actions/HistoryAction.php b/includes/actions/HistoryAction.php
index 1078f13a518b..3e85516c6acb 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -89,7 +89,7 @@ class HistoryAction extends FormlessAction {
// Precache various messages
if ( !isset( $this->message ) ) {
$this->message = [];
- $msgs = [ 'cur', 'last', 'pipe-separator' ];
+ $msgs = [ 'cur', 'tooltip-cur', 'last', 'tooltip-last', 'pipe-separator' ];
foreach ( $msgs as $msg ) {
$this->message[$msg] = $this->msg( $msg )->escaped();
}
@@ -203,7 +203,7 @@ class HistoryAction extends FormlessAction {
// Fail nicely if article doesn't exist.
if ( !$this->getWikiPage()->exists() ) {
- $send404Code = MediaWikiServices::getInstance()->getMainConfig()->get( 'Send404Code' );
+ $send404Code = $config->get( 'Send404Code' );
if ( $send404Code ) {
$out->setStatusCode( 404 );
}
diff --git a/includes/actions/pagers/HistoryPager.php b/includes/actions/pagers/HistoryPager.php
index f3cbb096493d..06bb2982f294 100644
--- a/includes/actions/pagers/HistoryPager.php
+++ b/includes/actions/pagers/HistoryPager.php
@@ -594,7 +594,9 @@ class HistoryPager extends ReverseChronologicalPager {
return $this->getLinkRenderer()->makeKnownLink(
$this->getTitle(),
new HtmlArmor( $cur ),
- [],
+ [
+ 'title' => $this->historyPage->message['tooltip-cur']
+ ],
[
'diff' => $latest,
'oldid' => $rev->getId()
@@ -626,7 +628,9 @@ class HistoryPager extends ReverseChronologicalPager {
return $linkRenderer->makeKnownLink(
$this->getTitle(),
new HtmlArmor( $last ),
- [],
+ [
+ 'title' => $this->historyPage->message['tooltip-last']
+ ],
[
'diff' => $prevRev->getId(),
'oldid' => 'prev'
@@ -649,7 +653,9 @@ class HistoryPager extends ReverseChronologicalPager {
return $linkRenderer->makeKnownLink(
$this->getTitle(),
new HtmlArmor( $last ),
- [],
+ [
+ 'title' => $this->historyPage->message['tooltip-last']
+ ],
[
'diff' => $prevRev->getId(),
'oldid' => $next->rev_id
diff --git a/includes/export/XmlDumpWriter.php b/includes/export/XmlDumpWriter.php
index 8f70f045da31..01a649401309 100644
--- a/includes/export/XmlDumpWriter.php
+++ b/includes/export/XmlDumpWriter.php
@@ -92,13 +92,13 @@ class XmlDumpWriter {
$schemaVersion = XML_DUMP_SCHEMA_VERSION_11
) {
Assert::parameter(
- in_array( $contentMode, [ self::WRITE_CONTENT, self::WRITE_STUB ] ),
+ in_array( $contentMode, [ self::WRITE_CONTENT, self::WRITE_STUB ], true ),
'$contentMode',
'must be one of the following constants: WRITE_CONTENT or WRITE_STUB.'
);
Assert::parameter(
- in_array( $schemaVersion, self::$supportedSchemas ),
+ in_array( $schemaVersion, self::$supportedSchemas, true ),
'$schemaVersion',
'must be one of the following schema versions: '
. implode( ',', self::$supportedSchemas )
diff --git a/includes/libs/MapCacheLRU.php b/includes/libs/MapCacheLRU.php
index f65e9c2b899c..4325412766a4 100644
--- a/includes/libs/MapCacheLRU.php
+++ b/includes/libs/MapCacheLRU.php
@@ -140,7 +140,10 @@ class MapCacheLRU implements ExpirationAwareness, Serializable {
* @since 1.32 Added $maxAge
*/
public function has( $key, $maxAge = INF ) {
- // Optimization: Forego type check because array_key_exists does it already (T275673)
+ if ( !is_int( $key ) && !is_string( $key ) ) {
+ throw new UnexpectedValueException(
+ __METHOD__ . ': invalid key; must be string or integer.' );
+ }
return array_key_exists( $key, $this->cache )
&& (
// Optimization: Avoid expensive getAge/getCurrentTime for common case (T275673)
@@ -195,8 +198,8 @@ class MapCacheLRU implements ExpirationAwareness, Serializable {
}
if ( !is_string( $field ) && !is_int( $field ) ) {
- trigger_error( "Field keys must be string or integer (key '$key')", E_USER_WARNING );
- return;
+ throw new UnexpectedValueException(
+ __METHOD__ . ": invalid field for '$key'; must be string or integer." );
}
$this->cache[$key][$field] = $value;
@@ -213,8 +216,11 @@ class MapCacheLRU implements ExpirationAwareness, Serializable {
public function hasField( $key, $field, $maxAge = INF ) {
$value = $this->get( $key );
+ if ( !is_int( $field ) && !is_string( $field ) ) {
+ throw new UnexpectedValueException(
+ __METHOD__ . ": invalid field for '$key'; must be string or integer." );
+ }
return is_array( $value )
- // Optimization: Forego $field type check because array_key_exists does it already (T275673)
&& array_key_exists( $field, $value )
&& (
$maxAge === INF
diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php
index a107ef08159d..4e18a288f223 100644
--- a/includes/libs/rdbms/database/DatabasePostgres.php
+++ b/includes/libs/rdbms/database/DatabasePostgres.php
@@ -1213,9 +1213,9 @@ SQL;
}
public function buildGroupConcatField(
- $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
+ $delim, $table, $field, $conds = '', $join_conds = []
) {
- $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
+ $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delim ) . ')';
return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
}
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php
index 3493dde4be1d..99366de5289a 100644
--- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php
+++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php
@@ -83,8 +83,6 @@ class LoadBalancer implements ILoadBalancer {
private $servers;
/** @var array[] Map of (group => server index => weight) */
private $groupLoads;
- /** @var int[] Map of (server index => seconds of lag considered "high") */
- private $maxLagByIndex;
/** @var int Seconds to spend waiting on replica DB lag to resolve */
private $waitTimeout;
/** @var array The LoadMonitor configuration */
@@ -210,7 +208,6 @@ class LoadBalancer implements ILoadBalancer {
$this->groupLoads[$group][$i] = $ratio;
}
$this->groupLoads[self::GROUP_GENERIC][$i] = $server['load'];
- $this->maxLagByIndex[$i] = $server['max lag'] ?? $this->maxLag;
}
$this->waitTimeout = $params['waitTimeout'] ?? self::MAX_WAIT_DEFAULT;
@@ -2315,25 +2312,6 @@ class LoadBalancer implements ILoadBalancer {
return $this->getLoadMonitor()->getLagTimes( $indexesWithLag, $domain ) + $knownLagTimes;
}
- /**
- * Get the lag in seconds for a given connection, or zero if this load
- * balancer does not have replication enabled.
- *
- * This should be used in preference to Database::getLag() in cases where
- * replication may not be in use, since there is no way to determine if
- * replication is in use at the connection level without running
- * potentially restricted queries such as SHOW SLAVE STATUS. Using this
- * function instead of Database::getLag() avoids a fatal error in this
- * case on many installations.
- *
- * @param IDatabase $conn
- * @return int|bool Returns false on error
- * @deprecated Since 1.34 Use IDatabase::getLag() instead
- */
- public function safeGetLag( IDatabase $conn ) {
- return $conn->getLag();
- }
-
public function waitForPrimaryPos( IDatabase $conn, $pos = false, $timeout = null ) {
$timeout = max( 1, $timeout ?: $this->waitTimeout );
diff --git a/includes/session/SessionManager.php b/includes/session/SessionManager.php
index 324d0f97e1e8..a2506e6a97b2 100644
--- a/includes/session/SessionManager.php
+++ b/includes/session/SessionManager.php
@@ -105,6 +105,9 @@ class SessionManager implements SessionManagerInterface {
/** @var UserNameUtils */
private $userNameUtils;
+ /** @var ObjectFactory */
+ private $objectFactory;
+
/** @var CachedBagOStuff|null */
private $store;
@@ -226,7 +229,9 @@ class SessionManager implements SessionManagerInterface {
$this->logger->debug( 'SessionManager using store ' . get_class( $store ) );
$this->store = $store instanceof CachedBagOStuff ? $store : new CachedBagOStuff( $store );
- $this->userNameUtils = MediawikiServices::getInstance()->getUserNameUtils();
+ $services = MediawikiServices::getInstance();
+ $this->userNameUtils = $services->getUserNameUtils();
+ $this->objectFactory = $services->getObjectFactory();
register_shutdown_function( [ $this, 'shutdown' ] );
}
@@ -462,7 +467,7 @@ class SessionManager implements SessionManagerInterface {
$this->sessionProviders = [];
foreach ( $this->config->get( 'SessionProviders' ) as $spec ) {
/** @var SessionProvider */
- $provider = ObjectFactory::getObjectFromSpec( $spec );
+ $provider = $this->objectFactory->createObject( $spec );
$provider->init(
$this->logger,
$this->config,
diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php
index 6c40766c34f0..b70c2cf83a4f 100644
--- a/includes/skins/Skin.php
+++ b/includes/skins/Skin.php
@@ -2637,6 +2637,7 @@ abstract class Skin extends ContextSource {
* preference 'search-special-page' instead.
*/
public function setSearchPageTitle( Title $title ) {
+ wfDeprecated( __METHOD__, '1.38 Use SpecialPage::newSearchPage' );
$userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
$user = $this->getUser();
$currentTitle = $userOptionsManager->getOption( $user, 'search-special-page' );
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index 506e9a3cc7fc..9e28d3127d81 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -848,8 +848,10 @@
"nextrevision": "Newer revision →",
"currentrevisionlink": "Latest revision",
"cur": "cur",
+ "tooltip-cur": "Difference with latest revision",
"next": "next",
"last": "prev",
+ "tooltip-last": "Difference with preceding revision",
"page_first": "first",
"page_last": "last",
"histlegend": "Diff selection: Mark the radio buttons of the revisions to compare and hit enter or the button at the bottom.<br />\nLegend: <strong>({{int:cur}})</strong> = difference with latest revision, <strong>({{int:last}})</strong> = difference with preceding revision, <strong>{{int:minoreditletter}}</strong> = minor edit.",
diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json
index 7802e85b5abc..c26cd07fc766 100644
--- a/languages/i18n/qqq.json
+++ b/languages/i18n/qqq.json
@@ -1080,8 +1080,10 @@
"nextrevision": "See also:\n* {{msg-mw|Previousrevision}}",
"currentrevisionlink": "{{Identical|Current revision}}",
"cur": "Link in page history ([https://translatewiki.net/w/i.php?title=MediaWiki:Cur/qqq&action=history example]).\n\nThis is short for \"current\".",
+ "tooltip-cur": "Tooltip for {{msg-mw|cur}} link in page history. Should match entry in {{msg-mw|histlegend}}",
"next": "Link in page history\n\n{{Identical|Next}}",
"last": "Link in page history",
+ "tooltip-last": "Tooltip for {{msg-mw|last}} link in page history. Should match entry in {{msg-mw|histlegend}}",
"page_first": "This is part of the navigation message on the top and bottom of Special pages which are lists of things in alphabetical order, e.g. the '[[Special:Categories|Categories]]' special page. It is followed by the message {{msg-mw|Viewprevnext}}.\n{{Identical|First}}",
"page_last": "This is part of the navigation message on the top and bottom of Special pages which are lists of things in alphabetical order, e.g. the '[[Special:Categories|Categories]]' special page. It is followed by the message {{msg-mw|Viewprevnext}}.\n\n{{Identical|Last}}",
"histlegend": "Text at the top of the history page, explaining how to use it.\n\nOne or two [[:w:en:Radio button|radio buttons]] appear in each row that represents a revision of a wiki page.\n\n\"Legend\" means \"explanation of symbols\".\n\nSee also:\n* {{msg-mw|Cur}}\n* {{msg-mw|Last}}\n* {{msg-mw|Minoreditletter}}\n\nScreenshot:\n[[File:Screenshot of Revision history of Douglas Adams (Q42) - Wikidata.png|thumb|Screenshot of the revision history feature]]",
diff --git a/languages/messages/MessagesSq.php b/languages/messages/MessagesSq.php
index 2562b1485c63..7384433b72b5 100644
--- a/languages/messages/MessagesSq.php
+++ b/languages/messages/MessagesSq.php
@@ -29,6 +29,7 @@
* @author Urhixidur
* @author Vinie007
* @author לערי ריינהארט
+ * @author Klein Muçi
*/
$namespaceNames = [
@@ -55,6 +56,8 @@ $namespaceAliases = [
'Perdoruesi_diskutim' => NS_USER_TALK,
'Figura' => NS_FILE,
'Figura_diskutim' => NS_FILE_TALK,
+ 'Ndihme' => NS_HELP,
+ 'Ndihme_diskutim' => NS_HELP_TALK,
'Kategori' => NS_CATEGORY,
'Kategori_Diskutim' => NS_CATEGORY_TALK,
];
@@ -67,64 +70,64 @@ $namespaceGenderAliases = [
/** @phpcs-require-sorted-array */
$specialPageAliases = [
'Activeusers' => [ 'PërdoruesitAktivë' ],
- 'Allmessages' => [ 'TëgjithaMesazhet' ],
- 'Allpages' => [ 'TëgjithaFaqet' ],
- 'Ancientpages' => [ 'FaqetAntike' ],
+ 'Allmessages' => [ 'Mesazhet', 'TëgjithaMesazhet' ],
+ 'Allpages' => [ 'Faqet', 'TëgjithaFaqet' ],
+ 'Ancientpages' => [ 'FaqeTëVjetra', 'FaqetAntike' ],
'Blankpage' => [ 'FaqeBosh' ],
- 'Block' => [ 'BllokoIP' ],
- 'Booksources' => [ 'BurimeteLibrave' ],
- 'Categories' => [ 'Kategori' ],
+ 'Block' => [ 'Blloko', 'BllokoIP' ],
+ 'Booksources' => [ 'BurimetELibrave', 'BurimeteLibrave' ],
+ 'Categories' => [ 'Kategoritë', 'Kategori' ],
'ChangeEmail' => [ 'NdryshoEmail' ],
'ChangePassword' => [ 'NdryshoFjalëkalimin' ],
'ComparePages' => [ 'KrahasoFaqet' ],
'Confirmemail' => [ 'KonfirmoEmail' ],
'Contributions' => [ 'Kontributet' ],
'CreateAccount' => [ 'HapLlogari' ],
- 'DeletedContributions' => [ 'GrisKontributet' ],
- 'Emailuser' => [ 'EmailPërdoruesit' ],
+ 'DeletedContributions' => [ 'KontributeTëGrisura', 'GrisKontributet' ],
+ 'Emailuser' => [ 'DërgoEmail', 'EmailPërdoruesit' ],
'Export' => [ 'Eksporto' ],
'Import' => [ 'Importo' ],
- 'Listadmins' => [ 'RreshtoAdmin' ],
- 'Listbots' => [ 'RreshtoBotët' ],
- 'Listfiles' => [ 'ListaSkedave' ],
- 'Listusers' => [ 'RreshtoPërdoruesit' ],
+ 'Listadmins' => [ 'RadhitAdminët', 'RreshtoAdmin' ],
+ 'Listbots' => [ 'RadhitRobotët', 'RreshtoBotët' ],
+ 'Listfiles' => [ 'RadhitSkedat', 'ListaSkedave' ],
+ 'Listusers' => [ 'RadhitPërdoruesit', 'RreshtoPërdoruesit' ],
'Lockdb' => [ 'MbyllDB' ],
- 'Longpages' => [ 'FaqeteGjata' ],
- 'Movepage' => [ 'LëvizFaqe' ],
- 'Mycontributions' => [ 'KontributetëMiat' ],
- 'Mypage' => [ 'FaqjaIme' ],
- 'Mytalk' => [ 'DiskutimiImë' ],
- 'Myuploads' => [ 'NgarkimeteMia' ],
+ 'Longpages' => [ 'FaqeTëGjata', 'FaqeteGjata' ],
+ 'Movepage' => [ 'LëvizFaqen', 'LëvizFaqe' ],
+ 'Mycontributions' => [ 'KontributetVetjake', 'KontributetëMiat' ],
+ 'Mypage' => [ 'FaqjaVetjake', 'FaqjaIme' ],
+ 'Mytalk' => [ 'DiskutimetVetjake', 'DiskutimiImë' ],
+ 'Myuploads' => [ 'NgarkimetVetjake', 'NgarkimeteMia' ],
'Newimages' => [ 'SkedaTëReja' ],
- 'Newpages' => [ 'FaqeteReja' ],
- 'Preferences' => [ 'Preferencat' ],
- 'Protectedpages' => [ 'FaqeteMbrojtura' ],
- 'Protectedtitles' => [ 'TitujteMbrojtur' ],
- 'Randompage' => [ 'Rastësishme', 'FaqeRastësishme' ],
+ 'Newpages' => [ 'FaqeTëReja', 'FaqeteReja' ],
+ 'Preferences' => [ 'Parapëlqimet', 'Preferencat' ],
+ 'Protectedpages' => [ 'FaqeTëMbrojtura', 'FaqeteMbrojtura' ],
+ 'Protectedtitles' => [ 'TitujTëMbrojtur', 'TitujteMbrojtur' ],
+ 'Randompage' => [ 'ArtikullIRastësishëm', 'Rastësishme', 'FaqeRastësishme' ],
'Recentchanges' => [ 'NdryshimeSëFundmi' ],
- 'Search' => [ 'Kërkim' ],
- 'Shortpages' => [ 'FasheteShkurta' ],
+ 'Search' => [ 'Kërko', 'Kërkim' ],
+ 'Shortpages' => [ 'FaqeTëShkurtra', 'FasheteShkurta' ],
'Specialpages' => [ 'FaqetSpeciale' ],
- 'Statistics' => [ 'Statistika' ],
+ 'Statistics' => [ 'Statistikat', 'Statistika' ],
'Unblock' => [ 'Zhblloko' ],
- 'Uncategorizedcategories' => [ 'KategoriTëpakategorizuara' ],
- 'Uncategorizedimages' => [ 'SkedaTëpakategorizuara' ],
- 'Uncategorizedpages' => [ 'FaqeTëpakategorizuara' ],
- 'Uncategorizedtemplates' => [ 'StampaTëpakategorizuara' ],
+ 'Uncategorizedcategories' => [ 'KategoriTëPakategorizuara', 'KategoriTëpakategorizuara' ],
+ 'Uncategorizedimages' => [ 'SkedaTëPakategorizuara', 'SkedaTëpakategorizuara' ],
+ 'Uncategorizedpages' => [ 'FaqeTëPakategorizuara', 'FaqeTëpakategorizuara' ],
+ 'Uncategorizedtemplates' => [ 'StampaTëPakategorizuara', 'StampaTëpakategorizuara' ],
'Undelete' => [ 'Rikthe' ],
'Unlockdb' => [ 'HapDB' ],
- 'Unusedcategories' => [ 'KategoriTëpapërdorura' ],
- 'Unusedimages' => [ 'SkedaTëpapërdorura' ],
+ 'Unusedcategories' => [ 'KategoriTëPapërdorura', 'KategoriTëpapërdorura' ],
+ 'Unusedimages' => [ 'SkedaTëPapërdorura', 'SkedaTëpapërdorura' ],
'Upload' => [ 'Ngarko' ],
- 'Userlogin' => [ 'HyrjePërdoruesi' ],
- 'Userlogout' => [ 'DaljePërdoruesi' ],
- 'Version' => [ 'Verzioni' ],
- 'Wantedcategories' => [ 'KaetgoritëeDëshiruara' ],
- 'Wantedfiles' => [ 'SkedateDëshiruara' ],
- 'Wantedpages' => [ 'FaqeteDëshiruara' ],
- 'Wantedtemplates' => [ 'StampateDëshiruara' ],
+ 'Userlogin' => [ 'Hyrja', 'HyrjePërdoruesi' ],
+ 'Userlogout' => [ 'Dalja', 'DaljePërdoruesi' ],
+ 'Version' => [ 'Versioni', 'Verzioni' ],
+ 'Wantedcategories' => [ 'KategoriTëDëshiruara', 'KaetgoritëeDëshiruara' ],
+ 'Wantedfiles' => [ 'SkedaTëDëshiruara', 'SkedateDëshiruara' ],
+ 'Wantedpages' => [ 'FaqeTëDëshiruara', 'FaqeteDëshiruara' ],
+ 'Wantedtemplates' => [ 'StampaTëDëshiruara', 'StampateDëshiruara' ],
'Whatlinkshere' => [ 'LidhjetKëtu' ],
- 'Withoutinterwiki' => [ 'PaInterwiki' ],
+ 'Withoutinterwiki' => [ 'PaLidhje', 'PaInterwiki' ],
];
/** @phpcs-require-sorted-array */
@@ -135,11 +138,11 @@ $magicWords = [
'currentday2' => [ '1', 'DITASOT2', 'CURRENTDAY2' ],
'currentdayname' => [ '1', 'EMRIIDITËSOT', 'CURRENTDAYNAME' ],
'currenthour' => [ '1', 'ORATANI', 'CURRENTHOUR' ],
- 'currentmonth' => [ '1', 'MUAJIMOMENTAL', 'MUAJIMOMENTAL2', 'CURRENTMONTH', 'CURRENTMONTH2' ],
- 'currentmonth1' => [ '1', 'MUAJIMOMENTAL1', 'CURRENTMONTH1' ],
- 'currentmonthname' => [ '1', 'EMRIIMUAJITMOMENTAL', 'CURRENTMONTHNAME' ],
+ 'currentmonth' => [ '1', 'MUAJIAKTUAL', 'MUAJIAKTUAL2', 'MUAJIMOMENTAL', 'MUAJIMOMENTAL2', 'CURRENTMONTH', 'CURRENTMONTH2' ],
+ 'currentmonth1' => [ '1', 'MUAJIAKTUAL1', 'MUAJIMOMENTAL1', 'CURRENTMONTH1' ],
+ 'currentmonthname' => [ '1', 'EMRIIMUAJITAKTUAL', 'EMRIIMUAJITMOMENTAL', 'CURRENTMONTHNAME' ],
'currenttime' => [ '1', 'KOHATANI', 'CURRENTTIME' ],
- 'currentweek' => [ '1', 'JAVAMOMENTALE', 'CURRENTWEEK' ],
+ 'currentweek' => [ '1', 'JAVAAKTUALE', 'JAVAMOMENTALE', 'CURRENTWEEK' ],
'currentyear' => [ '1', 'SIVJET', 'CURRENTYEAR' ],
'fullpagename' => [ '1', 'EMRIIPLOTËIFAQES', 'FULLPAGENAME' ],
'fullpagenamee' => [ '1', 'EMRIIPLOTËIFAQESE', 'FULLPAGENAMEE' ],
@@ -153,17 +156,17 @@ $magicWords = [
'img_center' => [ '1', 'qendër', 'qendrore', 'center', 'centre' ],
'img_framed' => [ '1', 'i_kornizuar', 'pa_kornizë', 'kornizë', 'frame', 'framed', 'enframed' ],
'img_left' => [ '1', 'majtas', 'left' ],
- 'img_link' => [ '1', 'lidhje=$1', 'link=$1' ],
+ 'img_link' => [ '1', 'lidhja=$1', 'lidhje=$1', 'link=$1' ],
'img_manualthumb' => [ '1', 'parapamje=$1', 'pamje=$1', 'thumbnail=$1', 'thumb=$1' ],
'img_middle' => [ '1', 'mes', 'middle' ],
- 'img_none' => [ '1', 's\'ka', 'none' ],
+ 'img_none' => [ '1', 'asnjë', 's\'ka', 'none' ],
'img_page' => [ '1', 'faqja=$1', 'faqja $1', 'page=$1', 'page $1' ],
'img_right' => [ '1', 'djathtas', 'right' ],
'img_sub' => [ '1', 'nën', 'sub' ],
'img_text_bottom' => [ '1', 'tekst-fund', 'text-bottom' ],
- 'img_text_top' => [ '1', 'tekst-top', 'text-top' ],
+ 'img_text_top' => [ '1', 'tekst-majë', 'tekst-top', 'text-top' ],
'img_thumbnail' => [ '1', 'parapamje', 'pamje', 'thumb', 'thumbnail' ],
- 'img_upright' => [ '1', 'lartdjathtas', 'lartdjathtas=$1', 'lartdjathtas $1', 'upright', 'upright=$1', 'upright $1' ],
+ 'img_upright' => [ '1', 'vertikale', 'vertikale=$1', 'vertikale $1', 'lartdjathtas', 'lartdjathtas=$1', 'lartdjathtas $1', 'upright', 'upright=$1', 'upright $1' ],
'language' => [ '0', '#GJUHA:', '#LANGUAGE:' ],
'localday' => [ '1', 'DITALOKALE', 'LOCALDAY' ],
'localday2' => [ '1', 'DITALOKALE2', 'LOCALDAY2' ],
@@ -175,11 +178,11 @@ $magicWords = [
'localurl' => [ '0', 'URLLOKALE', 'LOCALURL:' ],
'localyear' => [ '1', 'VITILOKAL', 'LOCALYEAR' ],
'namespace' => [ '1', 'HAPËSIRA', 'NAMESPACE' ],
- 'noeditsection' => [ '0', '__JOREDAKTIMSEKSIONI__', '__NOEDITSECTION__' ],
- 'nogallery' => [ '0', '__JOGALERI__', '__NOGALLERY__' ],
- 'notoc' => [ '0', '__JOTP__', '__NOTOC__' ],
+ 'noeditsection' => [ '0', '__PAREDAKTIMPJESE__', '__JOREDAKTIMSEKSIONI__', '__NOEDITSECTION__' ],
+ 'nogallery' => [ '0', '__PAGALERI__', '__JOGALERI__', '__NOGALLERY__' ],
+ 'notoc' => [ '0', '__PATP__', '__JOTP__', '__NOTOC__' ],
'numberofactiveusers' => [ '1', 'NUMRIIPËRDORUESVEAKTIVË', 'NUMBEROFACTIVEUSERS' ],
- 'numberofadmins' => [ '1', 'NUMRIIADMINISTRUESVE', 'NUMBEROFADMINS' ],
+ 'numberofadmins' => [ '1', 'NUMRIIADMINISTRATORËVE', 'NUMRIIADMINISTRUESVE', 'NUMBEROFADMINS' ],
'numberofarticles' => [ '1', 'NUMRIIARTIKUJVE', 'NUMBEROFARTICLES' ],
'numberofedits' => [ '1', 'NUMRIREDAKTIMEVE', 'NUMBEROFEDITS' ],
'numberoffiles' => [ '1', 'NUMRIISKEDAVE', 'NUMBEROFFILES' ],
@@ -191,7 +194,7 @@ $magicWords = [
'redirect' => [ '0', '#RIDREJTO', '#REDIRECT' ],
'server' => [ '0', 'SERVERI', 'SERVER' ],
'servername' => [ '0', 'EMRIISERVERIT', 'SERVERNAME' ],
- 'sitename' => [ '1', 'EMRIISAJTIT', 'SITENAME' ],
+ 'sitename' => [ '1', 'EMRIISITIT', 'EMRIISAJTIT', 'SITENAME' ],
'subpagename' => [ '1', 'EMRIINËNFAQES', 'SUBPAGENAME' ],
'subpagenamee' => [ '1', 'EMRIINËNFAQESE', 'SUBPAGENAMEE' ],
'subst' => [ '0', 'ZËVN', 'SUBST:' ],
diff --git a/maintenance/deleteAutoPatrolLogs.php b/maintenance/deleteAutoPatrolLogs.php
index 6f122239c092..60636c605967 100644
--- a/maintenance/deleteAutoPatrolLogs.php
+++ b/maintenance/deleteAutoPatrolLogs.php
@@ -119,7 +119,7 @@ class DeleteAutoPatrolLogs extends Maintenance {
'log_id',
$conds,
__METHOD__,
- [ 'LIMIT' => $this->getBatchSize() ]
+ [ 'LIMIT' => $this->getBatchSize(), 'ORDER BY' => 'log_id' ]
);
}
@@ -148,7 +148,7 @@ class DeleteAutoPatrolLogs extends Maintenance {
[ 'log_id', 'log_params' ],
$conds,
__METHOD__,
- [ 'LIMIT' => $batchSize ]
+ [ 'LIMIT' => $batchSize, 'ORDER BY' => 'log_id' ]
);
$last = null;
diff --git a/maintenance/generateSchemaChangeSql.php b/maintenance/generateSchemaChangeSql.php
index 870dbe04e87f..31587029c95f 100644
--- a/maintenance/generateSchemaChangeSql.php
+++ b/maintenance/generateSchemaChangeSql.php
@@ -61,7 +61,13 @@ class GenerateSchemaChangeSql extends Maintenance {
public function execute() {
global $IP;
$jsonPath = $this->getOption( 'json' );
- $relativeJsonPath = str_replace( "$IP/", '', $jsonPath );
+ $installPath = $IP;
+ // For windows
+ if ( DIRECTORY_SEPARATOR === '\\' ) {
+ $installPath = strtr( $installPath, '\\', '/' );
+ $jsonPath = strtr( $jsonPath, '\\', '/' );
+ }
+ $relativeJsonPath = str_replace( "$installPath/", '', $jsonPath );
$sqlPath = $this->getOption( 'sql' );
$abstractSchemaChange = json_decode( file_get_contents( $jsonPath ), true );
diff --git a/maintenance/generateSchemaSql.php b/maintenance/generateSchemaSql.php
index 0108be85e85c..14583828603e 100644
--- a/maintenance/generateSchemaSql.php
+++ b/maintenance/generateSchemaSql.php
@@ -62,8 +62,14 @@ class GenerateSchemaSql extends Maintenance {
global $IP;
$platform = $this->getOption( 'type', 'mysql' );
$jsonPath = $this->getOption( 'json', __DIR__ . '/tables.json' );
+ $installPath = $IP;
+ // For windows
+ if ( DIRECTORY_SEPARATOR === '\\' ) {
+ $installPath = strtr( $installPath, '\\', '/' );
+ $jsonPath = strtr( $jsonPath, '\\', '/' );
+ }
$relativeJsonPath = str_replace(
- [ "$IP/extensions/", "$IP/" ],
+ [ "$installPath/extensions/", "$installPath/" ],
'',
$jsonPath
);
diff --git a/maintenance/resetPageRandom.php b/maintenance/resetPageRandom.php
index 5362149b37bc..65fa9136b987 100644
--- a/maintenance/resetPageRandom.php
+++ b/maintenance/resetPageRandom.php
@@ -33,9 +33,9 @@ class ResetPageRandom extends Maintenance {
parent::__construct();
$this->addDescription( 'Reset the page_random for articles within given date range' );
$this->addOption( 'from',
- 'From date range selector to select articles to update, ex: 20041011000000' );
+ 'From date range selector to select articles to update, ex: 20041011000000', false, true );
$this->addOption( 'to',
- 'To date range selector to select articles to update, ex: 20050708000000' );
+ 'To date range selector to select articles to update, ex: 20050708000000', false, true );
$this->addOption( 'dry', 'Do not update column' );
$this->addOption( 'batch-start',
'Optional: Use when you need to restart the reset process from a given page ID offset'
diff --git a/maintenance/update.php b/maintenance/update.php
index 38698b981022..5580ecc75a0f 100755
--- a/maintenance/update.php
+++ b/maintenance/update.php
@@ -93,7 +93,7 @@ class UpdateMediaWiki extends Maintenance {
}
$this->fileHandle = null;
- if ( substr( $this->getOption( 'schema' ), 0, 2 ) === "--" ) {
+ if ( substr( $this->getOption( 'schema', '' ), 0, 2 ) === "--" ) {
$this->fatalError( "The --schema option requires a file as an argument.\n" );
} elseif ( $this->hasOption( 'schema' ) ) {
$file = $this->getOption( 'schema' );
diff --git a/tests/phpunit/MediaWikiIntegrationTestCase.php b/tests/phpunit/MediaWikiIntegrationTestCase.php
index 9e48075ebe92..fea9766c9639 100644
--- a/tests/phpunit/MediaWikiIntegrationTestCase.php
+++ b/tests/phpunit/MediaWikiIntegrationTestCase.php
@@ -2471,8 +2471,8 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
if ( $ignoreFormat !== false ) {
$this->assertThat( $jobStatus['error'],
$this->logicalOr(
- $this->matches( $ignoreFormat ),
- $this->isNull()
+ $this->isNull(),
+ $this->matches( $ignoreFormat )
),
"Error for job of type {$jobStatus['type']}"
);
diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php
index 4d10d8931db0..b9734419e1ba 100644
--- a/tests/phpunit/includes/OutputPageTest.php
+++ b/tests/phpunit/includes/OutputPageTest.php
@@ -727,15 +727,35 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
// HTML title should change as well
$this->assertSame( $this->getMsgText( $op, 'pagetitle', 'foobar' ), $op->getHTMLTitle() );
- // Test set to text with good and bad HTML. We don't try to be comprehensive here, that
- // belongs in Sanitizer tests.
- $op->setPageTitle( '<script>a</script>&amp;<i>b</i>' );
+ // Test set to text with good and bad HTML. We don't try to be *too*
+ // comprehensive here, that belongs in Sanitizer tests, but we'll
+ // address the issues specifically noted in T298401/T67747 at least...
+ $sanitizerTests = [
+ [
+ 'input' => '<script>a</script>&amp;<i>b</i>',
+ 'getPageTitle' => '&lt;script&gt;a&lt;/script&gt;&amp;<i>b</i>',
+ 'getHTMLTitle' => '<script>a</script>&b',
+ ],
+ [
+ 'input' => '<code style="display:none">', # T298401
+ 'getPageTitle' => '<code style="display:none"></code>',
+ 'getHTMLTitle' => '',
+ ],
+ [
+ 'input' => '<b>Foo bar<b>', # T67747
+ 'getPageTitle' => '<b>Foo bar<b></b></b>',
+ 'getHTMLTitle' => 'Foo bar',
+ ],
+ ];
+ foreach ( $sanitizerTests as $case ) {
+ $op->setPageTitle( $case['input'] );
- $this->assertSame( '&lt;script&gt;a&lt;/script&gt;&amp;<i>b</i>', $op->getPageTitle() );
- $this->assertSame(
- $this->getMsgText( $op, 'pagetitle', '<script>a</script>&b' ),
- $op->getHTMLTitle()
- );
+ $this->assertSame( $case['getPageTitle'], $op->getPageTitle() );
+ $this->assertSame(
+ $this->getMsgText( $op, 'pagetitle', $case['getHTMLTitle'] ),
+ $op->getHTMLTitle()
+ );
+ }
// Test set to message
$text = $this->getMsgText( $op, 'mainpage' );
diff --git a/tests/phpunit/includes/Revision/ContributionsLookupTest.php b/tests/phpunit/includes/Revision/ContributionsLookupTest.php
index cba3ce82cd95..26b6191750d8 100644
--- a/tests/phpunit/includes/Revision/ContributionsLookupTest.php
+++ b/tests/phpunit/includes/Revision/ContributionsLookupTest.php
@@ -399,18 +399,15 @@ class ContributionsLookupTest extends MediaWikiIntegrationTestCase {
ContributionsSegment $segmentObject,
RevisionRecord $actual
): void {
- // FIXME: fails under postgres, see T195807
- if ( $this->db->getType() !== 'postgres' ) {
- $actualTags = $segmentObject->getTagsForRevision( $actual->getId() );
-
- // Tag 3 was disabled and should not be included in results
- $this->assertArrayNotHasKey( self::TAG3, $actualTags );
- foreach ( $actualTags as $tagName => $actualTag ) {
- $this->assertContains( $tagName, $expectedTags );
- $this->assertInstanceOf( Message::class, $actualTag );
- $this->assertEquals( "<i>" . self::TAG_DISPLAY . "</i>", $actualTag->parse() );
- $this->assertEquals( "''" . self::TAG_DISPLAY . "''", $actualTag->text() );
- }
+ $actualTags = $segmentObject->getTagsForRevision( $actual->getId() );
+
+ // Tag 3 was disabled and should not be included in results
+ $this->assertArrayNotHasKey( self::TAG3, $actualTags );
+ foreach ( $actualTags as $tagName => $actualTag ) {
+ $this->assertContains( $tagName, $expectedTags );
+ $this->assertInstanceOf( Message::class, $actualTag );
+ $this->assertEquals( "<i>" . self::TAG_DISPLAY . "</i>", $actualTag->parse() );
+ $this->assertEquals( "''" . self::TAG_DISPLAY . "''", $actualTag->text() );
}
}
}
diff --git a/tests/phpunit/includes/Storage/NameTableStoreTest.php b/tests/phpunit/includes/Storage/NameTableStoreTest.php
index 18dc2d26b126..b84cc05571cd 100644
--- a/tests/phpunit/includes/Storage/NameTableStoreTest.php
+++ b/tests/phpunit/includes/Storage/NameTableStoreTest.php
@@ -333,7 +333,7 @@ class NameTableStoreTest extends MediaWikiIntegrationTestCase {
}
public function testGetAndAcquireIdInsertCallback() {
- // FIXME: fails under postgres
+ // Postgres does not allow to specify the SERIAL column on insert to fake an id
$this->markTestSkippedIfDbType( 'postgres' );
$store = $this->getNameTableSqlStore(
diff --git a/tests/phpunit/includes/Storage/SqlBlobStoreTest.php b/tests/phpunit/includes/Storage/SqlBlobStoreTest.php
index 3b7730643ae3..4d435b311acd 100644
--- a/tests/phpunit/includes/Storage/SqlBlobStoreTest.php
+++ b/tests/phpunit/includes/Storage/SqlBlobStoreTest.php
@@ -425,7 +425,7 @@ class SqlBlobStoreTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Storage\SqlBlobStore::getBlob
*/
public function testSimpleStoreGetBlobSimpleRoundtripWindowsLegacyEncodingGzip( $blob ) {
- // FIXME: fails under postgres
+ // FIXME: fails under postgres - T298692
$this->markTestSkippedIfDbType( 'postgres' );
$store = $this->getBlobStore();
$store->setLegacyEncoding( 'windows-1252' );
diff --git a/tests/phpunit/includes/api/ApiBaseTest.php b/tests/phpunit/includes/api/ApiBaseTest.php
index 223bb42166a5..e7cf45c88f99 100644
--- a/tests/phpunit/includes/api/ApiBaseTest.php
+++ b/tests/phpunit/includes/api/ApiBaseTest.php
@@ -186,9 +186,6 @@ class ApiBaseTest extends ApiTestCase {
}
public function testGetTitleOrPageIdInvalidPageId() {
- // FIXME: fails under postgres
- $this->markTestSkippedIfDbType( 'postgres' );
-
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'There is no page with ID 2147483648.' );
$mock = new MockApi();
diff --git a/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php b/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
index 75537466d6e4..d5d6ecef159f 100644
--- a/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
+++ b/tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
@@ -41,9 +41,6 @@ class ApiQueryUserContribsTest extends ApiTestCase {
* @param int $revs Number of revisions to expect
*/
public function testSorting( $params, $reverse, $revs ) {
- // FIXME: fails under sqlite
- $this->markTestSkippedIfDbType( 'sqlite' );
-
if ( isset( $params['ucuserids'] ) ) {
$params['ucuserids'] = implode( '|', array_map( [ User::class, 'idFromName' ], $params['ucuserids'] ) );
}
diff --git a/tests/phpunit/includes/parser/ParserCacheSerializationTestCases.php b/tests/phpunit/includes/parser/ParserCacheSerializationTestCases.php
index b9cc5636ec9b..6e321b6de833 100644
--- a/tests/phpunit/includes/parser/ParserCacheSerializationTestCases.php
+++ b/tests/phpunit/includes/parser/ParserCacheSerializationTestCases.php
@@ -185,8 +185,8 @@ abstract class ParserCacheSerializationTestCases {
);
$parserOutputWithMetadata->addExternalLink( 'https://test.org' );
$parserOutputWithMetadata->addHeadItem( 'head_item1', 'tag1' );
- $parserOutputWithMetadata->addModules( 'module1' );
- $parserOutputWithMetadata->addModuleStyles( 'module_style1' );
+ $parserOutputWithMetadata->addModules( [ 'module1' ] );
+ $parserOutputWithMetadata->addModuleStyles( [ 'module_style1' ] );
$parserOutputWithMetadata->addJsConfigVars( 'key1', 'value1' );
$parserOutputWithMetadata->addOutputHook( 'hook1', self::MOCK_EXT_DATA );
$parserOutputWithMetadata->addWarning( 'warning1' );
diff --git a/tests/phpunit/includes/parser/ParserOutputTest.php b/tests/phpunit/includes/parser/ParserOutputTest.php
index 86029cf83131..11de4a4654bc 100644
--- a/tests/phpunit/includes/parser/ParserOutputTest.php
+++ b/tests/phpunit/includes/parser/ParserOutputTest.php
@@ -594,8 +594,8 @@ EOF
$a = new ParserOutput();
$a->addHeadItem( '<foo1>' );
$a->addHeadItem( '<bar1>', 'bar' );
- $a->addModules( 'test-module-a' );
- $a->addModuleStyles( 'test-module-styles-a' );
+ $a->addModules( [ 'test-module-a' ] );
+ $a->addModuleStyles( [ 'test-module-styles-a' ] );
$a->addJsConfigVars( 'test-config-var-a', 'a' );
$a->addExtraCSPStyleSrc( 'css.com' );
$a->addExtraCSPStyleSrc( 'css2.com' );
@@ -606,8 +606,8 @@ EOF
$b->setIndexPolicy( 'noindex' );
$b->addHeadItem( '<foo2>' );
$b->addHeadItem( '<bar2>', 'bar' );
- $b->addModules( 'test-module-b' );
- $b->addModuleStyles( 'test-module-styles-b' );
+ $b->addModules( [ 'test-module-b' ] );
+ $b->addModuleStyles( [ 'test-module-styles-b' ] );
$b->addJsConfigVars( 'test-config-var-b', 'b' );
$b->addJsConfigVars( 'test-config-var-a', 'X' );
$b->addExtraCSPStyleSrc( 'https://css.ca' );
diff --git a/tests/phpunit/includes/search/PrefixSearchTest.php b/tests/phpunit/includes/search/PrefixSearchTest.php
index c6e8fdedec3f..9b8cb76921b4 100644
--- a/tests/phpunit/includes/search/PrefixSearchTest.php
+++ b/tests/phpunit/includes/search/PrefixSearchTest.php
@@ -190,17 +190,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
* @covers PrefixSearch::searchBackend
*/
public function testSearch( array $case ) {
- // FIXME: fails under postgres
- $this->markTestSkippedIfDbType( 'postgres' );
$this->searchProvision( null );
$namespaces = $case['namespaces'] ?? [];
- if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
- // Postgres will sort lexicographically on utf8 code units (" " before "/")
- sort( $case['results'], SORT_STRING );
- }
-
$searcher = new StringPrefixSearch;
$results = $searcher->search( $case['query'], 3, $namespaces );
$this->assertEquals(
@@ -216,8 +209,6 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
* @covers PrefixSearch::searchBackend
*/
public function testSearchWithOffset( array $case ) {
- // FIXME: fails under postgres
- $this->markTestSkippedIfDbType( 'postgres' );
$this->searchProvision( null );
$namespaces = $case['namespaces'] ?? [];
@@ -225,11 +216,6 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
$searcher = new StringPrefixSearch;
$results = $searcher->search( $case['query'], 3, $namespaces, 1 );
- if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
- // Postgres will sort lexicographically on utf8 code units (" " before "/")
- sort( $case['results'], SORT_STRING );
- }
-
// We don't expect the first result when offsetting
array_shift( $case['results'] );
// And sometimes we expect a different last result
diff --git a/tests/phpunit/includes/user/UserTest.php b/tests/phpunit/includes/user/UserTest.php
index 241d8ee9d6ee..fb28d5f5cd18 100644
--- a/tests/phpunit/includes/user/UserTest.php
+++ b/tests/phpunit/includes/user/UserTest.php
@@ -560,9 +560,6 @@ class UserTest extends MediaWikiIntegrationTestCase {
* @covers User::findUsersByGroup
*/
public function testFindUsersByGroup() {
- // FIXME: fails under postgres
- $this->markTestSkippedIfDbType( 'postgres' );
-
$users = User::findUsersByGroup( [] );
$this->assertSame( 0, iterator_count( $users ) );
diff --git a/tests/phpunit/integration/includes/export/WikiExporterFactoryTest.php b/tests/phpunit/integration/includes/export/WikiExporterFactoryTest.php
index 8f9db6fcdd38..8cb1facf4df8 100644
--- a/tests/phpunit/integration/includes/export/WikiExporterFactoryTest.php
+++ b/tests/phpunit/integration/includes/export/WikiExporterFactoryTest.php
@@ -9,7 +9,7 @@ use WikiExporter;
use XmlDumpWriter;
/**
- * @covers MediaWiki\Export\WikiExporterFactory
+ * @covers \MediaWiki\Export\WikiExporterFactory
*/
class WikiExporterFactoryTest extends MediaWikiIntegrationTestCase {
use FactoryArgTestTrait;
@@ -36,4 +36,11 @@ class WikiExporterFactoryTest extends MediaWikiIntegrationTestCase {
protected function getFactoryMethodName() {
return 'getWikiExporter';
}
+
+ protected function getOverriddenMockValueForParam( $param ) {
+ if ( $param->getName() === 'text' ) {
+ return [ WikiExporter::TEXT ];
+ }
+ return [];
+ }
}
diff --git a/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php b/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
index 88a8e8f2d63c..809e6eefea9f 100644
--- a/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
+++ b/tests/phpunit/maintenance/deleteAutoPatrolLogsTest.php
@@ -257,9 +257,6 @@ class DeleteAutoPatrolLogsTest extends MaintenanceBaseTestCase {
* @dataProvider runProvider
*/
public function testRun( $expected, $args ) {
- // FIXME: fails under postgres
- $this->markTestSkippedIfDbType( 'postgres' );
-
$this->maintenance->loadWithArgv( $args );
$this->maintenance->execute();
diff --git a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php
index ad6e8d217270..69c2d0a74b04 100644
--- a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php
+++ b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php
@@ -12,7 +12,7 @@ class MediaWikiIntegrationTestCaseSchema1Test extends MediaWikiIntegrationTestCa
protected function setUp(): void {
parent::setUp();
- // FIXME: fails under postgres
+ // FIXME: fails under postgres - T198222
$this->markTestSkippedIfDbType( 'postgres' );
}
diff --git a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php
index 8d73e188d759..39c948493fdb 100644
--- a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php
+++ b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php
@@ -14,7 +14,7 @@ class MediaWikiIntegrationTestCaseSchema2Test extends MediaWikiIntegrationTestCa
protected function setUp(): void {
parent::setUp();
- // FIXME: fails under postgres
+ // FIXME: fails under postgres - T198222
$this->markTestSkippedIfDbType( 'postgres' );
}
diff --git a/tests/phpunit/unit/includes/Settings/Cache/CachedSourceTest.php b/tests/phpunit/unit/includes/Settings/Cache/CachedSourceTest.php
index d6e9386632c3..81ef09f5a38f 100644
--- a/tests/phpunit/unit/includes/Settings/Cache/CachedSourceTest.php
+++ b/tests/phpunit/unit/includes/Settings/Cache/CachedSourceTest.php
@@ -5,6 +5,7 @@ namespace MediaWiki\Tests\Unit\Settings\Cache;
use BagOStuff;
use MediaWiki\Settings\Cache\CacheableSource;
use MediaWiki\Settings\Cache\CachedSource;
+use MediaWiki\Settings\SettingsBuilderException;
use PHPUnit\Framework\TestCase;
/**
@@ -118,4 +119,50 @@ class CachedSourceTest extends TestCase {
$this->assertSame( $settings, $cacheSource->load() );
}
+
+ public function testLoadStale() {
+ $cache = $this->createMock( BagOStuff::class );
+ $source = $this->createMock( CacheableSource::class );
+ $cacheSource = new CachedSource( $cache, $source );
+
+ $settings = [ 'config' => [ 'Foo' => 'value' ] ];
+ $hashKey = 'abc123';
+ $key = 'global:MediaWiki\Tests\Unit\Settings\Cache\CachedSourceTest:' . $hashKey;
+ $expired = microtime( true ) - 1;
+
+ $source
+ ->expects( $this->atLeastOnce() )
+ ->method( 'allowsStaleLoad' )
+ ->willReturn( true );
+
+ $source
+ ->expects( $this->once() )
+ ->method( 'getHashKey' )
+ ->willReturn( $hashKey );
+
+ $cache
+ ->expects( $this->once() )
+ ->method( 'makeGlobalKey' )
+ ->with( 'MediaWiki\Settings\Cache\CachedSource', $hashKey )
+ ->willReturn( $key );
+
+ $cache
+ ->expects( $this->once() )
+ ->method( 'get' )
+ ->with( $key )
+ ->willReturn(
+ [
+ 'value' => $settings,
+ 'expiry' => $expired,
+ 'generation' => 1.0
+ ]
+ );
+
+ $source
+ ->expects( $this->once() )
+ ->method( 'load' )
+ ->will( $this->throwException( new SettingsBuilderException( 'foo' ) ) );
+
+ $this->assertSame( $settings, $cacheSource->load() );
+ }
}
diff --git a/tests/phpunit/unit/includes/Settings/Source/FileSourceTest.php b/tests/phpunit/unit/includes/Settings/Source/FileSourceTest.php
index e1782af427c5..4b7295d825a9 100644
--- a/tests/phpunit/unit/includes/Settings/Source/FileSourceTest.php
+++ b/tests/phpunit/unit/includes/Settings/Source/FileSourceTest.php
@@ -11,6 +11,11 @@ use PHPUnit\Framework\TestCase;
* @covers \MediaWiki\Settings\Source\FileSource
*/
class FileSourceTest extends TestCase {
+ public function testAllowsStaleLoad() {
+ $source = new FileSource( __DIR__ . 'foo.json' );
+ $this->assertFalse( $source->allowsStaleLoad() );
+ }
+
public function testLoad() {
$source = new FileSource( __DIR__ . '/fixtures/settings.json' );
$settings = $source->load();
diff --git a/tests/phpunit/unit/includes/libs/MapCacheLRUTest.php b/tests/phpunit/unit/includes/libs/MapCacheLRUTest.php
index 4a5bc3c66243..9020c67ce8ab 100644
--- a/tests/phpunit/unit/includes/libs/MapCacheLRUTest.php
+++ b/tests/phpunit/unit/includes/libs/MapCacheLRUTest.php
@@ -250,8 +250,8 @@ class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
*/
public function testHasInvalidKey( $key ) {
$cache = new MapCacheLRU( 3 );
- $this->expectWarning();
- $this->expectWarningMessageMatches( '/should be either a string or an integer/' );
+ $this->expectException( UnexpectedValueException::class );
+ $this->expectExceptionMessage( 'must be string or integer' );
$cache->has( $key );
}
@@ -261,8 +261,8 @@ class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
*/
public function testGetInvalidKey( $key ) {
$cache = new MapCacheLRU( 3 );
- $this->expectWarning();
- $this->expectWarningMessageMatches( '/should be either a string or an integer/' );
+ $this->expectException( UnexpectedValueException::class );
+ $this->expectExceptionMessage( 'must be string or integer' );
$cache->get( $key );
}
@@ -272,8 +272,8 @@ class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
*/
public function testSetInvalidKey( $key ) {
$cache = new MapCacheLRU( 3 );
- $this->expectWarning();
- $this->expectWarningMessageMatches( '/should be either a string or an integer/' );
+ $this->expectException( UnexpectedValueException::class );
+ $this->expectExceptionMessage( 'must be string or integer' );
$cache->set( $key, 'x' );
}
@@ -283,8 +283,8 @@ class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
*/
public function testHasFieldInvalidKey( $field ) {
$cache = MapCacheLRU::newFromArray( [ 'key' => [] ], 3 );
- $this->expectWarning();
- $this->expectWarningMessageMatches( '/should be either a string or an integer/' );
+ $this->expectException( UnexpectedValueException::class );
+ $this->expectExceptionMessage( 'must be string or integer' );
$cache->hasField( 'key', $field );
}
@@ -294,8 +294,8 @@ class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
*/
public function testGetFieldInvalidKey( $field ) {
$cache = MapCacheLRU::newFromArray( [ 'key' => [] ], 3 );
- $this->expectWarning();
- $this->expectWarningMessageMatches( '/should be either a string or an integer/' );
+ $this->expectException( UnexpectedValueException::class );
+ $this->expectExceptionMessage( 'must be string or integer' );
$cache->getField( 'key', $field );
}
@@ -305,8 +305,8 @@ class MapCacheLRUTest extends PHPUnit\Framework\TestCase {
*/
public function testSetFieldInvalidKey( $field ) {
$cache = new MapCacheLRU( 3 );
- $this->expectWarning();
- $this->expectWarningMessageMatches( '/Field keys must be string or integer/' );
+ $this->expectException( UnexpectedValueException::class );
+ $this->expectExceptionMessage( 'must be string or integer' );
$cache->setField( 'key', $field, 'x' );
}
}