deprecated in 1.25, has been removed.
* (T60993) action=query list=filearchive, list=alldeletedrevisions and
prop=deletedrevisions no longer require the 'deletedhistory' user right.
+* In the response to queries that use 'prop=imageinfo', entries for
+ non-existing files (indicated by the 'filemissing' field) now omit the
+ following fields, since they are meaningless in this context:
+ 'timestamp', 'userhidden', 'user', 'userid', 'anon', 'size', 'width',
+ 'height', 'pagecount', 'duration', 'commenthidden', 'parsedcomment',
+ 'comment', 'thumburl', 'thumbwidth', 'thumbheight', 'thumbmime',
+ 'thumberror', 'url', 'sha1', 'metadata', 'extmetadata', 'commonmetadata',
+ 'mime', 'mediadtype', 'bitdepth'.
+ Clients that process these fields should first check if 'filemissing' is
+ set. Fields that are supported even if the file is missing include:
+ 'canonicaltitle', ''archivename' (deleted files only), 'descriptionurl',
+ 'descriptionshorturl'.
+* The 'blockexpiry' result property in list=users and list=allusers will now be
+ returned in the same format used by the rest of the API: ISO 8601 for
+ expiring blocks, and "infinite" for non-expiring blocks.
=== Action API internal changes in 1.34 ===
* The exception thrown in ApiModuleManager::getModule has been changed
ApiModuleManager::getModule now also throws InvalidArgumentExceptions when
ObjectFactory is presented with an invalid spec or incorrectly constructed
objects.
-* …
+* Added ApiQueryBlockInfoTrait.
=== Languages updated in 1.34 ===
MediaWiki supports over 350 languages. Many localisations are updated regularly.
* The jquery.colorUtil module was removed. Use jquery.color instead.
* The jquery.checkboxShiftClick module was removed. The functionality
is provided by mediawiki.page.ready instead (T232688).
+* The 'jquery.accessKeyLabel' module has been removed. This jQuery
+ plugin now ships as part of the 'mediawiki.util' module bundle.
* EditPage::submit(), deprecated in 1.29, has been removed. Use $this->edit()
directly.
* HTMLForm::getErrors(), deprecated in 1.28, has been removed. Use
* ResourceLoaderContext::getConfig and ResourceLoaderContext::getLogger have
been deprecated. Inside ResourceLoaderModule subclasses, use the local methods
instead. Elsewhere, use the methods from the ResourceLoader class.
-* The 'jquery.accessKeyLabel' module has been deprecated. This jQuery
- plugin is now ships as part of the 'mediawiki.util' module bundle.
* The Profiler::setTemplated and Profiler::getTemplated methods have been
deprecated. Use Profiler::setAllowOutput and Profiler::getAllowOutput
instead.
* Global variable $wgSysopEmailBans is deprecated; to allow sysops to ban
users from sending emails, use
$wgGroupPermissions['sysop']['blockemail'] = true;
+* ApiQueryBase::showHiddenUsersAddBlockInfo() is deprecated. Use
+ ApiQueryBlockInfoTrait instead.
=== Other changes in 1.34 ===
* …
'ApiQueryBacklinks' => __DIR__ . '/includes/api/ApiQueryBacklinks.php',
'ApiQueryBacklinksprop' => __DIR__ . '/includes/api/ApiQueryBacklinksprop.php',
'ApiQueryBase' => __DIR__ . '/includes/api/ApiQueryBase.php',
+ 'ApiQueryBlockInfoTrait' => __DIR__ . '/includes/api/ApiQueryBlockInfoTrait.php',
'ApiQueryBlocks' => __DIR__ . '/includes/api/ApiQueryBlocks.php',
'ApiQueryCategories' => __DIR__ . '/includes/api/ApiQueryCategories.php',
'ApiQueryCategoryInfo' => __DIR__ . '/includes/api/ApiQueryCategoryInfo.php',
global $wgDevelopmentWarnings, $wgShowExceptionDetails, $wgShowHostnames,
$wgDebugRawPage, $wgCommandLineMode, $wgDebugLogFile,
- $wgDBerrorLog, $wgDebugLogGroups;
+ $wgDBerrorLog, $wgDebugLogGroups, $wgLocalisationCacheConf;
// Use of wfWarn() should cause tests to fail
$wgDevelopmentWarnings = true;
// Disable legacy javascript globals in CI and for devs (T72470)
$wgLegacyJavaScriptGlobals = false;
+
+// Localisation Cache to StaticArray (T218207)
+$wgLocalisationCacheConf['store'] = 'array';
namespace MediaWiki\Rest\BasicAccess;
-use User;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\RequestInterface;
+use MediaWiki\User\UserIdentity;
/**
- * A factory for MWBasicRequestAuthorizer which passes through a User object
+ * A factory for MWBasicRequestAuthorizer which passes through a UserIdentity.
*
* @internal
*/
class MWBasicAuthorizer extends BasicAuthorizerBase {
- /** @var User */
+ /** @var UserIdentity */
private $user;
/** @var PermissionManager */
private $permissionManager;
- public function __construct( User $user, PermissionManager $permissionManager ) {
+ public function __construct( UserIdentity $user, PermissionManager $permissionManager ) {
$this->user = $user;
$this->permissionManager = $permissionManager;
}
namespace MediaWiki\Rest\BasicAccess;
-use User;
+use MediaWiki\User\UserIdentity;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\RequestInterface;
* @internal
*/
class MWBasicRequestAuthorizer extends BasicRequestAuthorizer {
- /** @var User */
+ /** @var UserIdentity */
private $user;
/** @var PermissionManager */
private $permissionManager;
public function __construct( RequestInterface $request, Handler $handler,
- User $user, PermissionManager $permissionManager
+ UserIdentity $user, PermissionManager $permissionManager
) {
parent::__construct( $request, $handler );
$this->user = $user;
$services->getPermissionManager() );
// @phan-suppress-next-line PhanAccessMethodInternal
- $restValidator = new Validator( $objectFactory, $request, RequestContext::getMain()->getUser() );
+ $restValidator = new Validator( $objectFactory,
+ $services->getPermissionManager(),
+ $request,
+ RequestContext::getMain()->getUser()
+ );
global $IP;
$router = new Router(
namespace MediaWiki\Rest\Validator;
use InvalidArgumentException;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\RequestInterface;
+use MediaWiki\User\UserIdentity;
use Psr\Http\Message\UploadedFileInterface;
-use User;
use Wikimedia\ParamValidator\Callbacks;
use Wikimedia\ParamValidator\ValidationException;
class ParamValidatorCallbacks implements Callbacks {
+ /** @var PermissionManager */
+ private $permissionManager;
+
/** @var RequestInterface */
private $request;
- /** @var User */
+ /** @var UserIdentity */
private $user;
- public function __construct( RequestInterface $request, User $user ) {
+ public function __construct(
+ PermissionManager $permissionManager,
+ RequestInterface $request,
+ UserIdentity $user
+ ) {
+ $this->permissionManager = $permissionManager;
$this->request = $request;
$this->user = $user;
}
}
public function useHighLimits( array $options ) {
- return $this->user->isAllowed( 'apihighlimits' );
+ return $this->permissionManager->userHasRight( $this->user, 'apihighlimits' );
}
}
namespace MediaWiki\Rest\Validator;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\HttpException;
use MediaWiki\Rest\RequestInterface;
-use User;
+use MediaWiki\User\UserIdentity;
use Wikimedia\ObjectFactory;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef\BooleanDef;
private $paramValidator;
/**
- * @internal
* @param ObjectFactory $objectFactory
+ * @param PermissionManager $permissionManager
* @param RequestInterface $request
- * @param User $user
+ * @param UserIdentity $user
+ * @internal
*/
public function __construct(
- ObjectFactory $objectFactory, RequestInterface $request, User $user
+ ObjectFactory $objectFactory,
+ PermissionManager $permissionManager,
+ RequestInterface $request,
+ UserIdentity $user
) {
$this->paramValidator = new ParamValidator(
- new ParamValidatorCallbacks( $request, $user ),
+ new ParamValidatorCallbacks( $permissionManager, $request, $user ),
$objectFactory,
[
'typeDefs' => self::$typeDefs,
* @file
*/
+use MediaWiki\Block\DatabaseBlock;
+
/**
* Query module to enumerate all registered users.
*
* @ingroup API
*/
class ApiQueryAllUsers extends ApiQueryBase {
+ use ApiQueryBlockInfoTrait;
+
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'au' );
}
$this->addWhere( 'user_editcount > 0' );
}
- $this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
+ $this->addBlockInfoToQuery( $fld_blockinfo );
if ( $fld_groups || $fld_rights ) {
$this->addFields( [ 'groups' =>
);
}
- if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) {
- $data['blockid'] = (int)$row->ipb_id;
- $data['blockedby'] = $row->ipb_by_text;
- $data['blockedbyid'] = (int)$row->ipb_by;
- $data['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
- $data['blockreason'] = $commentStore->getComment( 'ipb_reason', $row )->text;
- $data['blockexpiry'] = $row->ipb_expiry;
+ if ( $fld_blockinfo && !is_null( $row->ipb_id ) ) {
+ $data += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
}
if ( $row->ipb_deleted ) {
$data['hidden'] = true;
* @ingroup API
*/
abstract class ApiQueryBase extends ApiBase {
+ use ApiQueryBlockInfoTrait;
private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
return Hooks::run( 'ApiQueryBaseProcessRow', [ $this, $row, &$data, &$hookData ] );
}
- /**
- * Filters hidden users (where the user doesn't have the right to view them)
- * Also adds relevant block information
- *
- * @param bool $showBlockInfo
- * @return void
- */
- public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
- $db = $this->getDB();
-
- $tables = [ 'ipblocks' ];
- $fields = [ 'ipb_deleted' ];
- $joinConds = [
- 'blk' => [ 'LEFT JOIN', [
- 'ipb_user=user_id',
- 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() ),
- ] ],
- ];
-
- if ( $showBlockInfo ) {
- $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
- $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
- $tables += $actorQuery['tables'] + $commentQuery['tables'];
- $joinConds += $actorQuery['joins'] + $commentQuery['joins'];
- $fields = array_merge( $fields, [
- 'ipb_id',
- 'ipb_expiry',
- 'ipb_timestamp'
- ], $actorQuery['fields'], $commentQuery['fields'] );
- }
-
- $this->addTables( [ 'blk' => $tables ] );
- $this->addFields( $fields );
- $this->addJoinConds( $joinConds );
-
- // Don't show hidden names
- if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'hideuser' ) ) {
- $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
- }
- }
-
/** @} */
/************************************************************************//**
}
/** @} */
+
+ /************************************************************************//**
+ * @name Deprecated methods
+ * @{
+ */
+
+ /**
+ * Filters hidden users (where the user doesn't have the right to view them)
+ * Also adds relevant block information
+ *
+ * @deprecated since 1.34, use ApiQueryBlockInfoTrait instead
+ * @param bool $showBlockInfo
+ * @return void
+ */
+ public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
+ wfDeprecated( __METHOD__, '1.34' );
+ return $this->addBlockInfoToQuery( $showBlockInfo );
+ }
+
+ /** @} */
}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\Permissions\PermissionManager;
+
+/**
+ * @ingroup API
+ */
+trait ApiQueryBlockInfoTrait {
+ use ApiBlockInfoTrait;
+
+ /**
+ * Filters hidden users (where the user doesn't have the right to view them)
+ * Also adds relevant block information
+ *
+ * @param bool $showBlockInfo
+ * @return void
+ */
+ private function addBlockInfoToQuery( $showBlockInfo ) {
+ $db = $this->getDB();
+
+ if ( $showBlockInfo ) {
+ $queryInfo = DatabaseBlock::getQueryInfo();
+ } else {
+ $queryInfo = [
+ 'tables' => [ 'ipblocks' ],
+ 'fields' => [ 'ipb_deleted' ],
+ 'joins' => [],
+ ];
+ }
+
+ $this->addTables( [ 'blk' => $queryInfo['tables'] ] );
+ $this->addFields( $queryInfo['fields'] );
+ $this->addJoinConds( $queryInfo['joins'] );
+ $this->addJoinConds( [
+ 'blk' => [ 'LEFT JOIN', [
+ 'ipb_user=user_id',
+ 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() ),
+ ] ],
+ ] );
+
+ // Don't show hidden names
+ if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'hideuser' ) ) {
+ $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
+ }
+ }
+
+ /**
+ * @name Methods required from ApiQueryBase
+ * @{
+ */
+
+ /** @see ApiBase::getDB */
+ abstract protected function getDB();
+
+ /** @see ApiBase::getPermissionManager */
+ abstract protected function getPermissionManager(): PermissionManager;
+
+ /** @see IContextSource::getUser */
+ abstract public function getUser();
+
+ /** @see ApiQueryBase::addTables */
+ abstract protected function addTables( $tables, $alias = null );
+
+ /** @see ApiQueryBase::addFields */
+ abstract protected function addFields( $fields );
+
+ /** @see ApiQueryBase::addWhere */
+ abstract protected function addWhere( $conds );
+
+ /** @see ApiQueryBase::addJoinConds */
+ abstract protected function addJoinConds( $conds );
+
+ /**@}*/
+
+}
$vals = [
ApiResult::META_TYPE => 'assoc',
];
+
+ // Some information will be unavailable if the file does not exist. T221812
+ $exists = $file->exists();
+
// Timestamp is shown even if the file is revdelete'd in interface
// so do same here.
- if ( isset( $prop['timestamp'] ) ) {
+ if ( isset( $prop['timestamp'] ) && $exists ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
}
$user = isset( $prop['user'] );
$userid = isset( $prop['userid'] );
- if ( $user || $userid ) {
+ if ( ( $user || $userid ) && $exists ) {
if ( $file->isDeleted( File::DELETED_USER ) ) {
$vals['userhidden'] = true;
$anyHidden = true;
// This is shown even if the file is revdelete'd in interface
// so do same here.
- if ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) {
+ if ( ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) && $exists ) {
$vals['size'] = (int)$file->getSize();
$vals['width'] = (int)$file->getWidth();
$vals['height'] = (int)$file->getHeight();
$pcomment = isset( $prop['parsedcomment'] );
$comment = isset( $prop['comment'] );
- if ( $pcomment || $comment ) {
+ if ( ( $pcomment || $comment ) && $exists ) {
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
$vals['commenthidden'] = true;
$anyHidden = true;
}
if ( $url ) {
- if ( $file->exists() ) {
+ if ( $exists ) {
if ( !is_null( $thumbParams ) ) {
$mto = $file->transform( $thumbParams );
self::$transformCount++;
}
}
$vals['url'] = wfExpandUrl( $file->getFullUrl(), PROTO_CURRENT );
- } else {
- $vals['filemissing'] = true;
}
$vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl(), PROTO_CURRENT );
}
}
- if ( $sha1 ) {
+ if ( !$exists ) {
+ $vals['filemissing'] = true;
+ }
+
+ if ( $sha1 && $exists ) {
$vals['sha1'] = Wikimedia\base_convert( $file->getSha1(), 36, 16, 40 );
}
- if ( $meta ) {
+ if ( $meta && $exists ) {
Wikimedia\suppressWarnings();
$metadata = unserialize( $file->getMetadata() );
Wikimedia\restoreWarnings();
}
$vals['metadata'] = $metadata ? static::processMetaData( $metadata, $result ) : null;
}
- if ( $commonmeta ) {
+ if ( $commonmeta && $exists ) {
$metaArray = $file->getCommonMetaArray();
$vals['commonmetadata'] = $metaArray ? static::processMetaData( $metaArray, $result ) : [];
}
- if ( $extmetadata ) {
+ if ( $extmetadata && $exists ) {
// Note, this should return an array where all the keys
// start with a letter, and all the values are strings.
// Thus there should be no issue with format=xml.
$vals['extmetadata'] = $extmetaArray;
}
- if ( $mime ) {
+ if ( $mime && $exists ) {
$vals['mime'] = $file->getMimeType();
}
- if ( $mediatype ) {
+ if ( $mediatype && $exists ) {
$vals['mediatype'] = $file->getMediaType();
}
$vals['archivename'] = $file->getArchiveName();
}
- if ( $bitdepth ) {
+ if ( $bitdepth && $exists ) {
$vals['bitdepth'] = $file->getBitDepth();
}
* @file
*/
+use MediaWiki\Block\DatabaseBlock;
+
/**
* Query module to get information about a list of users
*
* @ingroup API
*/
class ApiQueryUsers extends ApiQueryBase {
+ use ApiQueryBlockInfoTrait;
private $tokenFunctions, $prop;
$this->addWhereFld( 'user_id', $userids );
}
- $this->showHiddenUsersAddBlockInfo( isset( $this->prop['blockinfo'] ) );
+ $this->addBlockInfoToQuery( isset( $this->prop['blockinfo'] ) );
$data = [];
$res = $this->select( __METHOD__ );
$data[$key]['hidden'] = true;
}
if ( isset( $this->prop['blockinfo'] ) && !is_null( $row->ipb_by_text ) ) {
- $data[$key]['blockid'] = (int)$row->ipb_id;
- $data[$key]['blockedby'] = $row->ipb_by_text;
- $data[$key]['blockedbyid'] = (int)$row->ipb_by;
- $data[$key]['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
- $data[$key]['blockreason'] = $commentStore->getComment( 'ipb_reason', $row )
- ->text;
- $data[$key]['blockexpiry'] = $row->ipb_expiry;
+ $data[$key] += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
}
if ( isset( $this->prop['emailable'] ) ) {
* Get the URL of the image description page. May return false if it is
* unknown or not applicable.
*
- * @return string
+ * @return string|bool
*/
function getDescriptionUrl() {
if ( $this->repo ) {
* This covers fields that are sometimes not cached.
*/
protected function loadExtraFromDB() {
+ if ( !$this->title ) {
+ return; // Avoid hard failure when the file does not exist. T221812
+ }
+
$fname = static::class . '::' . __FUNCTION__;
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
function getUser( $type = 'text' ) {
$this->load();
- if ( $type === 'object' ) {
- return $this->user;
- } elseif ( $type === 'text' ) {
- return $this->user->getName();
- } elseif ( $type === 'id' ) {
- return $this->user->getId();
+ if ( !$this->user ) {
+ // If the file does not exist, $this->user will be null, see T221812.
+ // Note: 'Unknown user' this is a reserved user name.
+ if ( $type === 'object' ) {
+ return User::newFromName( 'Unknown user', false );
+ } elseif ( $type === 'text' ) {
+ return 'Unknown user';
+ } elseif ( $type === 'id' ) {
+ return 0;
+ }
+ } else {
+ if ( $type === 'object' ) {
+ return $this->user;
+ } elseif ( $type === 'text' ) {
+ return $this->user->getName();
+ } elseif ( $type === 'id' ) {
+ return $this->user->getId();
+ }
}
throw new MWException( "Unknown type '$type'." );
* @since 1.27
*/
public function getDescriptionShortUrl() {
+ if ( !$this->title ) {
+ return null; // Avoid hard failure when the file does not exist. T221812
+ }
+
$pageId = $this->title->getArticleID();
- if ( $pageId !== null ) {
+ if ( $pageId ) {
$url = $this->repo->makeUrl( [ 'curid' => $pageId ] );
if ( $url !== false ) {
return $url;
* @return OldLocalFile[]
*/
function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
+ if ( !$this->exists() ) {
+ return []; // Avoid hard failure when the file does not exist. T221812
+ }
+
$dbr = $this->repo->getReplicaDB();
$oldFileQuery = OldLocalFile::getQueryInfo();
* 0 return line for current version
* 1 query for old versions, return first one
* 2, ... return next old version from above query
- * @return bool
+ * @return stdClass|bool
*/
public function nextHistoryLine() {
+ if ( !$this->exists() ) {
+ return false; // Avoid hard failure when the file does not exist. T221812
+ }
+
# Polymorphic function name to distinguish foreign and local fetches
$fname = static::class . '::' . __FUNCTION__;
/**
* Get the URL of the file description page.
- * @return string
+ * @return string|bool
*/
function getDescriptionUrl() {
+ if ( !$this->title ) {
+ return false; // Avoid hard failure when the file does not exist. T221812
+ }
+
return $this->title->getLocalURL();
}
* @return string|false
*/
function getDescriptionText( Language $lang = null ) {
+ if ( !$this->title ) {
+ return false; // Avoid hard failure when the file does not exist. T221812
+ }
+
$store = MediaWikiServices::getInstance()->getRevisionStore();
$revision = $store->getRevisionByTitle( $this->title, 0, Revision::READ_NORMAL );
if ( !$revision ) {
* @return bool|string
*/
public function getDescriptionTouched() {
+ if ( !$this->exists() ) {
+ return false; // Avoid hard failure when the file does not exist. T221812
+ }
+
// The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
// itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
// need to differentiate between null (uninitialized) and false (failed to load).
*/
private function determineKeyClassForStats( $key ) {
$parts = explode( ':', $key, 3 );
-
- return $parts[1] ?? $parts[0]; // sanity
+ // Sanity fallback in case the key was not made by makeKey.
+ // Replace dots because they are special in StatsD (T232907)
+ return strtr( $parts[1] ?? $parts[0], '.', '_' );
}
/**
$conn = Database::factory( $type, $info );
$conn->clearFlag( DBO_TRX ); // auto-commit mode
$this->conns[$shardIndex] = $conn;
+ // Automatically create the objectcache table for sqlite as needed
+ if ( $conn->getType() === 'sqlite' ) {
+ $this->initSqliteDatabase( $conn );
+ }
}
$conn = $this->conns[$shardIndex];
} else {
$attribs = $lb->getServerAttributes( $lb->getWriterIndex() );
$flags = $attribs[Database::ATTR_DB_LEVEL_LOCKING] ? 0 : $lb::CONN_TRX_AUTOCOMMIT;
$conn = $lb->getMaintenanceConnectionRef( $index, [], false, $flags );
- // Automatically create the objectcache table for sqlite as needed
- if ( $conn->getType() === 'sqlite' ) {
- $this->initSqliteDatabase( $conn );
- }
}
$this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $conn ) );
$search = $this->getRequest()->getText( 'ilsearch', '' );
$showAll = $this->getRequest()->getBool( 'ilshowall', false );
}
+ if ( $userName ) {
+ $pageTitle = $this->msg( 'listfiles_subpage', $userName );
+ } else {
+ $pageTitle = $this->msg( 'listfiles' );
+ }
$pager = new ImageListPager(
$this->getContext(),
);
$out = $this->getOutput();
+ $out->setPageTitle( $pageTitle );
+ $out->addModuleStyles( 'mediawiki.special' );
if ( $this->including() ) {
$out->addParserOutputContent( $pager->getBodyOutput() );
} else {
"resettokens-watchlist-token": "Token for the web feed (Atom/RSS) of [[Special:Watchlist|changes to pages on your watchlist]]",
"resettokens-done": "Tokens reset.",
"resettokens-resetbutton": "Reset selected tokens",
- "bold_sample": "Bold text",
- "bold_tip": "Bold text",
- "italic_sample": "Italic text",
- "italic_tip": "Italic text",
- "link_sample": "Link title",
- "link_tip": "Internal link",
- "extlink_sample": "http://www.example.com link title",
- "extlink_tip": "External link (remember http:// prefix)",
- "headline_sample": "Headline text",
- "headline_tip": "Level 2 headline",
- "nowiki_sample": "Insert non-formatted text here",
- "nowiki_tip": "Ignore wiki formatting",
- "image_sample": "Example.jpg",
- "image_tip": "Embedded file",
- "media_sample": "Example.ogg",
- "media_tip": "File link",
"sig-text": "--$1",
- "sig_tip": "Your signature with timestamp",
- "hr_tip": "Horizontal line (use sparingly)",
"summary": "Summary:",
"subject": "Subject:",
"minoredit": "This is a minor edit",
"listfiles-userdoesnotexist": "User account \"$1\" is not registered.",
"imgfile": "file",
"listfiles": "File list",
+ "listfiles_subpage": "Uploads by $1",
"listfiles_thumb": "Thumbnail",
"listfiles_date": "Date",
"listfiles_name": "Name",
"resettokens-watchlist-token": "Label for watchlist token checkbox on [[Special:ResetTokens]] (see {{msg-mw|prefs-watchlist-token}} at [[Special:Preferences#mw-prefsection-watchlist]]).",
"resettokens-done": "Message shown on [[Special:ResetTokens]] after the tokens have been reset successfully.",
"resettokens-resetbutton": "Form submit button on [[Special:ResetTokens]].",
- "bold_sample": "This is the sample text that you get when you press the first button on the left on the edit toolbar.\n\n{{Identical|Bold text}}",
- "bold_tip": "This is the text that appears when you hover the mouse over the first button on the left of the edit toolbar.\n\n{{Identical|Bold text}}",
- "italic_sample": "The sample text that you get when you press the second button from the left on the edit toolbar.\n\n{{Identical|Italic text}}",
- "italic_tip": "This is the tooltip that appears when the user points to the \"Italic\" button in the edit toolbar.\n\n{{Identical|Italic text}}",
- "link_sample": "This is the default text in the internal link that is created when you press the third button from the left on the edit toolbar (the \"Ab\" icon).",
- "link_tip": "Tip for internal links.\n{{Identical|Internal link}}",
- "extlink_sample": "This message appears when clicking on the fourth button of the edit toolbar. You can translate \"link title\". Because many of the localisations had urls that went to domains reserved for advertising, it is recommended that the link is left as-is. All customised links were replaced with the standard one, that is reserved in the standard and will never have ads or something.",
- "extlink_tip": "This is the tip that appears when you hover the mouse over the fourth button from the left on the edit toolbar.\n\n{{Identical|External link (remember http:// prefix)}}",
- "headline_sample": "Sample of headline text.",
- "headline_tip": "This is the text that appears when you hover the mouse over the fifth button from the left on the edit toolbar.",
- "nowiki_sample": "Text inserted between nowiki tags",
- "nowiki_tip": "This is the text that appears when you hover the mouse over the third button from the right on the edit toolbar.",
- "image_sample": "{{optional}}\nUsed in text generated by Picture button in toolbar.\n{{Identical|Example}}",
- "image_tip": "This is the text that appears when you hover the mouse over the sixth (middle) button on the edit toolbar.\n\n{{Identical|Embedded file}}",
- "media_sample": "{{optional}}\n{{Identical|Example}}",
- "media_tip": "This is the text that appears when you hover the mouse over the fifth button from the right in the edit toolbar.\n{{Identical|File link}}",
"sig-text": "{{notranslate}} This is the text that appears when you click on the signature button (second button from the right) on the edit toolbar. $1 will be replaced with four tildes (which cannot be included directly in the message for technical reasons).",
- "sig_tip": "This is the text that appears when you hover the mouse over the second key from the right on the edit toolbar.\n{{Identical|Signature with timestamp}}",
- "hr_tip": "This is the text that appears when you hover the mouse over the first button on the right on the edit toolbar.",
"summary": "The Summary text beside the edit summary field\n\nSee also:\n* {{msg-mw|Subject}}\nSee also:\n* {{msg-mw|Accesskey-summary}}\n* {{msg-mw|Tooltip-summary}}\n{{Identical|Summary}}",
"subject": "Used as label for the section title input box when adding a new section on a talk page.\n\nSee also:\n* {{msg-mw|Summary}}\n{{Identical|Subject}}",
"minoredit": "Text above Save page button in editor\n\nSee also:\n* {{msg-mw|Minoredit}}\n* {{msg-mw|Accesskey-minoredit}}\n* {{msg-mw|Tooltip-minoredit}}",
"listfiles-userdoesnotexist": "This message is displayed on [[Special:ListFiles]] when a invalid username is entered.",
"imgfile": "{{Identical|File}}",
"listfiles": "Page title and grouping label for the form displayed on [[Special:ListFiles]].\n{{Identical|List}}",
+ "listfiles_subpage": "Page title and grouping label for the form displayed on [[Special:ListFiles]].\n{{Identical|List}} when a username is selected. Parameters:\n * $1 - username",
"listfiles_thumb": "{{Identical|Thumbnail}}",
"listfiles_date": "Column header for the result table displayed on [[Special:ListFiles]].\n{{Identical|Date}}",
"listfiles_name": "Column header for the result table displayed on [[Special:ListFiles]].\n{{Identical|Name}}",
AdminSettings.php \
.svn \
*/.git/* \
+ */README.md \
{{EXCLUDE_PATTERNS}}
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
$this->addOption( 'output',
'Path to write doc to',
false, true );
- $this->addOption( 'no-extensions',
- 'Ignore extensions' );
+ $this->addOption( 'extensions',
+ 'Process the extensions/ directory as well (ignored if --file is used)' );
+ $this->addOption( 'skins',
+ 'Process the skins/ directory as well (ignored if --file is used)' );
}
public function getDbType() {
$this->template = $IP . '/maintenance/Doxyfile';
$this->excludes = [
- 'vendor',
- 'node_modules',
- 'resources/lib',
'images',
+ 'node_modules',
+ 'resources',
'static',
'tests',
- 'includes/libs/Message/README.md',
- 'includes/libs/objectcache/README.md',
- 'includes/libs/ParamValidator/README.md',
- 'maintenance/benchmarks/README.md',
- 'resources/src/mediawiki.ui/styleguide.md',
+ 'vendor',
];
$this->excludePatterns = [];
- if ( $this->hasOption( 'no-extensions' ) ) {
- $this->excludePatterns[] = 'extensions';
- $this->excludePatterns[] = 'skins';
+ if ( $this->input === '' ) {
+ // If no explicit --file filter is set, we're indexing all of $IP,
+ // but any extension or skin submodules should be excluded by default.
+ if ( !$this->hasOption( 'extensions' ) ) {
+ $this->excludePatterns[] = 'extensions';
+ }
+ if ( !$this->hasOption( 'skins' ) ) {
+ $this->excludePatterns[] = 'skins';
+ }
}
$this->doDot = shell_exec( 'which dot' );
/* jQuery Plugins */
- 'jquery.accessKeyLabel' => [
- 'deprecated' => 'Please use "mediawiki.util" instead.',
- 'dependencies' => [
- 'mediawiki.util',
- ],
- 'targets' => [ 'mobile', 'desktop' ],
- ],
'jquery.chosen' => [
'scripts' => 'resources/lib/jquery.chosen/chosen.jquery.js',
'styles' => 'resources/lib/jquery.chosen/chosen.css',
'resources/src/mediawiki.special/userrights.css',
'resources/src/mediawiki.special/watchlist.css',
'resources/src/mediawiki.special/block.less',
+ 'resources/src/mediawiki.special/listFiles.less',
'resources/src/mediawiki.special/blocklist.less',
],
'targets' => [ 'desktop', 'mobile' ],
--- /dev/null
+@import 'mediawiki.ui/variables';
+
+// On mobile devices the table layout is collapsed.
+@media all and ( max-width: @width-breakpoint-tablet ) {
+ .mw-special-Listfiles {
+ // stylelint-disable selector-class-pattern
+ thead,
+ .TablePager_col_count,
+ .TablePager_col_img_size,
+ .TablePager_col_img_name,
+ .TablePager_col_img_timestamp {
+ display: none;
+ }
+
+ tbody,
+ tr,
+ td,
+ .mw-datatable,
+ .TablePager_col_img_description,
+ .TablePager_col_thumb {
+ display: block;
+ }
+
+ .mw-datatable,
+ .mw-datatable th,
+ .mw-datatable td {
+ border: 0;
+ }
+
+ .TablePager_col_img_user_text,
+ .TablePager_col_img_description {
+ color: @colorGray5;
+ margin: 0.5em 0 0;
+ padding-bottom: 40px;
+ line-height: 1.5;
+ }
+
+ .TablePager_col_img_user_text {
+ padding: 0;
+ }
+ // stylelint-enable selector-class-pattern
+ }
+}
!! article
MediaWiki:T34057
!! text
-== {{int:headline_sample}} ==
+== {{int:ok}} ==
!! endarticle
!! test
!! wikitext
{{int:T34057}}
!! html
-<h2><span class="mw-headline" id="Headline_text">Headline text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Headline text">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span class="mw-headline" id="OK">OK</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: OK">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
!! end
!! test
namespace MediaWiki\Tests\Rest\BasicAccess;
use GuzzleHttp\Psr7\Uri;
-use MediaWiki\MediaWikiServices;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\RequestData;
class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
private function createRouter( $userRights, $request ) {
$user = User::newFromName( 'Test user' );
- // Don't allow the rights to everybody so that user rights kick in.
- $this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [ '*' => $userRights ] );
- $this->overrideUserPermissions(
- $user,
- array_keys( array_filter( $userRights ), function ( $value ) {
- return $value === true;
- } )
- );
-
- global $IP;
-
$objectFactory = new ObjectFactory(
$this->getMockForAbstractClass( ContainerInterface::class )
);
+ $permissionManager = $this->createMock( PermissionManager::class );
+ // Don't allow the rights to everybody so that user rights kick in.
+ $permissionManager->method( 'isEveryoneAllowed' )->willReturn( false );
+ $permissionManager->method( 'userHasRight' )
+ ->will( $this->returnCallback( function ( $user, $action ) use ( $userRights ) {
+ return isset( $userRights[$action] ) && $userRights[$action];
+ } ) );
+
+ global $IP;
return new Router(
[ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
'/rest',
new \EmptyBagOStuff(),
new ResponseFactory( [] ),
- new MWBasicAuthorizer( $user, MediaWikiServices::getInstance()->getPermissionManager() ),
+ new MWBasicAuthorizer( $user, $permissionManager ),
$objectFactory,
- new Validator( $objectFactory, $request, $user )
+ new Validator( $objectFactory, $permissionManager, $request, $user )
);
}
use EmptyBagOStuff;
use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\EntryPoint;
$objectFactory = new ObjectFactory(
$this->getMockForAbstractClass( ContainerInterface::class )
);
+ $permissionManager = $this->createMock( PermissionManager::class );
return new Router(
[ "$IP/tests/phpunit/unit/includes/Rest/testRoutes.json" ],
new ResponseFactory( [] ),
new StaticBasicAuthorizer(),
$objectFactory,
- new Validator( $objectFactory, $request, new User )
+ new Validator( $objectFactory, $permissionManager, $request, new User )
);
}
--- /dev/null
+<?php
+
+use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+
+/**
+ * @covers ApiQueryBlockInfoTrait
+ */
+class ApiQueryBlockInfoTraitTest extends MediaWikiTestCase {
+
+ public function testUsesApiBlockInfoTrait() {
+ $this->assertTrue( method_exists( ApiQueryBlockInfoTrait::class, 'getBlockDetails' ),
+ 'ApiQueryBlockInfoTrait::getBlockDetails exists' );
+ }
+
+ /**
+ * @dataProvider provideAddBlockInfoToQuery
+ */
+ public function testAddBlockInfoToQuery( $args, $expect ) {
+ // Fake timestamp to show up in the queries
+ $reset = ConvertibleTimestamp::setFakeTime( '20190101000000' );
+
+ $data = [];
+
+ $mock = $this->getMockForTrait( ApiQueryBlockInfoTrait::class );
+ $mock->method( 'getDB' )->willReturn( wfGetDB( DB_REPLICA ) );
+ $mock->method( 'getPermissionManager' )
+ ->willReturn( MediaWikiServices::getInstance()->getPermissionManager() );
+ $mock->method( 'getUser' )
+ ->willReturn( $this->getMutableTestUser()->getUser() );
+ $mock->method( 'addTables' )->willReturnCallback( function ( $v ) use ( &$data ) {
+ $data['tables'] = array_merge( $data['tables'] ?? [], (array)$v );
+ } );
+ $mock->method( 'addFields' )->willReturnCallback( function ( $v ) use ( &$data ) {
+ $data['fields'] = array_merge( $data['fields'] ?? [], (array)$v );
+ } );
+ $mock->method( 'addWhere' )->willReturnCallback( function ( $v ) use ( &$data ) {
+ $data['where'] = array_merge( $data['where'] ?? [], (array)$v );
+ } );
+ $mock->method( 'addJoinConds' )->willReturnCallback( function ( $v ) use ( &$data ) {
+ $data['joins'] = array_merge( $data['joins'] ?? [], (array)$v );
+ } );
+
+ TestingAccessWrapper::newFromObject( $mock )->addBlockInfoToQuery( ...$args );
+ $this->assertEquals( $expect, $data );
+ }
+
+ public function provideAddBlockInfoToQuery() {
+ $queryInfo = DatabaseBlock::getQueryInfo();
+
+ $db = wfGetDB( DB_REPLICA );
+ $ts = $db->addQuotes( $db->timestamp( '20190101000000' ) );
+
+ return [
+ [ [ false ], [
+ 'tables' => [ 'blk' => [ 'ipblocks' ] ],
+ 'fields' => [ 'ipb_deleted' ],
+ 'where' => [ 'ipb_deleted = 0 OR ipb_deleted IS NULL' ],
+ 'joins' => [
+ 'blk' => [ 'LEFT JOIN', [ 'ipb_user=user_id', "ipb_expiry > $ts" ] ]
+ ],
+ ] ],
+
+ [ [ true ], [
+ 'tables' => [ 'blk' => $queryInfo['tables'] ],
+ 'fields' => $queryInfo['fields'],
+ 'where' => [ 'ipb_deleted = 0 OR ipb_deleted IS NULL' ],
+ 'joins' => $queryInfo['joins'] + [
+ 'blk' => [ 'LEFT JOIN', [ 'ipb_user=user_id', "ipb_expiry > $ts" ] ]
+ ],
+ ] ],
+ ];
+ }
+
+}
'wfLocalFile() returns LocalFile for valid Titles'
);
}
+
+ /**
+ * @covers File::getUser
+ */
+ public function testGetUserForNonExistingFile() {
+ $this->assertSame( 'Unknown user', $this->file_hl0->getUser() );
+ $this->assertSame( 0, $this->file_hl0->getUser( 'id' ) );
+ }
+
+ /**
+ * @covers File::getUser
+ */
+ public function testDescriptionShortUrlForNonExistingFile() {
+ $this->assertNull( $this->file_hl0->getDescriptionShortUrl() );
+ }
+
+ /**
+ * @covers File::getUser
+ */
+ public function testDescriptionTextForNonExistingFile() {
+ $this->assertFalse( $this->file_hl0->getDescriptionText() );
+ }
}
[ 'domain:page:5', 'page' ],
[ 'domain:main-key', 'main-key' ],
[ 'domain:page:history', 'page' ],
+ // Regression test for T232907
+ [ 'domain:foo-bar-1.2:abc:v2', 'foo-bar-1_2' ],
[ 'missingdomainkey', 'missingdomainkey' ]
];
}
use EmptyBagOStuff;
use GuzzleHttp\Psr7\Uri;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
use MediaWiki\Rest\RequestData;
use MediaWiki\Rest\ResponseFactory;
$objectFactory = new ObjectFactory(
$this->getMockForAbstractClass( ContainerInterface::class )
);
-
+ $permissionManager = $this->createMock( PermissionManager::class );
$request = new RequestData( $requestInfo );
$router = new Router(
[ __DIR__ . '/../testRoutes.json' ],
new ResponseFactory( [] ),
new StaticBasicAuthorizer(),
$objectFactory,
- new Validator( $objectFactory, $request, new User )
+ new Validator( $objectFactory, $permissionManager, $request, new User )
);
$response = $router->execute( $request );
if ( isset( $responseInfo['statusCode'] ) ) {
namespace MediaWiki\Tests\Rest;
use GuzzleHttp\Psr7\Uri;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\HttpException;
$objectFactory = new ObjectFactory(
$this->getMockForAbstractClass( ContainerInterface::class )
);
+ $permissionManager = $this->createMock( PermissionManager::class );
return new Router(
[ __DIR__ . '/testRoutes.json' ],
[],
new ResponseFactory( [] ),
new StaticBasicAuthorizer( $authError ),
$objectFactory,
- new Validator( $objectFactory, $request, new User )
+ new Validator( $objectFactory, $permissionManager, $request, new User )
);
}
+## 0.5.0 / 2019-09-18
+
+* Api: Added `bot()` method.
+
## 0.4.0 / 2019-07-18
* Util: Added a `waitForModuleState()` method.
* `createAccount(string username, string password)`
* `blockUser([ string username [, string expiry ] ])`
* `unblockUser([ string username ])`
+* `bot([string username [, string password [, string baseUrl ] ] ])`
### RunJobs
{
"name": "wdio-mediawiki",
- "version": "0.4.0",
+ "version": "0.5.0",
"description": "WebdriverIO plugin for testing a MediaWiki site.",
"homepage": "https://gerrit.wikimedia.org/g/mediawiki/core/+/master/tests/selenium/wdio-mediawiki/",
"license": "MIT",