Completed
Branch master (939199)
by
unknown
39:35
created

includes/api/ApiOptions.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 *
4
 *
5
 * Created on Apr 15, 2012
6
 *
7
 * Copyright © 2012 Szymon Świerkosz [email protected]
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
/**
28
 * API module that facilitates the changing of user's preferences.
29
 * Requires API write mode to be enabled.
30
 *
31
 * @ingroup API
32
 */
33
class ApiOptions extends ApiBase {
34
	/**
35
	 * Changes preferences of the current user.
36
	 */
37
	public function execute() {
38
		if ( $this->getUser()->isAnon() ) {
39
			$this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' );
40
		} elseif ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) {
41
			$this->dieUsage( "You don't have permission to edit your options", 'permissiondenied' );
42
		}
43
44
		$params = $this->extractRequestParams();
45
		$changed = false;
46
47
		if ( isset( $params['optionvalue'] ) && !isset( $params['optionname'] ) ) {
48
			$this->dieUsageMsg( [ 'missingparam', 'optionname' ] );
49
		}
50
51
		// Load the user from the master to reduce CAS errors on double post (T95839)
52
		$user = $this->getUser()->getInstanceForUpdate();
53
		if ( !$user ) {
54
			$this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' );
55
		}
56
57
		if ( $params['reset'] ) {
58
			$user->resetOptions( $params['resetkinds'], $this->getContext() );
59
			$changed = true;
60
		}
61
62
		$changes = [];
63
		if ( count( $params['change'] ) ) {
64
			foreach ( $params['change'] as $entry ) {
65
				$array = explode( '=', $entry, 2 );
66
				$changes[$array[0]] = isset( $array[1] ) ? $array[1] : null;
67
			}
68
		}
69
		if ( isset( $params['optionname'] ) ) {
70
			$newValue = isset( $params['optionvalue'] ) ? $params['optionvalue'] : null;
71
			$changes[$params['optionname']] = $newValue;
72
		}
73
		if ( !$changed && !count( $changes ) ) {
74
			$this->dieUsage( 'No changes were requested', 'nochanges' );
75
		}
76
77
		$prefs = Preferences::getPreferences( $user, $this->getContext() );
0 ignored issues
show
It seems like $user defined by $this->getUser()->getInstanceForUpdate() on line 52 can be null; however, Preferences::getPreferences() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
78
		$prefsKinds = $user->getOptionKinds( $this->getContext(), $changes );
79
80
		$htmlForm = null;
81
		foreach ( $changes as $key => $value ) {
82
			switch ( $prefsKinds[$key] ) {
83
				case 'registered':
84
					// Regular option.
85
					if ( $htmlForm === null ) {
86
						// We need a dummy HTMLForm for the validate callback...
87
						$htmlForm = new HTMLForm( [], $this );
88
					}
89
					$field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
90
					$validation = $field->validate( $value, $user->getOptions() );
91
					break;
92
				case 'registered-multiselect':
93
				case 'registered-checkmatrix':
94
					// A key for a multiselect or checkmatrix option.
95
					$validation = true;
96
					$value = $value !== null ? (bool)$value : null;
97
					break;
98
				case 'userjs':
99
					// Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
100
					if ( strlen( $key ) > 255 ) {
101
						$validation = 'key too long (no more than 255 bytes allowed)';
102
					} elseif ( preg_match( '/[^a-zA-Z0-9_-]/', $key ) !== 0 ) {
103
						$validation = 'invalid key (only a-z, A-Z, 0-9, _, - allowed)';
104
					} else {
105
						$validation = true;
106
					}
107
					break;
108
				case 'special':
109
					$validation = 'cannot be set by this module';
110
					break;
111
				case 'unused':
112
				default:
113
					$validation = 'not a valid preference';
114
					break;
115
			}
116
			if ( $validation === true ) {
117
				$user->setOption( $key, $value );
118
				$changed = true;
119
			} else {
120
				$this->setWarning( "Validation error for '$key': $validation" );
121
			}
122
		}
123
124
		if ( $changed ) {
125
			// Commit changes
126
			$user->saveSettings();
127
		}
128
129
		$this->getResult()->addValue( null, $this->getModuleName(), 'success' );
130
	}
131
132
	public function mustBePosted() {
133
		return true;
134
	}
135
136
	public function isWriteMode() {
137
		return true;
138
	}
139
140
	public function getAllowedParams() {
141
		$optionKinds = User::listOptionKinds();
142
		$optionKinds[] = 'all';
143
144
		return [
145
			'reset' => false,
146
			'resetkinds' => [
147
				ApiBase::PARAM_TYPE => $optionKinds,
148
				ApiBase::PARAM_DFLT => 'all',
149
				ApiBase::PARAM_ISMULTI => true
150
			],
151
			'change' => [
152
				ApiBase::PARAM_ISMULTI => true,
153
			],
154
			'optionname' => [
155
				ApiBase::PARAM_TYPE => 'string',
156
			],
157
			'optionvalue' => [
158
				ApiBase::PARAM_TYPE => 'string',
159
			],
160
		];
161
	}
162
163
	public function needsToken() {
164
		return 'csrf';
165
	}
166
167
	public function getHelpUrls() {
168
		return 'https://www.mediawiki.org/wiki/API:Options';
169
	}
170
171
	protected function getExamplesMessages() {
172
		return [
173
			'action=options&reset=&token=123ABC'
174
				=> 'apihelp-options-example-reset',
175
			'action=options&change=skin=vector|hideminor=1&token=123ABC'
176
				=> 'apihelp-options-example-change',
177
			'action=options&reset=&change=skin=monobook&optionname=nickname&' .
178
				'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC'
179
				=> 'apihelp-options-example-complex',
180
		];
181
	}
182
}
183