$services->getContentLanguage(),
AuthManager::singleton(),
$services->getLinkRendererFactory()->create(),
- $services->getNamespaceInfo()
+ $services->getNamespaceInfo(),
+ $services->getPermissionManager()
);
$factory->setLogger( LoggerFactory::getInstance( 'preferences' ) );
case 'png':
return 'image/png';
case 'jpg':
- return 'image/jpeg';
case 'jpeg':
return 'image/jpeg';
}
use MediaWiki\MediaWikiServices as MediaWikiServicesAlias;
use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
/**
* @ingroup SpecialPage Dump
/** @var XmlDumpWriter */
private $writer;
- /** @var Database */
+ /** @var IDatabase */
protected $db;
/** @var array|int */
}
/**
- * @param Database $db
+ * @param IDatabase $db
* @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
* WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
* - offset: non-inclusive offset at which to start the query
if ( $cond ) {
$where[] = $cond;
}
- # Get logging table name for logging.* clause
- $logging = $this->db->tableName( 'logging' );
-
$result = null; // Assuring $result is not undefined, if exception occurs early
$commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
$actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
+ $tables = array_merge(
+ [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ]
+ );
+ $fields = [
+ 'log_id', 'log_type', 'log_action', 'log_timestamp', 'log_namespace',
+ 'log_title', 'log_params', 'log_deleted', 'user_name'
+ ] + $commentQuery['fields'] + $actorQuery['fields'];
+ $options = [
+ 'ORDER BY' => 'log_id',
+ 'USE INDEX' => [ 'logging' => 'PRIMARY' ],
+ 'LIMIT' => self::BATCH_SIZE,
+ ];
+ $joins = [
+ 'user' => [ 'JOIN', 'user_id = ' . $actorQuery['fields']['log_user'] ]
+ ] + $commentQuery['joins'] + $actorQuery['joins'];
+
$lastLogId = 0;
while ( true ) {
$result = $this->db->select(
- array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
- [ "{$logging}.*", 'user_name' ] + $commentQuery['fields'] + $actorQuery['fields'],
+ $tables,
+ $fields,
array_merge( $where, [ 'log_id > ' . intval( $lastLogId ) ] ),
__METHOD__,
- [
- 'ORDER BY' => 'log_id',
- 'USE INDEX' => [ 'logging' => 'PRIMARY' ],
- 'LIMIT' => self::BATCH_SIZE,
- ],
- [
- 'user' => [ 'JOIN', 'user_id = ' . $actorQuery['fields']['log_user'] ]
- ] + $commentQuery['joins'] + $actorQuery['joins']
+ $options,
+ $joins
);
if ( !$result->numRows() ) {
/** @var string Required OS username to own files */
protected $fileOwner;
- /** @var bool Whether the OS is Windows (otherwise assumed Unix-like)*/
+ /** @var bool Whether the OS is Windows (otherwise assumed Unix-like) */
protected $isWindows;
/** @var string OS username running this script */
protected $currentUser;
private $masterTemplateOverrides = [];
/** @var array[] Map of (host => server config map overrides) for main and external servers */
private $templateOverridesByServer = [];
- /** @var string[]|bool[] A map of section name to read-only message */
+ /** @var string[]|bool[] A map of section name to read-only message */
private $readOnlyBySection = [];
/** @var string An ILoadMonitor class */
* data can be before the load balancer tries to avoid using it. The map can have 'is static'
* set to disable blocking replication sync checks (intended for archive servers with
* unchanging data).
-
+ *
* @see LBFactory::__construct()
* @param array $conf Additional parameters include:
* - hostsByName Optional (hostname => IP address) map.
* Send mail using a PEAR mailer
*
* @param Mail_smtp $mailer
- * @param string $dest
- * @param string $headers
+ * @param string[]|string $dest
+ * @param array $headers
* @param string $body
*
* @return Status
throw new MWException( 'PEAR mail package is not installed' );
}
+ $recips = array_map( 'strval', $to );
+
Wikimedia\suppressWarnings();
// Create the mail object using the Mail::factory method
$headers['Subject'] = self::quotedPrintable( $subject );
// When sending only to one recipient, shows it its email using To:
- if ( count( $to ) == 1 ) {
- $headers['To'] = $to[0]->toString();
+ if ( count( $recips ) == 1 ) {
+ $headers['To'] = $recips[0];
}
// Split jobs since SMTP servers tends to limit the maximum
// number of possible recipients.
- $chunks = array_chunk( $to, $wgEnotifMaxRecips );
+ $chunks = array_chunk( $recips, $wgEnotifMaxRecips );
foreach ( $chunks as $chunk ) {
$status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
// FIXME : some chunks might be sent while others are not!
namespace MediaWiki\Preferences;
-use Config;
use DateTime;
use DateTimeZone;
use Exception;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Permissions\PermissionManager;
use MessageLocalizer;
use MWException;
use MWTimestamp;
/** @var NamespaceInfo */
protected $nsInfo;
+ /** @var PermissionManager */
+ protected $permissionManager;
+
/**
* TODO Make this a const when we drop HHVM support (T192166)
*
/**
* Do not call this directly. Get it from MediaWikiServices.
*
- * @param ServiceOptions|Config $options Config accepted for backwards compatibility
+ * @param ServiceOptions $options
* @param Language $contLang
* @param AuthManager $authManager
* @param LinkRenderer $linkRenderer
- * @param NamespaceInfo|null $nsInfo
+ * @param NamespaceInfo $nsInfo
+ * @param PermissionManager|null $permissionManager
*/
public function __construct(
- $options,
+ ServiceOptions $options,
Language $contLang,
AuthManager $authManager,
LinkRenderer $linkRenderer,
- NamespaceInfo $nsInfo = null
+ NamespaceInfo $nsInfo,
+ PermissionManager $permissionManager = null
) {
- if ( $options instanceof Config ) {
- wfDeprecated( __METHOD__ . ' with Config parameter', '1.34' );
- $options = new ServiceOptions( self::$constructorOptions, $options );
- }
-
$options->assertRequiredOptions( self::$constructorOptions );
- if ( !$nsInfo ) {
- wfDeprecated( __METHOD__ . ' with no NamespaceInfo argument', '1.34' );
- $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+ if ( !$permissionManager ) {
+ // TODO: this is actually hard-deprecated, left for jenkins to pass
+ // together with GlobalPreferences extension. Will be removed in a followup.
+ $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
}
$this->options = $options;
$this->contLang = $contLang;
$this->authManager = $authManager;
$this->linkRenderer = $linkRenderer;
$this->nsInfo = $nsInfo;
+ $this->permissionManager = $permissionManager;
$this->logger = new NullLogger();
}
# # Make sure that form fields have their parent set. See T43337.
$dummyForm = new HTMLForm( [], $context );
- $disable = !$user->isAllowed( 'editmyoptions' );
+ $disable = !$this->permissionManager->userHasRight( $user, 'editmyoptions' );
$defaultOptions = User::getDefaultOptions();
$userOptions = $user->getOptions();
];
}
- $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
- $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
+ $canViewPrivateInfo = $this->permissionManager->userHasRight( $user, 'viewmyprivateinfo' );
+ $canEditPrivateInfo = $this->permissionManager->userHasRight( $user, 'editmyprivateinfo' );
// Actually changeable stuff
$defaultPreferences['realname'] = [
];
}
- if ( $this->options->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
+ if ( $this->options->get( 'EnableUserEmail' ) &&
+ $this->permissionManager->userHasRight( $user, 'sendemail' )
+ ) {
$defaultPreferences['disablemail'] = [
'id' => 'wpAllowEmail',
'type' => 'toggle',
'label-message' => 'tog-numberheadings',
];
- if ( $user->isAllowed( 'rollback' ) ) {
+ if ( $this->permissionManager->userHasRight( $user, 'rollback' ) ) {
$defaultPreferences['showrollbackconfirmation'] = [
'type' => 'toggle',
'section' => 'rendering/advancedrendering',
];
}
- if ( $user->isAllowed( 'minoredit' ) ) {
+ if ( $this->permissionManager->userHasRight( $user, 'minoredit' ) ) {
$defaultPreferences['minordefault'] = [
'type' => 'toggle',
'section' => 'editing/editor',
$watchlistdaysMax = ceil( $this->options->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
# # Watchlist #####################################
- if ( $user->isAllowed( 'editmywatchlist' ) ) {
+ if ( $this->permissionManager->userHasRight( $user, 'editmywatchlist' ) ) {
$editWatchlistLinks = '';
$editWatchlistModes = [
'edit' => [ 'subpage' => false, 'flags' => [] ],
];
// Kinda hacky
- if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
+ if ( $this->permissionManager->userHasAnyRight( $user, 'createpage', 'createtalk' ) ) {
$watchTypes['read'] = 'watchcreations';
}
- if ( $user->isAllowed( 'rollback' ) ) {
+ if ( $this->permissionManager->userHasRight( $user, 'rollback' ) ) {
$watchTypes['rollback'] = 'watchrollback';
}
- if ( $user->isAllowed( 'upload' ) ) {
+ if ( $this->permissionManager->userHasRight( $user, 'upload' ) ) {
$watchTypes['upload'] = 'watchuploads';
}
foreach ( $watchTypes as $action => $pref ) {
- if ( $user->isAllowed( $action ) ) {
+ if ( $this->permissionManager->userHasRight( $user, $action ) ) {
// Messages:
// tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
// tog-watchrollback
$hiddenPrefs = $this->options->get( 'HiddenPrefs' );
$result = true;
- if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+ if ( !$this->permissionManager
+ ->userHasAnyRight( $user, 'editmyprivateinfo', 'editmyoptions' )
+ ) {
return Status::newFatal( 'mypreferencesprotected' );
}
// (not really "private", but still shouldn't be edited without permission)
if ( !in_array( 'realname', $hiddenPrefs )
- && $user->isAllowed( 'editmyprivateinfo' )
+ && $this->permissionManager->userHasRight( $user, 'editmyprivateinfo' )
&& array_key_exists( 'realname', $formData )
) {
$realName = $formData['realname'];
$user->setRealName( $realName );
}
- if ( $user->isAllowed( 'editmyoptions' ) ) {
+ if ( $this->permissionManager->userHasRight( $user, 'editmyoptions' ) ) {
$oldUserOptions = $user->getOptions();
foreach ( $this->getSaveBlacklist() as $b ) {
'dependencies' => [
'mediawiki.api',
'oojs',
+ 'mediawiki.Uri',
],
'targets' => [ 'desktop', 'mobile' ],
],
]
],
'mediawiki.util' => [
- 'localBasePath' => "$IP/resources/src/mediawiki.util/",
- 'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.util/",
+ 'localBasePath' => "$IP/resources/src/mediawiki.util",
+ 'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.util",
'packageFiles' => [
'util.js',
'jquery.accessKeyLabel.js',
}
},
parameters: {
- // Add 'origin' query parameter to all requests.
origin: this.getOrigin()
}
},
* any).
*
* @protected
- * @return {string}
+ * @return {string|undefined}
*/
CoreForeignApi.prototype.getOrigin = function () {
- var origin;
+ var origin, apiUri, apiOrigin;
if ( this.anonymous ) {
return '*';
}
+
origin = location.protocol + '//' + location.hostname;
if ( location.port ) {
origin += ':' + location.port;
}
+
+ apiUri = new mw.Uri( this.apiUrl );
+ apiOrigin = apiUri.protocol + '://' + apiUri.getAuthority();
+ if ( origin === apiOrigin ) {
+ // requests are not cross-origin, omit parameter
+ return undefined;
+ }
+
return origin;
};
if ( ajaxOptions.type === 'POST' ) {
url = ( ajaxOptions && ajaxOptions.url ) || this.defaults.ajax.url;
origin = ( parameters && parameters.origin ) || this.defaults.parameters.origin;
- url += ( url.indexOf( '?' ) !== -1 ? '&' : '?' ) +
- // Depending on server configuration, MediaWiki may forbid periods in URLs, due to an IE 6
- // XSS bug. So let's escape them here. See WebRequest::checkUrlExtension() and T30235.
- 'origin=' + encodeURIComponent( origin ).replace( /\./g, '%2E' );
+ if ( origin !== undefined ) {
+ url += ( url.indexOf( '?' ) !== -1 ? '&' : '?' ) +
+ // Depending on server configuration, MediaWiki may forbid periods in URLs, due to an IE 6
+ // XSS bug. So let's escape them here. See WebRequest::checkUrlExtension() and T30235.
+ 'origin=' + encodeURIComponent( origin ).replace( /\./g, '%2E' );
+ }
newAjaxOptions = $.extend( {}, ajaxOptions, { url: url } );
} else {
newAjaxOptions = ajaxOptions;
use MediaWiki\Auth\AuthManager;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Preferences\DefaultPreferencesFactory;
use Wikimedia\TestingAccessWrapper;
/**
* Get a basic PreferencesFactory for testing with.
+ * @param PermissionManager|null $manager
* @return DefaultPreferencesFactory
*/
- protected function getPreferencesFactory() {
+ protected function getPreferencesFactory( PermissionManager $manager = null ) {
$mockNsInfo = $this->createMock( NamespaceInfo::class );
$mockNsInfo->method( 'getValidNamespaces' )->willReturn( [
NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK
$mockNsInfo->expects( $this->never() )
->method( $this->anythingBut( 'getValidNamespaces', '__destruct' ) );
+ $mockPermissionManager = $manager ?? $this->createMock( PermissionManager::class );
+
return new DefaultPreferencesFactory(
new LoggedServiceOptions( self::$serviceOptionsAccessLog,
DefaultPreferencesFactory::$constructorOptions, $this->config ),
new Language(),
AuthManager::singleton(),
MediaWikiServices::getInstance()->getLinkRenderer(),
- $mockNsInfo
+ $mockNsInfo,
+ $mockPermissionManager
);
}
* @dataProvider emailAuthenticationProvider
*/
public function testEmailAuthentication( $user, $cssClass ) {
- $prefs = $this->getPreferencesFactory()->getFormDescriptor( $user, $this->context );
+ $pm = $this->createMock( PermissionManager::class );
+ $pm->method( 'userHasRight' )->willReturn( true );
+ $prefs = $this->getPreferencesFactory( $pm )->getFormDescriptor( $user, $this->context );
$this->assertArrayHasKey( 'cssclass', $prefs['emailauthentication'] );
$this->assertEquals( $cssClass, $prefs['emailauthentication']['cssclass'] );
}
$userMock = $this->getMockBuilder( User::class )
->disableOriginalConstructor()
->getMock();
- $userMock->method( 'isAllowed' )
- ->willReturn( false );
$userMock->method( 'getEffectiveGroups' )
->willReturn( [] );
$userMock->method( 'getGroupMemberships' )
->willReturn( [] );
$userMock->method( 'getOptions' )
->willReturn( [ 'test' => 'yes' ] );
-
- $prefs = $this->getPreferencesFactory()->getFormDescriptor( $userMock, $this->context );
+ $pm = $this->createMock( PermissionManager::class );
+ $pm->method( 'userHasRight' )
+ ->will( $this->returnValueMap( [
+ [ $userMock, 'editmyoptions', true ]
+ ] ) );
+ $prefs = $this->getPreferencesFactory( $pm )->getFormDescriptor( $userMock, $this->context );
$this->assertArrayNotHasKey( 'showrollbackconfirmation', $prefs );
}
$userMock = $this->getMockBuilder( User::class )
->disableOriginalConstructor()
->getMock();
- $userMock->method( 'isAllowed' )
- ->willReturn( true );
$userMock->method( 'getEffectiveGroups' )
->willReturn( [] );
$userMock->method( 'getGroupMemberships' )
->willReturn( [] );
$userMock->method( 'getOptions' )
->willReturn( [ 'test' => 'yes' ] );
-
- $prefs = $this->getPreferencesFactory()->getFormDescriptor( $userMock, $this->context );
+ $pm = $this->createMock( PermissionManager::class );
+ $pm->method( 'userHasRight' )
+ ->will( $this->returnValueMap( [
+ [ $userMock, 'editmyoptions', true ],
+ [ $userMock, 'rollback', true ]
+ ] ) );
+ $prefs = $this->getPreferencesFactory( $pm )->getFormDescriptor( $userMock, $this->context );
$this->assertArrayHasKey( 'showrollbackconfirmation', $prefs );
$this->assertEquals(
'rendering/advancedrendering',
->getMock();
$userMock->method( 'getOptions' )
->willReturn( $oldOptions );
- $userMock->method( 'isAllowedAny' )
- ->willReturn( true );
- $userMock->method( 'isAllowed' )
- ->willReturn( true );
$userMock->expects( $this->exactly( 2 ) )
->method( 'setOption' )
[ $this->equalTo( 'option' ), $this->equalTo( $newOptions[ 'option' ] ) ]
);
- $form->expects( $this->any() )
- ->method( 'getModifiedUser' )
+ $form->method( 'getModifiedUser' )
->willReturn( $userMock );
- $form->expects( $this->any() )
- ->method( 'getContext' )
+ $form->method( 'getContext' )
->willReturn( $this->context );
- $form->expects( $this->any() )
- ->method( 'getConfig' )
+ $form->method( 'getConfig' )
->willReturn( $configMock );
+ $pm = $this->createMock( PermissionManager::class );
+ $pm->method( 'userHasAnyRight' )
+ ->will( $this->returnValueMap( [
+ [ $userMock, 'editmyprivateinfo', 'editmyoptions', true ]
+ ] ) );
+ $pm->method( 'userHasRight' )
+ ->will( $this->returnValueMap( [
+ [ $userMock, 'editmyoptions', true ]
+ ] ) );
+
$this->setTemporaryHook( 'PreferencesFormPreSave',
function ( $formData, $form, $user, &$result, $oldUserOptions )
use ( $newOptions, $oldOptions, $userMock ) {
);
/** @var DefaultPreferencesFactory $factory */
- $factory = TestingAccessWrapper::newFromObject( $this->getPreferencesFactory() );
+ $factory = TestingAccessWrapper::newFromObject( $this->getPreferencesFactory( $pm ) );
$factory->saveFormData( $newOptions, $form, [] );
}
// Test a string with leading zeros (i.e. not octal) and spaces.
$this->context->getRequest()->setVal( 'wprclimit', ' 0012 ' );
$user = new User;
- $form = $this->getPreferencesFactory()->getForm( $user, $this->context );
+ $pm = $this->createMock( PermissionManager::class );
+ $pm->method( 'userHasAnyRight' )
+ ->willReturn( true );
+ $pm->method( 'userHasRight' )
+ ->will( $this->returnValueMap( [
+ [ $user, 'editmyoptions', true ]
+ ] ) );
+ $form = $this->getPreferencesFactory( $pm )->getForm( $user, $this->context );
$form->show();
$form->trySubmit();
$this->assertEquals( 12, $user->getOption( 'rclimit' ) );
<?php
/**
-* @covers MWDoxygenFilter
+ * @covers MWDoxygenFilter
*/
class MWDoxygenFilterTest extends \PHPUnit\Framework\TestCase {
return api.post( {} );
} );
+ QUnit.test( 'origin is not included in same-origin GET requests', function ( assert ) {
+ var apiUrl = location.protocol + '//' + location.host + '/w/api.php',
+ api = new mw.ForeignApi( apiUrl );
+
+ this.server.respond( function ( request ) {
+ assert.strictEqual( request.url.match( /origin=.*?(?:&|$)/ ), null, 'origin is not included in GET requests' );
+ request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
+ } );
+
+ return api.get( {} );
+ } );
+
+ QUnit.test( 'origin is not included in same-origin POST requests', function ( assert ) {
+ var apiUrl = location.protocol + '//' + location.host + '/w/api.php',
+ api = new mw.ForeignApi( apiUrl );
+
+ this.server.respond( function ( request ) {
+ assert.strictEqual( request.requestBody.match( /origin=.*?(?:&|$)/ ), null, 'origin is not included in POST request body' );
+ assert.strictEqual( request.url.match( /origin=.*?(?:&|$)/ ), null, 'origin is not included in POST request URL, either' );
+ request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
+ } );
+
+ return api.post( {} );
+ } );
+
}() );