(bug 18195) Allow changing preferences via API
I have created an API module for changing the preferences. It allows resetting preferences (reset argument) and bulk changes of preferences (change argument) in a format: name1=value1|name2=value2 The change argument has a limitation imposed by the current API implementation as it cannot accept | in values. There is available a pair of arguments optionname and optionvalue, the latter accepts values with |. I have created optionstoken parameter in meta=userinfo to provide a token. There is already preferencestoken there, but I would like to have a consistent naming. Change-Id: I0d6c654a7354ba77e65e338423952a6a78c1150f
This commit is contained in:
parent
0e6dc67f12
commit
a6cd69d83a
6 changed files with 386 additions and 0 deletions
|
|
@ -315,6 +315,7 @@ $wgAutoloadLocalClasses = array(
|
|||
'ApiMain' => 'includes/api/ApiMain.php',
|
||||
'ApiMove' => 'includes/api/ApiMove.php',
|
||||
'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php',
|
||||
'ApiOptions' => 'includes/api/ApiOptions.php',
|
||||
'ApiPageSet' => 'includes/api/ApiPageSet.php',
|
||||
'ApiParamInfo' => 'includes/api/ApiParamInfo.php',
|
||||
'ApiParse' => 'includes/api/ApiParse.php',
|
||||
|
|
|
|||
|
|
@ -2281,7 +2281,10 @@ class User {
|
|||
* Reset all options to the site defaults
|
||||
*/
|
||||
public function resetOptions() {
|
||||
$this->load();
|
||||
|
||||
$this->mOptions = self::getDefaultOptions();
|
||||
$this->mOptionsLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class ApiMain extends ApiBase {
|
|||
'patrol' => 'ApiPatrol',
|
||||
'import' => 'ApiImport',
|
||||
'userrights' => 'ApiUserrights',
|
||||
'options' => 'ApiOptions',
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
150
includes/api/ApiOptions.php
Normal file
150
includes/api/ApiOptions.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Created on Apr 15, 2012
|
||||
*
|
||||
* Copyright © 2012 Szymon Świerkosz beau@adres.pl
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* API module that facilitates the changing of user's preferences.
|
||||
* Requires API write mode to be enabled.
|
||||
*
|
||||
* @ingroup API
|
||||
*/
|
||||
class ApiOptions extends ApiBase {
|
||||
|
||||
public function __construct( $main, $action ) {
|
||||
parent::__construct( $main, $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes preferences of the current user.
|
||||
*/
|
||||
public function execute() {
|
||||
$user = $this->getUser();
|
||||
|
||||
if ( $user->isAnon() ) {
|
||||
$this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' );
|
||||
}
|
||||
|
||||
$params = $this->extractRequestParams();
|
||||
$changes = 0;
|
||||
|
||||
if ( isset( $params['optionvalue'] ) && !isset( $params['optionname'] ) ) {
|
||||
$this->dieUsageMsg( array( 'missingparam', 'optionname' ) );
|
||||
}
|
||||
|
||||
if ( $params['reset'] ) {
|
||||
$user->resetOptions();
|
||||
$changes++;
|
||||
}
|
||||
if ( count( $params['change'] ) ) {
|
||||
foreach ( $params['change'] as $entry ) {
|
||||
$array = explode( '=', $entry, 2 );
|
||||
$user->setOption( $array[0], isset( $array[1] ) ? $array[1] : null );
|
||||
$changes++;
|
||||
}
|
||||
}
|
||||
if ( isset( $params['optionname'] ) ) {
|
||||
$newValue = isset( $params['optionvalue'] ) ? $params['optionvalue'] : null;
|
||||
$user->setOption( $params['optionname'], $newValue );
|
||||
$changes++;
|
||||
}
|
||||
|
||||
if ( $changes ) {
|
||||
// Commit changes
|
||||
$user->saveSettings();
|
||||
} else {
|
||||
$this->dieUsage( 'No changes were requested', 'nochanges' );
|
||||
}
|
||||
|
||||
$this->getResult()->addValue( null, $this->getModuleName(), 'success' );
|
||||
}
|
||||
|
||||
public function mustBePosted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isWriteMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAllowedParams() {
|
||||
return array(
|
||||
'token' => array(
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true
|
||||
),
|
||||
'reset' => false,
|
||||
'change' => array(
|
||||
ApiBase::PARAM_ISMULTI => true,
|
||||
),
|
||||
'optionname' => array(
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
),
|
||||
'optionvalue' => array(
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function getParamDescription() {
|
||||
return array(
|
||||
'token' => 'An options token previously obtained through the meta=userinfo',
|
||||
'reset' => 'Resets all preferences to the site defaults',
|
||||
'change' => 'Pipe-separated list of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters',
|
||||
'optionname' => 'A name of a option which should have an optionvalue set',
|
||||
'optionvalue' => 'A value of the option specified by the optionname, can contain pipe characters',
|
||||
);
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return 'Change preferences of the current user';
|
||||
}
|
||||
|
||||
public function getPossibleErrors() {
|
||||
return array_merge( parent::getPossibleErrors(), array(
|
||||
array( 'notloggedin' ),
|
||||
array( 'nochanges' ),
|
||||
) );
|
||||
}
|
||||
|
||||
public function needsToken() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTokenSalt() {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getExamples() {
|
||||
return array(
|
||||
'api.php?action=options&reset=&token=123ABC',
|
||||
'api.php?action=options&change=skin=vector|hideminor=1&token=123ABC',
|
||||
'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
|
||||
);
|
||||
}
|
||||
|
||||
public function getVersion() {
|
||||
return __CLASS__ . ': $Id$';
|
||||
}
|
||||
}
|
||||
|
|
@ -102,6 +102,12 @@ class ApiQueryUserInfo extends ApiQueryBase {
|
|||
$vals['options'] = $user->getOptions();
|
||||
}
|
||||
|
||||
if ( isset( $this->prop['optionstoken'] ) &&
|
||||
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) )
|
||||
) {
|
||||
$vals['optionstoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
|
||||
}
|
||||
|
||||
if ( isset( $this->prop['preferencestoken'] ) &&
|
||||
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) )
|
||||
) {
|
||||
|
|
@ -197,6 +203,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
|
|||
'rights',
|
||||
'changeablegroups',
|
||||
'options',
|
||||
'optionstoken',
|
||||
'preferencestoken',
|
||||
'editcount',
|
||||
'ratelimits',
|
||||
|
|
@ -220,6 +227,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
|
|||
' rights - Lists all the rights the current user has',
|
||||
' changeablegroups - Lists the groups the current user can add to and remove from',
|
||||
' options - Lists all preferences the current user has set',
|
||||
' optionstoken - Get a token to change current user\'s preferences',
|
||||
' preferencestoken - Get a token to change current user\'s preferences',
|
||||
' editcount - Adds the current user\'s edit count',
|
||||
' ratelimits - Lists all rate limits applying to the current user',
|
||||
|
|
|
|||
223
tests/phpunit/includes/api/ApiOptionsTest.php
Normal file
223
tests/phpunit/includes/api/ApiOptionsTest.php
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group API
|
||||
*/
|
||||
class ApiOptionsTest extends MediaWikiLangTestCase {
|
||||
|
||||
private $mTested, $mApiMainMock, $mUserMock, $mContext, $mSession;
|
||||
|
||||
private static $Success = array( 'options' => 'success' );
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->mUserMock = $this->getMockBuilder( 'User' )
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->mApiMainMock = $this->getMockBuilder( 'ApiBase' )
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
// Create a new context
|
||||
$this->mContext = new DerivativeContext( new RequestContext() );
|
||||
$this->mContext->setUser( $this->mUserMock );
|
||||
|
||||
$this->mApiMainMock->expects( $this->any() )
|
||||
->method( 'getContext' )
|
||||
->will( $this->returnValue( $this->mContext ) );
|
||||
|
||||
$this->mApiMainMock->expects( $this->any() )
|
||||
->method( 'getResult' )
|
||||
->will( $this->returnValue( new ApiResult( $this->mApiMainMock ) ) );
|
||||
|
||||
|
||||
// Empty session
|
||||
$this->mSession = array();
|
||||
|
||||
$this->mTested = new ApiOptions( $this->mApiMainMock, 'options' );
|
||||
}
|
||||
|
||||
private function getSampleRequest( $custom = array() ) {
|
||||
$request = array(
|
||||
'token' => '123ABC',
|
||||
'change' => null,
|
||||
'optionname' => null,
|
||||
'optionvalue' => null,
|
||||
);
|
||||
return array_merge( $request, $custom );
|
||||
}
|
||||
|
||||
private function executeQuery( $request ) {
|
||||
$this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) );
|
||||
$this->mTested->execute();
|
||||
return $this->mTested->getResult()->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException UsageException
|
||||
*/
|
||||
public function testNoToken() {
|
||||
$request = $this->getSampleRequest( array( 'token' => null ) );
|
||||
|
||||
$this->executeQuery( $request );
|
||||
}
|
||||
|
||||
public function testAnon() {
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'isAnon' )
|
||||
->will( $this->returnValue( true ) );
|
||||
|
||||
try {
|
||||
$request = $this->getSampleRequest();
|
||||
|
||||
$this->executeQuery( $request );
|
||||
} catch ( UsageException $e ) {
|
||||
$this->assertEquals( 'notloggedin', $e->getCodeString() );
|
||||
$this->assertEquals( 'Anonymous users cannot change preferences', $e->getMessage() );
|
||||
return;
|
||||
}
|
||||
$this->fail( "UsageException was not thrown" );
|
||||
}
|
||||
|
||||
public function testNoOptionname() {
|
||||
try {
|
||||
$request = $this->getSampleRequest( array( 'optionvalue' => '1' ) );
|
||||
|
||||
$this->executeQuery( $request );
|
||||
} catch ( UsageException $e ) {
|
||||
$this->assertEquals( 'nooptionname', $e->getCodeString() );
|
||||
$this->assertEquals( 'The optionname parameter must be set', $e->getMessage() );
|
||||
return;
|
||||
}
|
||||
$this->fail( "UsageException was not thrown" );
|
||||
}
|
||||
|
||||
public function testNoChanges() {
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'resetOptions' );
|
||||
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'setOption' );
|
||||
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'saveSettings' );
|
||||
|
||||
try {
|
||||
$request = $this->getSampleRequest();
|
||||
|
||||
$this->executeQuery( $request );
|
||||
} catch ( UsageException $e ) {
|
||||
$this->assertEquals( 'nochanges', $e->getCodeString() );
|
||||
$this->assertEquals( 'No changes were requested', $e->getMessage() );
|
||||
return;
|
||||
}
|
||||
$this->fail( "UsageException was not thrown" );
|
||||
}
|
||||
|
||||
public function testReset() {
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'resetOptions' );
|
||||
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'setOption' );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'saveSettings' );
|
||||
|
||||
$request = $this->getSampleRequest( array( 'reset' => '' ) );
|
||||
|
||||
$response = $this->executeQuery( $request );
|
||||
|
||||
$this->assertEquals( self::$Success, $response );
|
||||
}
|
||||
|
||||
public function testOptionWithValue() {
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'resetOptions' );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'saveSettings' );
|
||||
|
||||
$request = $this->getSampleRequest( array( 'optionname' => 'name', 'optionvalue' => 'value' ) );
|
||||
|
||||
$response = $this->executeQuery( $request );
|
||||
|
||||
$this->assertEquals( self::$Success, $response );
|
||||
}
|
||||
|
||||
public function testOptionResetValue() {
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'resetOptions' );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'name' ), $this->equalTo( null ) );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'saveSettings' );
|
||||
|
||||
$request = $this->getSampleRequest( array( 'optionname' => 'name' ) );
|
||||
$response = $this->executeQuery( $request );
|
||||
|
||||
$this->assertEquals( self::$Success, $response );
|
||||
}
|
||||
|
||||
public function testChange() {
|
||||
$this->mUserMock->expects( $this->never() )
|
||||
->method( 'resetOptions' );
|
||||
|
||||
$this->mUserMock->expects( $this->at( 1 ) )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'willBeNull' ), $this->equalTo( null ) );
|
||||
|
||||
$this->mUserMock->expects( $this->at( 2 ) )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) );
|
||||
|
||||
$this->mUserMock->expects( $this->at( 3 ) )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'saveSettings' );
|
||||
|
||||
$request = $this->getSampleRequest( array( 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy' ) );
|
||||
|
||||
$response = $this->executeQuery( $request );
|
||||
|
||||
$this->assertEquals( self::$Success, $response );
|
||||
}
|
||||
|
||||
public function testResetChangeOption() {
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'resetOptions' );
|
||||
|
||||
$this->mUserMock->expects( $this->at( 2 ) )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) );
|
||||
|
||||
$this->mUserMock->expects( $this->at( 3 ) )
|
||||
->method( 'setOption' )
|
||||
->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) );
|
||||
|
||||
$this->mUserMock->expects( $this->once() )
|
||||
->method( 'saveSettings' );
|
||||
|
||||
$args = array(
|
||||
'reset' => '',
|
||||
'change' => 'willBeHappy=Happy',
|
||||
'optionname' => 'name',
|
||||
'optionvalue' => 'value'
|
||||
);
|
||||
|
||||
$response = $this->executeQuery( $this->getSampleRequest( $args ) );
|
||||
|
||||
$this->assertEquals( self::$Success, $response );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue