ApiOptions.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <?php
  2. /**
  3. * Copyright © 2012 Szymon Świerkosz beau@adres.pl
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. use MediaWiki\MediaWikiServices;
  23. /**
  24. * API module that facilitates the changing of user's preferences.
  25. * Requires API write mode to be enabled.
  26. *
  27. * @ingroup API
  28. */
  29. class ApiOptions extends ApiBase {
  30. /** @var User User account to modify */
  31. private $userForUpdates;
  32. /**
  33. * Changes preferences of the current user.
  34. */
  35. public function execute() {
  36. $user = $this->getUserForUpdates();
  37. if ( !$user || $user->isAnon() ) {
  38. $this->dieWithError(
  39. [ 'apierror-mustbeloggedin', $this->msg( 'action-editmyoptions' ) ], 'notloggedin'
  40. );
  41. }
  42. $this->checkUserRightsAny( 'editmyoptions' );
  43. $params = $this->extractRequestParams();
  44. $changed = false;
  45. if ( isset( $params['optionvalue'] ) && !isset( $params['optionname'] ) ) {
  46. $this->dieWithError( [ 'apierror-missingparam', 'optionname' ] );
  47. }
  48. $resetKinds = $params['resetkinds'];
  49. if ( !$params['reset'] ) {
  50. $resetKinds = [];
  51. }
  52. $changes = [];
  53. if ( $params['change'] ) {
  54. foreach ( $params['change'] as $entry ) {
  55. $array = explode( '=', $entry, 2 );
  56. $changes[$array[0]] = $array[1] ?? null;
  57. }
  58. }
  59. if ( isset( $params['optionname'] ) ) {
  60. $newValue = $params['optionvalue'] ?? null;
  61. $changes[$params['optionname']] = $newValue;
  62. }
  63. Hooks::run( 'ApiOptions', [ $this, $user, $changes, $resetKinds ] );
  64. if ( $resetKinds ) {
  65. $this->resetPreferences( $resetKinds );
  66. $changed = true;
  67. }
  68. if ( !$changed && !count( $changes ) ) {
  69. $this->dieWithError( 'apierror-nochanges' );
  70. }
  71. $prefs = $this->getPreferences();
  72. $prefsKinds = $user->getOptionKinds( $this->getContext(), $changes );
  73. $htmlForm = null;
  74. foreach ( $changes as $key => $value ) {
  75. switch ( $prefsKinds[$key] ) {
  76. case 'registered':
  77. // Regular option.
  78. if ( $value === null ) {
  79. // Reset it
  80. $validation = true;
  81. } else {
  82. // Validate
  83. if ( $htmlForm === null ) {
  84. // We need a dummy HTMLForm for the validate callback...
  85. $htmlForm = new HTMLForm( [], $this );
  86. }
  87. $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
  88. $validation = $field->validate( $value, $user->getOptions() );
  89. }
  90. break;
  91. case 'registered-multiselect':
  92. case 'registered-checkmatrix':
  93. // A key for a multiselect or checkmatrix option.
  94. $validation = true;
  95. $value = $value !== null ? (bool)$value : null;
  96. break;
  97. case 'userjs':
  98. // Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
  99. if ( strlen( $key ) > 255 ) {
  100. $validation = $this->msg( 'apiwarn-validationfailed-keytoolong', Message::numParam( 255 ) );
  101. } elseif ( preg_match( '/[^a-zA-Z0-9_-]/', $key ) !== 0 ) {
  102. $validation = $this->msg( 'apiwarn-validationfailed-badchars' );
  103. } else {
  104. $validation = true;
  105. }
  106. break;
  107. case 'special':
  108. $validation = $this->msg( 'apiwarn-validationfailed-cannotset' );
  109. break;
  110. case 'unused':
  111. default:
  112. $validation = $this->msg( 'apiwarn-validationfailed-badpref' );
  113. break;
  114. }
  115. if ( $validation === true ) {
  116. $this->setPreference( $key, $value );
  117. $changed = true;
  118. } else {
  119. $this->addWarning( [ 'apiwarn-validationfailed', wfEscapeWikiText( $key ), $validation ] );
  120. }
  121. }
  122. if ( $changed ) {
  123. $this->commitChanges();
  124. }
  125. $this->getResult()->addValue( null, $this->getModuleName(), 'success' );
  126. }
  127. /**
  128. * Load the user from the master to reduce CAS errors on double post (T95839)
  129. *
  130. * @return null|User
  131. */
  132. protected function getUserForUpdates() {
  133. if ( !$this->userForUpdates ) {
  134. $this->userForUpdates = $this->getUser()->getInstanceForUpdate();
  135. }
  136. return $this->userForUpdates;
  137. }
  138. /**
  139. * Returns preferences form descriptor
  140. * @return mixed[][]
  141. */
  142. protected function getPreferences() {
  143. $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
  144. return $preferencesFactory->getFormDescriptor( $this->getUserForUpdates(),
  145. $this->getContext() );
  146. }
  147. /**
  148. * @param string[] $kinds One or more types returned by User::listOptionKinds() or 'all'
  149. */
  150. protected function resetPreferences( array $kinds ) {
  151. $this->getUserForUpdates()->resetOptions( $kinds, $this->getContext() );
  152. }
  153. /**
  154. * Sets one user preference to be applied by commitChanges()
  155. *
  156. * @param string $preference
  157. * @param mixed $value
  158. */
  159. protected function setPreference( $preference, $value ) {
  160. $this->getUserForUpdates()->setOption( $preference, $value );
  161. }
  162. /**
  163. * Applies changes to user preferences
  164. */
  165. protected function commitChanges() {
  166. $this->getUserForUpdates()->saveSettings();
  167. }
  168. public function mustBePosted() {
  169. return true;
  170. }
  171. public function isWriteMode() {
  172. return true;
  173. }
  174. public function getAllowedParams() {
  175. $optionKinds = User::listOptionKinds();
  176. $optionKinds[] = 'all';
  177. return [
  178. 'reset' => false,
  179. 'resetkinds' => [
  180. ApiBase::PARAM_TYPE => $optionKinds,
  181. ApiBase::PARAM_DFLT => 'all',
  182. ApiBase::PARAM_ISMULTI => true
  183. ],
  184. 'change' => [
  185. ApiBase::PARAM_ISMULTI => true,
  186. ],
  187. 'optionname' => [
  188. ApiBase::PARAM_TYPE => 'string',
  189. ],
  190. 'optionvalue' => [
  191. ApiBase::PARAM_TYPE => 'string',
  192. ],
  193. ];
  194. }
  195. public function needsToken() {
  196. return 'csrf';
  197. }
  198. public function getHelpUrls() {
  199. return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Options';
  200. }
  201. protected function getExamplesMessages() {
  202. return [
  203. 'action=options&reset=&token=123ABC'
  204. => 'apihelp-options-example-reset',
  205. 'action=options&change=skin=vector|hideminor=1&token=123ABC'
  206. => 'apihelp-options-example-change',
  207. 'action=options&reset=&change=skin=monobook&optionname=nickname&' .
  208. 'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC'
  209. => 'apihelp-options-example-complex',
  210. ];
  211. }
  212. }