// approximate error count: 45
"PhanTypeMismatchArgument",
- // approximate error count: 693
- "PhanUndeclaredProperty",
] );
// This helps a lot in discovering bad code, but unfortunately it will always fail for
// @todo Enable when the issue above is resolved and we update our config!
$cfg['redundant_condition_detection'] = false;
+// Do not use aliases in core.
+// Use the correct name, because we don't need backward compatibility
+$cfg['enable_class_alias_support'] = false;
+
$cfg['ignore_undeclared_variables_in_global_scope'] = true;
// @todo It'd be great if we could just make phan read these from DefaultSettings, to avoid
// duplicating the types.
* The GetBlockedStatus hook is deprecated. Use GetUserBlock instead, to add or
remove a block.
* $wgContentHandlerUseDB is deprecated and should always be true.
+* StreamFile::send404Message() and StreamFile::parseRange() are now deprecated.
+ Use HTTPFileStreamer::send404Message() and HTTPFileStreamer::parseRange()
+ respectively instead.
=== Other changes in 1.34 ===
* …
Note that lists should be in the format name => object and the names in both
lists should be distinct.
-'SecondaryDataUpdates': DEPRECATED! Use RevisionDataUpdates or override
+'SecondaryDataUpdates': DEPRECATED since 1.32! Use RevisionDataUpdates or override
ContentHandler::getSecondaryDataUpdates instead.
Allows modification of the list of DataUpdates to perform when page content is modified.
$title: Title of the page that is being edited.
&$opts: Options to use for the query
&$join: Join conditions
-'WikiPageDeletionUpdates': DEPRECATED! Use PageDeletionDataUpdates or
+'WikiPageDeletionUpdates': DEPRECATED since 1.32! Use PageDeletionDataUpdates or
override ContentHandler::getDeletionDataUpdates instead.
Manipulates the list of DeferrableUpdates to be applied when a page is deleted.
$page: the WikiPage
use MediaWiki\EditPage\TextConflictHelper;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\ScopedCallback;
/**
* @ingroup Feed
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Helper functions for feeds
*/
use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Some internal bits split of from Skin.php. These functions are used
* @param ParserOptions|null $options Either the ParserOption to use or null to only get the
* current ParserOption object. This parameter is deprecated since 1.31.
* @return ParserOptions
+ * @suppress PhanUndeclaredProperty For isBogus
*/
public function parserOptions( $options = null ) {
if ( $options !== null ) {
use ActorMigration;
use CommentStore;
-use MediaWiki\Logger\Spi as LoggerSpi;
+use Psr\Log\LoggerInterface;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStoreFactory;
use WANObjectCache;
private $dbLoadBalancerFactory;
/** @var WANObjectCache */
private $cache;
- /** @var LoggerSpi */
- private $loggerProvider;
+ /** @var LoggerInterface */
+ private $logger;
/** @var CommentStore */
private $commentStore;
* @param CommentStore $commentStore
* @param ActorMigration $actorMigration
* @param int $migrationStage
- * @param LoggerSpi $loggerProvider
+ * @param LoggerInterface $logger
* @param bool $contentHandlerUseDB see {@link $wgContentHandlerUseDB}. Must be the same
* for all wikis in the cluster. Will go away after MCR migration.
*/
CommentStore $commentStore,
ActorMigration $actorMigration,
$migrationStage,
- LoggerSpi $loggerProvider,
+ LoggerInterface $logger,
$contentHandlerUseDB
) {
Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
$this->commentStore = $commentStore;
$this->actorMigration = $actorMigration;
$this->mcrMigrationStage = $migrationStage;
- $this->loggerProvider = $loggerProvider;
+ $this->logger = $logger;
$this->contentHandlerUseDB = $contentHandlerUseDB;
}
*
* @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
*
- * @return RevisionStore for the given wikiId with all necessary services and a logger
+ * @return RevisionStore for the given wikiId with all necessary services
*/
public function getRevisionStore( $dbDomain = false ) {
Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
$dbDomain
);
- $store->setLogger( $this->loggerProvider->getLogger( 'RevisionStore' ) );
+ $store->setLogger( $this->logger );
$store->setContentHandlerUseDB( $this->contentHandlerUseDB );
return $store;
new ServiceOptions(
BlockManager::$constructorOptions, $services->getMainConfig()
),
- $services->getPermissionManager()
+ $services->getPermissionManager(),
+ LoggerFactory::getInstance( 'BlockManager' )
);
},
$services->getCommentStore(),
$services->getActorMigration(),
$config->get( 'MultiContentRevisionSchemaMigrationStage' ),
- LoggerFactory::getProvider(),
+ LoggerFactory::getInstance( 'RevisionStore' ),
$config->get( 'ContentHandlerUseDB' )
);
* ]
*
* @return Status[]
+ * @suppress PhanUndeclaredProperty Status vs StatusValue
*/
public function splitByErrorType() {
list( $errorsOnlyStatus, $warningsOnlyStatus ) = parent::splitByErrorType();
* @param ExternalStoreAccess $extStoreAccess Access layer for external storage
* @param WANObjectCache $cache A cache manager for caching blobs. This can be the local
* wiki's default instance even if $dbDomain refers to a different wiki, since
- * makeGlobalKey() is used to constructed a key that allows cached blobs from the
- * same database to be re-used between wikis. For example, enwiki and frwiki will
- * use the same cache keys for blobs from the wikidatawiki database, regardless of
- * the cache's default key space.
+ * makeGlobalKey() is used to construct a key that allows cached blobs from the
+ * same database to be re-used between wikis. For example, wiki A and wiki B will
+ * use the same cache keys for blobs fetched from wiki C, regardless of the
+ * wiki-specific default key space.
* @param bool|string $dbDomain The ID of the target wiki database. Use false for the local wiki.
*/
public function __construct(
* Get a cache key for a given Blob address.
*
* The cache key is constructed in a way that allows cached blobs from the same database
- * to be re-used between wikis. For example, enwiki and frwiki will use the same cache keys
- * for blobs from the wikidatawiki database.
+ * to be re-used between wikis. For example, wiki A and wiki B will use the same cache keys
+ * for blobs fetched from wiki C.
*
* @param string $blobAddress
* @return string
*/
private function getCacheKey( $blobAddress ) {
return $this->cache->makeGlobalKey(
- 'BlobStore',
- 'address',
+ 'SqlBlobStore-blob',
$this->dbLoadBalancer->resolveDomainID( $this->dbDomain ),
$blobAddress
);
* @param string $fname Full name and path of the file to stream
* @param int $flags Bitfield of STREAM_* constants
* @since 1.24
+ * @deprecated since 1.34, use HTTPFileStreamer::send404Message() instead
*/
public static function send404Message( $fname, $flags = 0 ) {
+ wfDeprecated( __METHOD__, '1.34' );
HTTPFileStreamer::send404Message( $fname, $flags );
}
* @param int $size File size
* @return array|string Returns error string on failure (start, end, length)
* @since 1.24
+ * @deprecated since 1.34, use HTTPFileStreamer::parseRange() instead
*/
public static function parseRange( $range, $size ) {
+ wfDeprecated( __METHOD__, '1.34' );
return HTTPFileStreamer::parseRange( $range, $size );
}
*/
use MediaWiki\Permissions\PermissionManager;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\Database;
/**
* @ingroup Actions
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* User interface for the rollback action
);
if ( $row ) {
$rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
+ // @phan-suppress-next-line PhanUndeclaredProperty
$rev->isArchive = true;
}
}
}
}
+ // @phan-suppress-next-line PhanUndeclaredProperty
if ( !empty( $rev->isArchive ) ) {
$this->getMain()->setCacheMode( 'private' );
$vals["{$prefix}archive"] = true;
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* A module that allows for editing and creating pages.
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* @ingroup API
private $redirect;
private $bl_ns, $bl_from, $bl_from_ns, $bl_table, $bl_code, $bl_title, $bl_fields, $hasNS;
+ /** @var string */
+ private $helpUrl;
+
/**
* Maps ns and title to pageid
*
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\NameTableAccessException;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Query module to enumerate all deleted revisions.
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\User\UserIdentity;
use MWCryptHash;
+use Psr\Log\LoggerInterface;
use User;
use WebRequest;
use WebResponse;
* @since 1.34 Refactored from User and Block.
*/
class BlockManager {
+ /** @var PermissionManager */
+ private $permissionManager;
+
+ /** @var ServiceOptions */
+ private $options;
+
/**
* TODO Make this a const when HHVM support is dropped (T192166)
*
'SoftBlockRanges',
];
+ /** @var LoggerInterface */
+ private $logger;
+
/**
* @param ServiceOptions $options
* @param PermissionManager $permissionManager
+ * @param LoggerInterface $logger
*/
public function __construct(
ServiceOptions $options,
- PermissionManager $permissionManager
+ PermissionManager $permissionManager,
+ LoggerInterface $logger
) {
$options->assertRequiredOptions( self::$constructorOptions );
$this->options = $options;
$this->permissionManager = $permissionManager;
+ $this->logger = $logger;
}
/**
$ipList = $this->checkHost( $hostname );
if ( $ipList ) {
- wfDebugLog(
- 'dnsblacklist',
+ $this->logger->info(
"Hostname $hostname is {$ipList[0]}, it's a proxy says $basename!"
);
$found = true;
break;
}
- wfDebugLog( 'dnsblacklist', "Requested $hostname, not found in $basename." );
+ $this->logger->debug( "Requested $hostname, not found in $basename." );
}
}
<?php
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Helper class for category membership changes
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Feed to Special:RecentChanges and Special:RecentChangesLinked.
*/
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
class ChangesList extends ContextSource {
<?php
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Generates a list of changes using an Enhanced system (uses javascript).
* @file
*/
use MediaWiki\Linker\LinkRenderer;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
class RCCacheEntryFactory {
*/
public function newFromRecentChange( RecentChange $baseRC, $watched ) {
$user = $this->context->getUser();
- $counter = $baseRC->counter;
$cacheEntry = RCCacheEntry::newFromParent( $baseRC );
// called too many times (50% of CPU time on RecentChanges!).
$showDiffLinks = $this->showDiffLinks( $cacheEntry, $user );
- $cacheEntry->difflink = $this->buildDiffLink( $cacheEntry, $showDiffLinks, $counter );
- $cacheEntry->curlink = $this->buildCurLink( $cacheEntry, $showDiffLinks, $counter );
+ $cacheEntry->difflink = $this->buildDiffLink( $cacheEntry, $showDiffLinks );
+ $cacheEntry->curlink = $this->buildCurLink( $cacheEntry, $showDiffLinks );
$cacheEntry->lastlink = $this->buildLastLink( $cacheEntry, $showDiffLinks );
// Make user links
/**
* @param RecentChange $cacheEntry
* @param bool $showDiffLinks
- * @param int $counter
*
* @return string
*/
- private function buildCurLink( RecentChange $cacheEntry, $showDiffLinks, $counter ) {
+ private function buildCurLink( RecentChange $cacheEntry, $showDiffLinks ) {
$queryParams = $this->buildCurQueryParams( $cacheEntry );
$curMessage = $this->getMessage( 'cur' );
$logTypes = [ RC_LOG ];
/**
* @param RecentChange $cacheEntry
* @param bool $showDiffLinks
- * @param int $counter
*
* @return string
*/
- private function buildDiffLink( RecentChange $cacheEntry, $showDiffLinks, $counter ) {
+ private function buildDiffLink( RecentChange $cacheEntry, $showDiffLinks ) {
$queryParams = $this->buildDiffQueryParams( $cacheEntry );
$diffMessage = $this->getMessage( 'diff' );
$logTypes = [ RC_NEW, RC_LOG ];
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Item class for a logging table row with its associated change tags.
/** @var array $puaSubset List of private use area codes */
private $puaSubset;
+ /** @var array */
+ private $firstLetters;
+
/**
* @note This assumes $alphabet does not contain U+F3000-U+F3FFF
*
* @author Daniel Kinzler
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Assert\Assert;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
* goal.
*/
class ConsoleLogger extends AbstractLogger {
+ /** @var string */
+ private $channel;
+
/**
* @param string $channel
*/
*/
use MediaWiki\MediaWikiServices as MediaWikiServicesAlias;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\IDatabase;
/** @var WANObjectCache */
protected $wanCache;
+ /**
+ * @var string
+ * @protected Use $this->getName(). Public for back-compat only
+ */
+ public $name;
+
/**
* @param array|null $info
* @throws MWException
/** @var Title */
protected $title; # image title
+ /** @var bool */
+ private $exists;
+
/**
* @throws MWException
* @param Title $title
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Helper class for file deletion
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
- // @phan-suppress-next-line PhanRedefineClassAlias
class_alias( ConcatenatedGzipHistoryBlob::class, 'concatenatedgziphistoryblob' );
}
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
- // @phan-suppress-next-line PhanRedefineClassAlias
class_alias( HistoryBlobCurStub::class, 'historyblobcurstub' );
}
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
- // @phan-suppress-next-line PhanRedefineClassAlias
class_alias( HistoryBlobStub::class, 'historyblobstub' );
}
* @param OOUI\Widget $inputField
* @param array $config
* @return OOUI\FieldLayout|OOUI\ActionFieldLayout
+ * @suppress PhanUndeclaredProperty Only some subclasses declare mClassWithButton
*/
protected function getFieldLayoutOOUI( $inputField, $config ) {
if ( isset( $this->mClassWithButton ) ) {
* @todo FIXME: If made 'required', only the text field should be compulsory.
*/
class HTMLSelectAndOtherField extends HTMLSelectField {
+ /** @var string[] */
+ private $mFlatOptions;
+
public function __construct( $params ) {
if ( array_key_exists( 'other', $params ) ) {
// Do nothing
*
* @private for use by GuzzleHttpRequest only
* @since 1.33
+ * @property StreamInterface $stream Defined in StreamDecoratorTrait via @property, not read by phan
*/
class MWCallbackStream implements StreamInterface {
use StreamDecoratorTrait;
* @ingroup SpecialPage
*/
class ImportStreamSource implements ImportSource {
+ /** @var resource */
+ private $mHandle;
+
/**
* @param resource $handle
*/
*/
class InstallDocFormatter {
+ /** @var string */
+ private $text;
+
public static function format( $text ) {
$obj = new self( $text );
class LocalSettingsGenerator {
protected $extensions = [];
+ protected $skins = [];
protected $values = [];
protected $groupPermissions = [];
protected $dbSettings = '';
*
* @ingroup Deployment
* @since 1.17
- * @property DatabaseMysqlBase $db
+ * @property Wikimedia\Rdbms\DatabaseMysqlBase $db
*/
class MysqlUpdater extends DatabaseUpdater {
protected function getCoreUpdateList() {
*/
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabasePostgres;
use Wikimedia\Rdbms\DBQueryError;
use Wikimedia\Rdbms\DBConnectionError;
}
}
- $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
+ $statuses = $this->doExecuteOpHandlesInternal( $fileOpHandles );
foreach ( $fileOpHandles as $fileOpHandle ) {
$fileOpHandle->closeResources();
}
- return $res;
+ return $statuses;
}
/**
}
/**
- * Sanitize and filter the custom headers from a $params array.
- * Only allows certain "standard" Content- and X-Content- headers.
+ * Filter/normalize a header map to only include mutable "content-"/"x-content-" headers
*
- * @param array $params
- * @return array Sanitized value of 'headers' field in $params
- */
- protected function sanitizeHdrsStrict( array $params ) {
- if ( !isset( $params['headers'] ) ) {
- return [];
- }
-
- $headers = $this->getCustomHeaders( $params['headers'] );
- unset( $headers[ 'content-type' ] );
-
- return $headers;
- }
-
- /**
- * Sanitize and filter the custom headers from a $params array.
- * Only allows certain "standard" Content- and X-Content- headers.
+ * Mutable headers can be changed via HTTP POST even if the file content is the same
*
- * When POSTing data, libcurl adds Content-Type: application/x-www-form-urlencoded
- * if Content-Type is not set, which overwrites the stored Content-Type header
- * in Swift - therefore for POSTing data do not strip the Content-Type header (the
- * previously-stored header that has been already read back from swift is sent)
- *
- * @param array $params
- * @return array Sanitized value of 'headers' field in $params
+ * @see https://docs.openstack.org/api-ref/object-store
+ * @param string[] $headers Map of (header => value) for a swift object
+ * @return string[] Map of (header => value) for Content-* headers mutable via POST
*/
- protected function sanitizeHdrs( array $params ) {
- return isset( $params['headers'] )
- ? $this->getCustomHeaders( $params['headers'] )
- : [];
- }
-
- /**
- * @param array $rawHeaders
- * @return array Custom non-metadata HTTP headers
- */
- protected function getCustomHeaders( array $rawHeaders ) {
- $headers = [];
-
+ protected function extractMutableContentHeaders( array $headers ) {
+ $contentHeaders = [];
// Normalize casing, and strip out illegal headers
- foreach ( $rawHeaders as $name => $value ) {
+ foreach ( $headers as $name => $value ) {
$name = strtolower( $name );
- if ( preg_match( '/^content-length$/', $name ) ) {
- continue; // blacklisted
- } elseif ( preg_match( '/^(x-)?content-/', $name ) ) {
- $headers[$name] = $value; // allowed
- } elseif ( preg_match( '/^content-(disposition)/', $name ) ) {
- $headers[$name] = $value; // allowed
+ if ( !preg_match( '/^(x-)?content-(?!length$)/', $name ) ) {
+ // Only allow content-* and x-content-* headers (but not content-length)
+ continue;
+ } elseif ( $name === 'content-type' && !strlen( $value ) ) {
+ // This header can be set to a value but not unset for sanity
+ continue;
}
+ $contentHeaders[$name] = $value;
}
// By default, Swift has annoyingly low maximum header value limits
- if ( isset( $headers['content-disposition'] ) ) {
+ if ( isset( $contentHeaders['content-disposition'] ) ) {
$disposition = '';
// @note: assume FileBackend::makeContentDisposition() already used
- foreach ( explode( ';', $headers['content-disposition'] ) as $part ) {
+ foreach ( explode( ';', $contentHeaders['content-disposition'] ) as $part ) {
$part = trim( $part );
$new = ( $disposition === '' ) ? $part : "{$disposition};{$part}";
if ( strlen( $new ) <= 255 ) {
break; // too long; sigh
}
}
- $headers['content-disposition'] = $disposition;
+ $contentHeaders['content-disposition'] = $disposition;
}
- return $headers;
+ return $contentHeaders;
}
/**
- * @param array $rawHeaders
- * @return array Custom metadata headers
+ * @see https://docs.openstack.org/api-ref/object-store
+ * @param string[] $headers Map of (header => value) for a swift object
+ * @return string[] Map of (metadata header name => metadata value)
*/
- protected function getMetadataHeaders( array $rawHeaders ) {
- $headers = [];
- foreach ( $rawHeaders as $name => $value ) {
+ protected function extractMetadataHeaders( array $headers ) {
+ $metadataHeaders = [];
+ foreach ( $headers as $name => $value ) {
$name = strtolower( $name );
if ( strpos( $name, 'x-object-meta-' ) === 0 ) {
- $headers[$name] = $value;
+ $metadataHeaders[$name] = $value;
}
}
- return $headers;
+ return $metadataHeaders;
}
/**
- * @param array $rawHeaders
- * @return array Custom metadata headers with prefix removed
+ * @see https://docs.openstack.org/api-ref/object-store
+ * @param string[] $headers Map of (header => value) for a swift object
+ * @return string[] Map of (metadata key name => metadata value)
*/
- protected function getMetadata( array $rawHeaders ) {
+ protected function getMetadataFromHeaders( array $headers ) {
+ $prefixLen = strlen( 'x-object-meta-' );
+
$metadata = [];
- foreach ( $this->getMetadataHeaders( $rawHeaders ) as $name => $value ) {
- $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value;
+ foreach ( $this->extractMetadataHeaders( $headers ) as $name => $value ) {
+ $metadata[substr( $name, $prefixLen )] = $value;
}
return $metadata;
return $status;
}
- $sha1Hash = Wikimedia\base_convert( sha1( $params['content'] ), 16, 36, 31 );
- $contentType = $params['headers']['content-type']
+ // Headers that are not strictly a function of the file content
+ $mutableHeaders = $this->extractMutableContentHeaders( $params['headers'] ?? [] );
+ // Make sure that the "content-type" header is set to something sensible
+ $mutableHeaders['content-type'] = $mutableHeaders['content-type']
?? $this->getContentType( $params['dst'], $params['content'], null );
$reqs = [ [
'method' => 'PUT',
'url' => [ $dstCont, $dstRel ],
- 'headers' => [
- 'content-length' => strlen( $params['content'] ),
- 'etag' => md5( $params['content'] ),
- 'content-type' => $contentType,
- 'x-object-meta-sha1base36' => $sha1Hash
- ] + $this->sanitizeHdrsStrict( $params ),
+ 'headers' => array_merge(
+ $mutableHeaders,
+ [
+ 'content-length' => strlen( $params['content'] ),
+ 'etag' => md5( $params['content'] ),
+ 'x-object-meta-sha1base36' =>
+ Wikimedia\base_convert( sha1( $params['content'] ), 16, 36, 31 )
+ ]
+ ),
'body' => $params['content']
] ];
} else {
$this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
+
+ return SwiftFileOpHandle::CONTINUE_IF_OK;
};
$opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
}
AtEase::suppressWarnings();
- $sha1Hash = sha1_file( $params['src'] );
+ $sha1Base16 = sha1_file( $params['src'] );
AtEase::restoreWarnings();
- if ( $sha1Hash === false ) { // source doesn't exist?
+ if ( $sha1Base16 === false ) { // source doesn't exist?
$status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
return $status;
}
- $sha1Hash = Wikimedia\base_convert( $sha1Hash, 16, 36, 31 );
- $contentType = $params['headers']['content-type']
- ?? $this->getContentType( $params['dst'], null, $params['src'] );
$handle = fopen( $params['src'], 'rb' );
if ( $handle === false ) { // source doesn't exist?
return $status;
}
+ // Headers that are not strictly a function of the file content
+ $mutableHeaders = $this->extractMutableContentHeaders( $params['headers'] ?? [] );
+ // Make sure that the "content-type" header is set to something sensible
+ $mutableHeaders['content-type'] = $mutableHeaders['content-type']
+ ?? $this->getContentType( $params['dst'], null, $params['src'] );
+
$reqs = [ [
'method' => 'PUT',
'url' => [ $dstCont, $dstRel ],
- 'headers' => [
- 'content-length' => filesize( $params['src'] ),
- 'etag' => md5_file( $params['src'] ),
- 'content-type' => $contentType,
- 'x-object-meta-sha1base36' => $sha1Hash
- ] + $this->sanitizeHdrsStrict( $params ),
+ 'headers' => array_merge(
+ $mutableHeaders,
+ [
+ 'content-length' => fstat( $handle )['size'],
+ 'etag' => md5_file( $params['src'] ),
+ 'x-object-meta-sha1base36' => Wikimedia\base_convert( $sha1Base16, 16, 36, 31 )
+ ]
+ ),
'body' => $handle // resource
] ];
} else {
$this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
+
+ return SwiftFileOpHandle::CONTINUE_IF_OK;
};
$opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
$reqs = [ [
'method' => 'PUT',
'url' => [ $dstCont, $dstRel ],
- 'headers' => [
- 'x-copy-from' => '/' . rawurlencode( $srcCont ) .
- '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
- ] + $this->sanitizeHdrsStrict( $params ), // extra headers merged into object
+ 'headers' => array_merge(
+ $this->extractMutableContentHeaders( $params['headers'] ?? [] ),
+ [
+ 'x-copy-from' => '/' . rawurlencode( $srcCont ) . '/' .
+ str_replace( "%2F", "/", rawurlencode( $srcRel ) )
+ ]
+ )
] ];
$method = __METHOD__;
if ( $rcode === 201 ) {
// good
} elseif ( $rcode === 404 ) {
- $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ }
} else {
$this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
+
+ return SwiftFileOpHandle::CONTINUE_IF_OK;
};
$opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
return $status;
}
- $reqs = [
- [
- 'method' => 'PUT',
- 'url' => [ $dstCont, $dstRel ],
- 'headers' => [
- 'x-copy-from' => '/' . rawurlencode( $srcCont ) .
- '/' . str_replace( "%2F", "/", rawurlencode( $srcRel ) )
- ] + $this->sanitizeHdrsStrict( $params ) // extra headers merged into object
- ]
- ];
+ $reqs = [ [
+ 'method' => 'PUT',
+ 'url' => [ $dstCont, $dstRel ],
+ 'headers' => array_merge(
+ $this->extractMutableContentHeaders( $params['headers'] ?? [] ),
+ [
+ 'x-copy-from' => '/' . rawurlencode( $srcCont ) . '/' .
+ str_replace( "%2F", "/", rawurlencode( $srcRel ) )
+ ]
+ )
+ ] ];
if ( "{$srcCont}/{$srcRel}" !== "{$dstCont}/{$dstRel}" ) {
$reqs[] = [
'method' => 'DELETE',
} elseif ( $request['method'] === 'DELETE' && $rcode === 204 ) {
// good
} elseif ( $rcode === 404 ) {
- $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ } else {
+ // Leave Status as OK but skip the DELETE request
+ return SwiftFileOpHandle::CONTINUE_NO;
+ }
} else {
$this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
+
+ return SwiftFileOpHandle::CONTINUE_IF_OK;
};
$opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
} else {
$this->onError( $status, $method, $params, $rerr, $rcode, $rdesc );
}
+
+ return SwiftFileOpHandle::CONTINUE_IF_OK;
};
$opHandle = new SwiftFileOpHandle( $this, $handler, $reqs );
return $status;
}
- // POST clears prior headers, so we need to merge the changes in to the old ones
- $metaHdrs = [];
+ // Swift object POST clears any prior headers, so merge the new and old headers here.
+ // Also, during, POST, libcurl adds "Content-Type: application/x-www-form-urlencoded"
+ // if "Content-Type" is not set, which would clobber the header value for the object.
+ $oldMetadataHeaders = [];
foreach ( $stat['xattr']['metadata'] as $name => $value ) {
- $metaHdrs["x-object-meta-$name"] = $value;
+ $oldMetadataHeaders["x-object-meta-$name"] = $value;
}
- $customHdrs = $this->sanitizeHdrs( $params ) + $stat['xattr']['headers'];
+ $newContentHeaders = $this->extractMutableContentHeaders( $params['headers'] ?? [] );
+ $oldContentHeaders = $stat['xattr']['headers'];
$reqs = [ [
'method' => 'POST',
'url' => [ $srcCont, $srcRel ],
- 'headers' => $metaHdrs + $customHdrs
+ 'headers' => $oldMetadataHeaders + $newContentHeaders + $oldContentHeaders
] ];
$method = __METHOD__;
}
// Find prior custom HTTP headers
- $postHeaders = $this->getCustomHeaders( $objHdrs );
+ $postHeaders = $this->extractMutableContentHeaders( $objHdrs );
// Find prior metadata headers
- $postHeaders += $this->getMetadataHeaders( $objHdrs );
+ $postHeaders += $this->extractMetadataHeaders( $objHdrs );
$status = $this->newStatus();
/** @noinspection PhpUnusedLocalVariableInspection */
return $hdrs;
}
- /**
- * @param FileBackendStoreOpHandle[] $fileOpHandles
- *
- * @return StatusValue[]
- */
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
/** @var SwiftFileOpHandle[] $fileOpHandles */
'@phan-var SwiftFileOpHandle[] $fileOpHandles';
for ( $stage = 0; $stage < $reqCount; ++$stage ) {
$httpReqs = $this->http->runMulti( $httpReqsByStage[$stage] );
foreach ( $httpReqs as $index => $httpReq ) {
+ /** @var SwiftFileOpHandle $fileOpHandle */
+ $fileOpHandle = $fileOpHandles[$index];
// Run the callback for each request of this operation
- $callback = $fileOpHandles[$index]->callback;
- $callback( $httpReq, $statuses[$index] );
- // On failure, abort all remaining requests for this operation
- // (e.g. abort the DELETE request if the COPY request fails for a move)
- if ( !$statuses[$index]->isOK() ) {
- $stages = count( $fileOpHandles[$index]->httpOp );
+ $status = $statuses[$index];
+ ( $fileOpHandle->callback )( $httpReq, $status );
+ // On failure, abort all remaining requests for this operation. This is used
+ // in "move" operations to abort the DELETE request if the PUT request fails.
+ if (
+ !$status->isOK() ||
+ $fileOpHandle->state === $fileOpHandle::CONTINUE_NO
+ ) {
+ $stages = count( $fileOpHandle->httpOp );
for ( $s = ( $stage + 1 ); $s < $stages; ++$s ) {
unset( $httpReqsByStage[$s][$index] );
}
*/
protected function getStatFromHeaders( array $rhdrs ) {
// Fetch all of the custom metadata headers
- $metadata = $this->getMetadata( $rhdrs );
+ $metadata = $this->getMetadataFromHeaders( $rhdrs );
// Fetch all of the custom raw HTTP headers
- $headers = $this->sanitizeHdrs( [ 'headers' => $rhdrs ] );
+ $headers = $this->extractMutableContentHeaders( $rhdrs );
return [
// Convert various random Swift dates to TS_MW
* @see FileBackendStoreOpHandle
*/
class SwiftFileOpHandle extends FileBackendStoreOpHandle {
- /** @var array List of Requests for MultiHttpClient */
+ /** @var array[] List of HTTP request maps for MultiHttpClient */
public $httpOp;
- /** @var Closure */
+ /** @var Closure Function to run after each HTTP request finishes */
public $callback;
+ /** @var int Class CONTINUE_* constant */
+ public $state = self::CONTINUE_IF_OK;
+
+ /** @var int Continue with the next requests stages if no errors occured */
+ const CONTINUE_IF_OK = 0;
+ /** @var int Cancel the next requests stages */
+ const CONTINUE_NO = 1;
+
/**
+ * Construct a handle to be use with SwiftFileOpHandle::doExecuteOpHandlesInternal()
+ *
+ * The callback returns a class CONTINUE_* constant and takes the following parameters:
+ * - An HTTP request map array with 'response' filled
+ * - A StatusValue instance to be updated as needed
+ *
* @param SwiftFileBackend $backend
- * @param Closure $callback Function that takes (HTTP request array, status)
+ * @param Closure $callback
* @param array $httpOp MultiHttpClient op
*/
public function __construct( SwiftFileBackend $backend, Closure $callback, array $httpOp ) {
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* This class formats delete log entries.
* @param File|FSFile $image
* @param string $path
* @return DjVuImage
+ * @suppress PhanUndeclaredProperty Custom property
*/
function getDjVuImage( $image, $path ) {
if ( !$image ) {
* @param File $image
* @param bool $gettext DOCUMENT (Default: false)
* @return bool|SimpleXMLElement
+ * @suppress PhanUndeclaredProperty Custom property
*/
public function getMetaTree( $image, $gettext = false ) {
if ( $gettext && isset( $image->djvuTextTree ) ) {
*/
const DJVUTXT_MEMORY_LIMIT = 300000;
+ /** @var string */
+ private $mFilename;
+
/**
* @param string $filename The DjVu file name.
*/
Hooks::run( 'ArticleUndelete',
[ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
+
if ( $this->title->getNamespace() == NS_FILE ) {
- DeferredUpdates::addUpdate(
- new HTMLCacheUpdate( $this->title, 'imagelinks', 'file-restore' )
+ $job = HTMLCacheUpdateJob::newForBacklinks(
+ $this->title,
+ 'imagelinks',
+ [ 'causeAction' => 'file-restore' ]
);
+ JobQueueGroup::singleton()->lazyPush( $job );
}
}
if ( $this->mFile->exists() ) {
wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
- DeferredUpdates::addUpdate(
- new HTMLCacheUpdate( $this->mTitle, 'imagelinks', 'file-purge' )
+ $job = HTMLCacheUpdateJob::newForBacklinks(
+ $this->mTitle,
+ 'imagelinks',
+ [ 'causeAction' => 'file-purge' ]
);
+ JobQueueGroup::singleton()->lazyPush( $job );
} else {
wfDebug( 'ImagePage::doPurge no image for '
. $this->mFile->getName() . "; limiting purge to cache only\n" );
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
// Invalidate caches of articles which include this page
- DeferredUpdates::addUpdate(
- new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
+ $job = HTMLCacheUpdateJob::newForBacklinks(
+ $title,
+ 'templatelinks',
+ [ 'causeAction' => 'page-create' ]
);
+ JobQueueGroup::singleton()->lazyPush( $job );
if ( $title->getNamespace() == NS_CATEGORY ) {
// Load the Category object, which will schedule a job to create
// Images
if ( $title->getNamespace() == NS_FILE ) {
- DeferredUpdates::addUpdate(
- new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
+ $job = HTMLCacheUpdateJob::newForBacklinks(
+ $title,
+ 'imagelinks',
+ [ 'causeAction' => 'page-delete' ]
);
+ JobQueueGroup::singleton()->lazyPush( $job );
}
// User talk pages
$slotsChanged = null
) {
// TODO: move this into a PageEventEmitter service
-
- if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
+ $jobs = [];
+ if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
// Invalidate caches of articles which include this page.
// Only for the main slot, because only the main slot is transcluded.
// TODO: MCR: not true for TemplateStyles! [SlotHandler]
- DeferredUpdates::addUpdate(
- new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
+ $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+ $title,
+ 'templatelinks',
+ [ 'causeAction' => 'page-edit' ]
);
}
-
// Invalidate the caches of all pages which redirect here
- DeferredUpdates::addUpdate(
- new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
+ $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
+ $title,
+ 'redirect',
+ [ 'causeAction' => 'page-edit' ]
);
+ JobQueueGroup::singleton()->lazyPush( $jobs );
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
// Reattach all direct children of the `<svg>` root node to the `<g>` wrapper
while ( $root->firstChild ) {
$node = $root->firstChild;
+ // @phan-suppress-next-line PhanUndeclaredProperty False positive
if ( !$titleNode && $node->nodeType === XML_ELEMENT_NODE && $node->tagName === 'title' ) {
// Remember the first encountered `<title>` node
$titleNode = $node;
*/
use MediaWiki\Linker\LinkTarget;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Item class for a filearchive table row
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Item class for an oldimage table row
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Abstract base class for a list of deletable items. The list class
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Item class for a logging table row
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IDatabase;
/**
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Item class for a live revision table row
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IDatabase;
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IDatabase;
/**
* @ingroup RevisionDelete
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* General controller for RevDel, used by both SpecialRevisiondelete and
* @file
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Item class for a live revision table row
}
/**
- * Get (cheap to compute) information about change tags.
+ * Get information about change tags, without parsing messages, for getRcFiltersConfigSummary().
+ *
+ * Message contents are the raw values (->plain()), because parsing messages is expensive.
+ * Even though we're not parsing messages, building a data structure with the contents of
+ * hundreds of i18n messages is still not cheap (see T223260#5370610), so the result of this
+ * function is cached in WANCache for 24 hours.
*
* Returns an array of associative arrays with information about each tag:
* - name: Tag name (string)
* - labelMsg: Short description message (Message object)
+ * - label: Short description message (raw message contents)
* - descriptionMsg: Long description message (Message object)
+ * - description: Long description message (raw message contents)
* - cssClass: CSS class to use for RC entries with this tag
* - hits: Number of RC entries that have this tag
*
* @param ResourceLoaderContext $context
* @return array[] Information about each tag
*/
- protected static function getChangeTagInfo( ResourceLoaderContext $context ) {
- $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
- $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
-
- $tagStats = ChangeTags::tagUsageStatistics();
- $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
-
- $result = [];
- foreach ( $tagHitCounts as $tagName => $hits ) {
- if (
- (
- // Only get active tags
- isset( $explicitlyDefinedTags[ $tagName ] ) ||
- isset( $softwareActivatedTags[ $tagName ] )
- ) &&
- // Only get tags with more than 0 hits
- $hits > 0
- ) {
- $labelMsg = ChangeTags::tagShortDescriptionMessage( $tagName, $context );
- if ( $labelMsg === false ) {
- // Tag is hidden, skip it
- continue;
- }
- $result[] = [
- 'name' => $tagName,
- // 'label' and 'description' filled in by getChangeTagList()
- 'labelMsg' => $labelMsg,
- 'descriptionMsg' => ChangeTags::tagLongDescriptionMessage( $tagName, $context ),
- 'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
- 'hits' => $hits,
- ];
- }
- }
- return $result;
- }
-
- /**
- * Get information about change tags for use in getRcFiltersConfigSummary().
- *
- * This expands labelMsg and descriptionMsg to the raw values of each message, which captures
- * changes in the messages but avoids the expensive step of parsing them.
- *
- * @param ResourceLoaderContext $context
- * @return array[] Result of getChangeTagInfo(), with messages expanded to raw contents
- */
protected static function getChangeTagListSummary( ResourceLoaderContext $context ) {
- $tags = self::getChangeTagInfo( $context );
- foreach ( $tags as &$tagInfo ) {
- $tagInfo['labelMsg'] = $tagInfo['labelMsg']->plain();
- if ( $tagInfo['descriptionMsg'] ) {
- $tagInfo['descriptionMsg'] = $tagInfo['descriptionMsg']->plain();
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ return $cache->getWithSetCallback(
+ $cache->makeKey( 'ChangesListSpecialPage-changeTagListSummary', $context->getLanguage() ),
+ WANObjectCache::TTL_DAY,
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $context ) {
+ $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
+ $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
+
+ $tagStats = ChangeTags::tagUsageStatistics();
+ $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
+
+ $result = [];
+ foreach ( $tagHitCounts as $tagName => $hits ) {
+ if (
+ (
+ // Only get active tags
+ isset( $explicitlyDefinedTags[ $tagName ] ) ||
+ isset( $softwareActivatedTags[ $tagName ] )
+ ) &&
+ // Only get tags with more than 0 hits
+ $hits > 0
+ ) {
+ $labelMsg = ChangeTags::tagShortDescriptionMessage( $tagName, $context );
+ if ( $labelMsg === false ) {
+ // Tag is hidden, skip it
+ continue;
+ }
+ $descriptionMsg = ChangeTags::tagLongDescriptionMessage( $tagName, $context );
+ $result[] = [
+ 'name' => $tagName,
+ 'labelMsg' => $labelMsg,
+ 'label' => $labelMsg->plain(),
+ 'descriptionMsg' => $descriptionMsg,
+ 'description' => $descriptionMsg ? $descriptionMsg->plain() : '',
+ 'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
+ 'hits' => $hits,
+ ];
+ }
+ }
+ return $result;
}
- }
- return $tags;
+ );
}
/**
* Get information about change tags to export to JS via getRcFiltersConfigVars().
*
- * This removes labelMsg and descriptionMsg, and adds label and description, which are parsed,
- * stripped and (in the case of description) truncated versions of these messages. Message
+ * This manipulates the label and description of each tag, which are parsed, stripped
+ * and (in the case of description) truncated versions of these messages. Message
* parsing is expensive, so to detect whether the tag list has changed, use
* getChangeTagListSummary() instead.
*
+ * The result of this function is cached in WANCache for 24 hours.
+ *
* @param ResourceLoaderContext $context
- * @return array[] Result of getChangeTagInfo(), with messages parsed, stripped and truncated
+ * @return array[] Same as getChangeTagListSummary(), with messages parsed, stripped and truncated
*/
protected static function getChangeTagList( ResourceLoaderContext $context ) {
- $tags = self::getChangeTagInfo( $context );
+ $tags = self::getChangeTagListSummary( $context );
$language = Language::factory( $context->getLanguage() );
foreach ( $tags as &$tagInfo ) {
$tagInfo['label'] = Sanitizer::stripAllTags( $tagInfo['labelMsg']->parse() );
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
/**
* A special page that lists existing blocks
* @ingroup SpecialPage
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Special page allowing users with the appropriate permissions to
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Permissions\PermissionManager;
/**
$form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
$form->setAction( $this->getPageTitle( 'delete' )->getLocalURL() );
+ // @phan-suppress-next-line PhanUndeclaredProperty
$form->tagAction = 'delete'; // custom property on HTMLForm object
$form->setSubmitCallback( [ $this, 'processTagForm' ] );
$form->setSubmitTextMsg( 'tags-delete-submit' );
$form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
$form->setAction( $this->getPageTitle( $actionStr )->getLocalURL() );
+ // @phan-suppress-next-line PhanUndeclaredProperty
$form->tagAction = $actionStr;
$form->setSubmitCallback( [ $this, 'processTagForm' ] );
// tags-activate-submit, tags-deactivate-submit
$form->show();
}
+ /**
+ * @param array $data
+ * @param HTMLForm $form
+ * @return bool
+ * @suppress PhanUndeclaredProperty $form->tagAction
+ */
public function processTagForm( array $data, HTMLForm $form ) {
$context = $form->getContext();
$out = $context->getOutput();
function filterDataForSubmit( $data ) {
foreach ( $this->mFlatFields as $fieldname => $field ) {
if ( $field instanceof HTMLNestedFilterable ) {
+ // @phan-suppress-next-next-line PhanUndeclaredProperty All HTMLForm fields have mParams,
+ // but the instanceof confuses phan, which doesn't support intersections
$info = $field->mParams;
$prefix = $info['prefix'] ?? $fieldname;
foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Linker\LinkRenderer;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IDatabase;
*/
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
* @param LinkTarget $title
*
* @return string
+ * @suppress PhanUndeclaredProperty
*/
public function getPrefixedText( LinkTarget $title ) {
if ( !isset( $title->prefixedText ) ) {
* @return string|string[] An error or list of errors in the
* provided $datum. When no errors exist the empty array is
* returned.
- * @suppress PhanUndeclaredMethod
+ * @suppress PhanUndeclaredMethod,PhanUndeclaredProperty
*/
public static function getErrors( AvroSchema $schema, $datum ) {
switch ( $schema->type ) {
<?php
use MediaWiki\Linker\LinkTarget;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use MediaWiki\User\UserIdentity;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\IDatabase;
use MediaWiki\MediaWikiServices;
use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
/**
* Base class for language conversion.
'az' => 'azərbaycanca', # Azerbaijani
'azb' => 'تۆرکجه', # South Azerbaijani
'ba' => 'башҡортса', # Bashkir
- 'ban' => 'Basa Bali', # Balinese
+ 'ban' => 'Bali', # Balinese
'bar' => 'Boarisch', # Bavarian (Austro-Bavarian and South Tyrolean)
'bat-smg' => 'žemaitėška', # Samogitian (deprecated code, 'sgs' in ISO 639-3 since 2010-06-30 )
'bbc' => 'Batak Toba', # Batak Toba (falls back to bbc-latn)
require __DIR__ . '/../commandLine.inc';
+use Wikimedia\Rdbms\Database;
+
/**
* Maintenance script that upgrade for log_id/log_deleted fields in a
* replication-safe way.
* @ingroup Maintenance
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
require_once __DIR__ . '/Maintenance.php';
*
* @file
* @ingroup Maintenance
+ * @phan-file-suppress PhanUndeclaredProperty Lots of custom properties
*/
require_once __DIR__ . '/Maintenance.php';
* @ingroup Maintenance
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
require_once __DIR__ . '/Maintenance.php';
* @ingroup Maintenance
*/
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IDatabase;
require_once __DIR__ . '/Maintenance.php';
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
require_once __DIR__ . '/dumpIterator.php';
*/
use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use Wikimedia\Rdbms\IDatabase;
require_once __DIR__ . '/Maintenance.php';
*/
class UserDupes {
/**
- * @var Database
+ * @var IMaintainableDatabase
*/
private $db;
private $reassigned;
'targets' => [ 'mobile', 'desktop' ],
],
'jquery.checkboxShiftClick' => [
- 'scripts' => 'resources/src/jquery/jquery.checkboxShiftClick.js',
+ 'deprecated' => 'Please use "mediawiki.page.ready" instead.',
+ 'dependencies' => [
+ 'mediawiki.page.ready',
+ ],
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.chosen' => [
]
],
'mediawiki.page.ready' => [
- 'scripts' => 'resources/src/mediawiki.page.ready.js',
+ 'scripts' => [
+ 'resources/src/mediawiki.page.ready/checkboxShift.js',
+ 'resources/src/mediawiki.page.ready/ready.js',
+ ],
'dependencies' => [
- 'jquery.checkboxShiftClick',
'mediawiki.util',
'mediawiki.notify',
'mediawiki.api'
+++ /dev/null
-/**
- * @class jQuery.plugin.checkboxShiftClick
- */
-( function () {
-
- /**
- * Enable checkboxes to be checked or unchecked in a row by clicking one,
- * holding shift and clicking another one.
- *
- * @return {jQuery}
- * @chainable
- */
- $.fn.checkboxShiftClick = function () {
- var prevCheckbox = null,
- $box = this;
- // When our boxes are clicked..
- $box.on( 'click', function ( e ) {
- // And one has been clicked before...
- if ( prevCheckbox !== null && e.shiftKey ) {
- // Check or uncheck this one and all in-between checkboxes,
- // except for disabled ones
- $box
- .slice(
- Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ),
- Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1
- )
- .filter( function () {
- return !this.disabled;
- } )
- .prop( 'checked', !!e.target.checked );
- }
- // Either way, update the prevCheckbox variable to the one clicked now
- prevCheckbox = e.target;
- } );
- return $box;
- };
-
- /**
- * @class jQuery
- * @mixins jQuery.plugin.checkboxShiftClick
- */
-
-}() );
+++ /dev/null
-( function () {
- mw.hook( 'wikipage.content' ).add( function ( $content ) {
- var $sortable, $collapsible;
-
- $collapsible = $content.find( '.mw-collapsible' );
- if ( $collapsible.length ) {
- // Preloaded by Skin::getDefaultModules()
- mw.loader.using( 'jquery.makeCollapsible', function () {
- $collapsible.makeCollapsible();
- } );
- }
-
- $sortable = $content.find( 'table.sortable' );
- if ( $sortable.length ) {
- // Preloaded by Skin::getDefaultModules()
- mw.loader.using( 'jquery.tablesorter', function () {
- $sortable.tablesorter();
- } );
- }
-
- // Run jquery.checkboxShiftClick
- $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ).checkboxShiftClick();
- } );
-
- // Things outside the wikipage content
- $( function () {
- var $nodes;
-
- // Add accesskey hints to the tooltips
- $( '[accesskey]' ).updateTooltipAccessKeys();
-
- $nodes = $( '.catlinks[data-mw="interface"]' );
- if ( $nodes.length ) {
- /**
- * Fired when categories are being added to the DOM
- *
- * It is encouraged to fire it before the main DOM is changed (when $content
- * is still detached). However, this order is not defined either way, so you
- * should only rely on $content itself.
- *
- * This includes the ready event on a page load (including post-edit loads)
- * and when content has been previewed with LivePreview.
- *
- * @event wikipage_categories
- * @member mw.hook
- * @param {jQuery} $content The most appropriate element containing the content,
- * such as .catlinks
- */
- mw.hook( 'wikipage.categories' ).fire( $nodes );
- }
-
- $( '#t-print a' ).on( 'click', function ( e ) {
- window.print();
- e.preventDefault();
- } );
-
- // Turn logout to a POST action
- $( '#pt-logout a' ).on( 'click', function ( e ) {
- var api = new mw.Api(),
- returnUrl = $( '#pt-logout a' ).attr( 'href' );
- mw.notify(
- mw.message( 'logging-out-notify' ),
- { tag: 'logout', autoHide: false }
- );
- api.postWithToken( 'csrf', {
- action: 'logout'
- } ).then(
- function () {
- location.href = returnUrl;
- },
- function ( e ) {
- mw.notify(
- mw.message( 'logout-failed', e ),
- { type: 'error', tag: 'logout', autoHide: false }
- );
- }
- );
- e.preventDefault();
- } );
- } );
-
-}() );
--- /dev/null
+/**
+ * @class jQuery.plugin.checkboxShiftClick
+ */
+( function () {
+
+ /**
+ * Enable checkboxes to be checked or unchecked in a row by clicking one,
+ * holding shift and clicking another one.
+ *
+ * @return {jQuery}
+ * @chainable
+ */
+ $.fn.checkboxShiftClick = function () {
+ var prevCheckbox = null,
+ $box = this;
+ // When our boxes are clicked..
+ $box.on( 'click', function ( e ) {
+ // And one has been clicked before...
+ if ( prevCheckbox !== null && e.shiftKey ) {
+ // Check or uncheck this one and all in-between checkboxes,
+ // except for disabled ones
+ $box
+ .slice(
+ Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ),
+ Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1
+ )
+ .filter( function () {
+ return !this.disabled;
+ } )
+ .prop( 'checked', !!e.target.checked );
+ }
+ // Either way, update the prevCheckbox variable to the one clicked now
+ prevCheckbox = e.target;
+ } );
+ return $box;
+ };
+
+ /**
+ * @class jQuery
+ * @mixins jQuery.plugin.checkboxShiftClick
+ */
+
+}() );
--- /dev/null
+( function () {
+ mw.hook( 'wikipage.content' ).add( function ( $content ) {
+ var $sortable, $collapsible;
+
+ $collapsible = $content.find( '.mw-collapsible' );
+ if ( $collapsible.length ) {
+ // Preloaded by Skin::getDefaultModules()
+ mw.loader.using( 'jquery.makeCollapsible', function () {
+ $collapsible.makeCollapsible();
+ } );
+ }
+
+ $sortable = $content.find( 'table.sortable' );
+ if ( $sortable.length ) {
+ // Preloaded by Skin::getDefaultModules()
+ mw.loader.using( 'jquery.tablesorter', function () {
+ $sortable.tablesorter();
+ } );
+ }
+
+ // Run jquery.checkboxShiftClick
+ $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ).checkboxShiftClick();
+ } );
+
+ // Things outside the wikipage content
+ $( function () {
+ var $nodes;
+
+ // Add accesskey hints to the tooltips
+ $( '[accesskey]' ).updateTooltipAccessKeys();
+
+ $nodes = $( '.catlinks[data-mw="interface"]' );
+ if ( $nodes.length ) {
+ /**
+ * Fired when categories are being added to the DOM
+ *
+ * It is encouraged to fire it before the main DOM is changed (when $content
+ * is still detached). However, this order is not defined either way, so you
+ * should only rely on $content itself.
+ *
+ * This includes the ready event on a page load (including post-edit loads)
+ * and when content has been previewed with LivePreview.
+ *
+ * @event wikipage_categories
+ * @member mw.hook
+ * @param {jQuery} $content The most appropriate element containing the content,
+ * such as .catlinks
+ */
+ mw.hook( 'wikipage.categories' ).fire( $nodes );
+ }
+
+ $( '#t-print a' ).on( 'click', function ( e ) {
+ window.print();
+ e.preventDefault();
+ } );
+
+ // Turn logout to a POST action
+ $( '#pt-logout a' ).on( 'click', function ( e ) {
+ var api = new mw.Api(),
+ returnUrl = $( '#pt-logout a' ).attr( 'href' );
+ mw.notify(
+ mw.message( 'logging-out-notify' ),
+ { tag: 'logout', autoHide: false }
+ );
+ api.postWithToken( 'csrf', {
+ action: 'logout'
+ } ).then(
+ function () {
+ location.href = returnUrl;
+ },
+ function ( e ) {
+ mw.notify(
+ mw.message( 'logout-failed', e ),
+ { type: 'error', tag: 'logout', autoHide: false }
+ );
+ }
+ );
+ e.preventDefault();
+ } );
+ } );
+
+}() );
use ActorMigration;
use CommentStore;
-use MediaWiki\Logger\Spi as LoggerSpi;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\RevisionStoreFactory;
use MediaWiki\Revision\SlotRoleRegistry;
$this->getMockCommentStore(),
ActorMigration::newMigration(),
MIGRATION_OLD,
- $this->getMockLoggerSpi(),
+ new NullLogger(),
true
);
$this->assertTrue( true );
$cache = $this->getHashWANObjectCache();
$commentStore = $this->getMockCommentStore();
$actorMigration = ActorMigration::newMigration();
- $loggerProvider = $this->getMockLoggerSpi();
+ $logger = new NullLogger();
$factory = new RevisionStoreFactory(
$lbFactory,
$commentStore,
$actorMigration,
$mcrMigrationStage,
- $loggerProvider,
+ $logger,
$contentHandlerUseDb
);
return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
}
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|LoggerSpi
- */
- private function getMockLoggerSpi() {
- $mock = $this->getMock( LoggerSpi::class );
-
- $mock->method( 'getLogger' )
- ->willReturn( new NullLogger() );
-
- return $mock;
- }
-
}
);
$cacheKey = $cache->makeGlobalKey(
- 'BlobStore',
- 'address',
+ 'SqlBlobStore-blob',
$lb->getLocalDomainID(),
'tt:7777'
);
use MediaWiki\Block\SystemBlock;
use MediaWiki\MediaWikiServices;
use Wikimedia\TestingAccessWrapper;
+use Psr\Log\LoggerInterface;
/**
* @group Blocking
private function getBlockManagerConstructorArgs( $overrideConfig ) {
$blockManagerConfig = array_merge( $this->blockManagerConfig, $overrideConfig );
$this->setMwGlobals( $blockManagerConfig );
+ $logger = $this->getMockBuilder( LoggerInterface::class )->getMock();
return [
new LoggedServiceOptions(
self::$serviceOptionsAccessLog,
BlockManager::$constructorOptions,
MediaWikiServices::getInstance()->getMainConfig()
),
- MediaWikiServices::getInstance()->getPermissionManager()
+ MediaWikiServices::getInstance()->getPermissionManager(),
+ $logger
];
}
"$base/subdir2/subdir/sub/120-px-file.txt",
];
- for ( $i = 0; $i < 25; $i++ ) {
+ for ( $i = 0; $i < 2; $i++ ) {
$status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX );
$this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
"Locking of files succeeded ($backendName) ($i)." );
* @covers SwiftFileBackendList
*/
class SwiftFileBackendTest extends MediaWikiTestCase {
- /** @var TestingAccessWrapper Proxy to SwiftFileBackend */
+ /** @var TestingAccessWrapper|SwiftFileBackend */
private $backend;
protected function setUp() {
}
/**
- * @dataProvider provider_testSanitizeHdrsStrict
+ * @covers SwiftFileBackend::extractMutableContentHeaders
+ * @dataProvider provider_testExtractPostableContentHeaders
*/
- public function testSanitizeHdrsStrict( $raw, $sanitized ) {
- $hdrs = $this->backend->sanitizeHdrsStrict( [ 'headers' => $raw ] );
+ public function testExtractPostableContentHeaders( $raw, $sanitized ) {
+ $hdrs = $this->backend->extractMutableContentHeaders( $raw );
- $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrsStrict() has expected result' );
+ $this->assertEquals( $hdrs, $sanitized, 'Correct extractPostableContentHeaders() result' );
}
- public static function provider_testSanitizeHdrsStrict() {
+ public static function provider_testExtractPostableContentHeaders() {
return [
[
[
'content-length' => 345,
- 'content-type' => 'image+bitmap/jpeg',
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => 'inline',
'content-duration' => 35.6363,
'content-Custom' => 'hello',
'x-content-custom' => 'hello'
],
[
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => 'inline',
'content-duration' => 35.6363,
'content-custom' => 'hello',
[
[
'content-length' => 345,
- 'content-type' => 'image+bitmap/jpeg',
+ 'content-type' => 'image+bitmap/jpeg',
'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ),
'content-duration' => 35.6363,
'content-custom' => 'hello',
'x-content-custom' => 'hello'
],
[
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => 'inline;filename=xxx',
'content-duration' => 35.6363,
'content-custom' => 'hello',
[
[
'content-length' => 345,
- 'content-type' => 'image+bitmap/jpeg',
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => 'filename=' . str_repeat( 'o', 1024 ) . ';inline',
'content-duration' => 35.6363,
'content-custom' => 'hello',
'x-content-custom' => 'hello'
],
[
+ 'content-type' => 'image+bitmap/jpeg',
'content-disposition' => '',
'content-duration' => 35.6363,
'content-custom' => 'hello',
}
/**
- * @dataProvider provider_testSanitizeHdrs
- */
- public function testSanitizeHdrs( $raw, $sanitized ) {
- $hdrs = $this->backend->sanitizeHdrs( [ 'headers' => $raw ] );
-
- $this->assertEquals( $hdrs, $sanitized, 'sanitizeHdrs() has expected result' );
- }
-
- public static function provider_testSanitizeHdrs() {
- return [
- [
- [
- 'content-length' => 345,
- 'content-type' => 'image+bitmap/jpeg',
- 'content-disposition' => 'inline',
- 'content-duration' => 35.6363,
- 'content-Custom' => 'hello',
- 'x-content-custom' => 'hello'
- ],
- [
- 'content-type' => 'image+bitmap/jpeg',
- 'content-disposition' => 'inline',
- 'content-duration' => 35.6363,
- 'content-custom' => 'hello',
- 'x-content-custom' => 'hello'
- ]
- ],
- [
- [
- 'content-length' => 345,
- 'content-type' => 'image+bitmap/jpeg',
- 'content-Disposition' => 'inline; filename=xxx; ' . str_repeat( 'o', 1024 ),
- 'content-duration' => 35.6363,
- 'content-custom' => 'hello',
- 'x-content-custom' => 'hello'
- ],
- [
- 'content-type' => 'image+bitmap/jpeg',
- 'content-disposition' => 'inline;filename=xxx',
- 'content-duration' => 35.6363,
- 'content-custom' => 'hello',
- 'x-content-custom' => 'hello'
- ]
- ],
- [
- [
- 'content-length' => 345,
- 'content-type' => 'image+bitmap/jpeg',
- 'content-disposition' => 'filename=' . str_repeat( 'o', 1024 ) . ';inline',
- 'content-duration' => 35.6363,
- 'content-custom' => 'hello',
- 'x-content-custom' => 'hello'
- ],
- [
- 'content-type' => 'image+bitmap/jpeg',
- 'content-disposition' => '',
- 'content-duration' => 35.6363,
- 'content-custom' => 'hello',
- 'x-content-custom' => 'hello'
- ]
- ]
- ];
- }
-
- /**
+ * @covers SwiftFileBackend::extractMetadataHeaders
* @dataProvider provider_testGetMetadataHeaders
*/
public function testGetMetadataHeaders( $raw, $sanitized ) {
- $hdrs = $this->backend->getMetadataHeaders( $raw );
+ $hdrs = $this->backend->extractMetadataHeaders( $raw );
$this->assertEquals( $hdrs, $sanitized, 'getMetadataHeaders() has expected result' );
}
}
/**
+ * @covers SwiftFileBackend::getMetadataFromHeaders
* @dataProvider provider_testGetMetadata
*/
public function testGetMetadata( $raw, $sanitized ) {
- $hdrs = $this->backend->getMetadata( $raw );
+ $hdrs = $this->backend->getMetadataFromHeaders( $raw );
$this->assertEquals( $hdrs, $sanitized, 'getMetadata() has expected result' );
}
use ContentHandler;
use FetchText;
-use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
use MediaWikiTestCase;
use MWException;
use Title;
* /w/images/thumb/a/ab/Foo.png/120px-Foo.png. The $thumbRel parameter
* of this function would be set to "a/ab/Foo.png/120px-Foo.png".
* This method is responsible for turning that into an array
- * with the folowing keys:
+ * with the following keys:
* * f => the filename (Foo.png)
* * rel404 => the whole thing (a/ab/Foo.png/120px-Foo.png)
* * archived => 1 (If the request is for an archived thumb)