MediaWiki  master
ApiOptions.php
Go to the documentation of this file.
1 <?php
29 
36 class ApiOptions extends ApiBase {
38  private $userForUpdates;
39 
41  private $userOptionsManager;
42 
44  private $preferencesFactory;
45 
52  public function __construct(
53  ApiMain $main,
54  $action,
55  UserOptionsManager $userOptionsManager = null,
56  PreferencesFactory $preferencesFactory = null
57  ) {
58  parent::__construct( $main, $action );
63  $services = MediaWikiServices::getInstance();
64  $this->userOptionsManager = $userOptionsManager ?? $services->getUserOptionsManager();
65  $this->preferencesFactory = $preferencesFactory ?? $services->getPreferencesFactory();
66  }
67 
71  public function execute() {
72  $user = $this->getUserForUpdates();
73  if ( !$user || !$user->isRegistered() ) {
74  $this->dieWithError(
75  [ 'apierror-mustbeloggedin', $this->msg( 'action-editmyoptions' ) ], 'notloggedin'
76  );
77  }
78 
79  $this->checkUserRightsAny( 'editmyoptions' );
80 
81  $params = $this->extractRequestParams();
82  $changed = false;
83 
84  if ( isset( $params['optionvalue'] ) && !isset( $params['optionname'] ) ) {
85  $this->dieWithError( [ 'apierror-missingparam', 'optionname' ] );
86  }
87 
88  $resetKinds = $params['resetkinds'];
89  if ( !$params['reset'] ) {
90  $resetKinds = [];
91  }
92 
93  $changes = [];
94  if ( $params['change'] ) {
95  foreach ( $params['change'] as $entry ) {
96  $array = explode( '=', $entry, 2 );
97  $changes[$array[0]] = $array[1] ?? null;
98  }
99  }
100  if ( isset( $params['optionname'] ) ) {
101  $newValue = $params['optionvalue'] ?? null;
102  $changes[$params['optionname']] = $newValue;
103  }
104 
105  $this->getHookRunner()->onApiOptions( $this, $user, $changes, $resetKinds );
106 
107  if ( $resetKinds ) {
108  $this->resetPreferences( $resetKinds );
109  $changed = true;
110  }
111 
112  if ( !$changed && !count( $changes ) ) {
113  $this->dieWithError( 'apierror-nochanges' );
114  }
115 
116  $prefs = $this->getPreferences();
117  $prefsKinds = $this->userOptionsManager->getOptionKinds( $user, $this->getContext(), $changes );
118 
119  $htmlForm = new HTMLForm( DefaultPreferencesFactory::simplifyFormDescriptor( $prefs ), $this );
120  foreach ( $changes as $key => $value ) {
121  switch ( $prefsKinds[$key] ) {
122  case 'registered':
123  // Regular option.
124  if ( $value === null ) {
125  // Reset it
126  $validation = true;
127  } else {
128  // Validate
129  $field = $htmlForm->getField( $key );
130  $validation = $field->validate( $value, $this->userOptionsManager->getOptions( $user ) );
131  }
132  break;
133  case 'registered-multiselect':
134  case 'registered-checkmatrix':
135  // A key for a multiselect or checkmatrix option.
136  // TODO: Apply validation properly.
137  $validation = true;
138  $value = $value !== null ? (bool)$value : null;
139  break;
140  case 'userjs':
141  // Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
142  if ( strlen( $key ) > 255 ) {
143  $validation = $this->msg( 'apiwarn-validationfailed-keytoolong', Message::numParam( 255 ) );
144  } elseif ( preg_match( '/[^a-zA-Z0-9_-]/', $key ) !== 0 ) {
145  $validation = $this->msg( 'apiwarn-validationfailed-badchars' );
146  } else {
147  $validation = true;
148  }
149 
150  LoggerFactory::getInstance( 'api-warning' )->info(
151  'ApiOptions: Setting userjs option',
152  [
153  'phab' => 'T259073',
154  'OptionName' => substr( $key, 0, 255 ),
155  'OptionValue' => substr( $value ?? '', 0, 255 ),
156  'OptionSize' => strlen( $value ?? '' ),
157  'OptionValidation' => $validation,
158  'UserId' => $user->getId(),
159  'RequestIP' => $this->getRequest()->getIP(),
160  'RequestUA' => $this->getRequest()->getHeader( 'User-Agent' )
161  ]
162  );
163  break;
164  case 'special':
165  $validation = $this->msg( 'apiwarn-validationfailed-cannotset' );
166  break;
167  case 'unused':
168  default:
169  $validation = $this->msg( 'apiwarn-validationfailed-badpref' );
170  break;
171  }
172  if ( $validation === true ) {
173  $this->setPreference( $key, $value );
174  $changed = true;
175  } else {
176  $this->addWarning( [ 'apiwarn-validationfailed', wfEscapeWikiText( $key ), $validation ] );
177  }
178  }
179 
180  if ( $changed ) {
181  $this->commitChanges();
182  }
183 
184  $this->getResult()->addValue( null, $this->getModuleName(), 'success' );
185  }
186 
192  protected function getUserForUpdates() {
193  if ( !$this->userForUpdates ) {
194  $this->userForUpdates = $this->getUser()->getInstanceForUpdate();
195  }
196 
197  return $this->userForUpdates;
198  }
199 
204  protected function getPreferences() {
205  return $this->preferencesFactory->getFormDescriptor( $this->getUserForUpdates(),
206  $this->getContext() );
207  }
208 
212  protected function resetPreferences( array $kinds ) {
213  $this->userOptionsManager->resetOptions( $this->getUserForUpdates(), $this->getContext(), $kinds );
214  }
215 
222  protected function setPreference( $preference, $value ) {
223  $this->userOptionsManager->setOption( $this->getUserForUpdates(), $preference, $value );
224  }
225 
229  protected function commitChanges() {
230  $this->getUserForUpdates()->saveSettings();
231  }
232 
233  public function mustBePosted() {
234  return true;
235  }
236 
237  public function isWriteMode() {
238  return true;
239  }
240 
241  public function getAllowedParams() {
242  $optionKinds = $this->userOptionsManager->listOptionKinds();
243  $optionKinds[] = 'all';
244 
245  return [
246  'reset' => false,
247  'resetkinds' => [
248  ParamValidator::PARAM_TYPE => $optionKinds,
249  ParamValidator::PARAM_DEFAULT => 'all',
250  ParamValidator::PARAM_ISMULTI => true
251  ],
252  'change' => [
253  ParamValidator::PARAM_ISMULTI => true,
254  ],
255  'optionname' => [
256  ParamValidator::PARAM_TYPE => 'string',
257  ],
258  'optionvalue' => [
259  ParamValidator::PARAM_TYPE => 'string',
260  ],
261  ];
262  }
263 
264  public function needsToken() {
265  return 'csrf';
266  }
267 
268  public function getHelpUrls() {
269  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Options';
270  }
271 
272  protected function getExamplesMessages() {
273  return [
274  'action=options&reset=&token=123ABC'
275  => 'apihelp-options-example-reset',
276  'action=options&change=skin=vector|hideminor=1&token=123ABC'
277  => 'apihelp-options-example-change',
278  'action=options&reset=&change=skin=monobook&optionname=nickname&' .
279  'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC'
280  => 'apihelp-options-example-complex',
281  ];
282  }
283 }
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:57
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition: ApiBase.php:1455
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:1561
getResult()
Get the result object.
Definition: ApiBase.php:630
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:766
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1373
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:499
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:712
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:55
API module that facilitates the changing of user's preferences.
Definition: ApiOptions.php:36
resetPreferences(array $kinds)
Definition: ApiOptions.php:212
commitChanges()
Applies changes to user preferences.
Definition: ApiOptions.php:229
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiOptions.php:237
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiOptions.php:272
setPreference( $preference, $value)
Sets one user preference to be applied by commitChanges()
Definition: ApiOptions.php:222
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiOptions.php:264
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiOptions.php:268
execute()
Changes preferences of the current user.
Definition: ApiOptions.php:71
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiOptions.php:241
__construct(ApiMain $main, $action, UserOptionsManager $userOptionsManager=null, PreferencesFactory $preferencesFactory=null)
Definition: ApiOptions.php:52
getPreferences()
Returns preferences form descriptor.
Definition: ApiOptions.php:204
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiOptions.php:233
getUserForUpdates()
Load the user from the primary to reduce CAS errors on double post (T95839)
Definition: ApiOptions.php:192
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:150
PSR-3 logger instance factory.
Service locator for MediaWiki core services.
This is the default implementation of PreferencesFactory.
A service class to control user options.
static numParam( $num)
Definition: Message.php:1144
Service for formatting and validating API parameters.
A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a given ...
return true
Definition: router.php:90