Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslatorSandboxActionApi.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TranslatorSandbox;
5
6use ManualLogEntry;
7use MediaWiki\Api\ApiBase;
8use MediaWiki\Api\ApiMain;
9use MediaWiki\CommentStore\CommentStoreComment;
10use MediaWiki\Config\ServiceOptions;
11use MediaWiki\Content\ContentHandler;
12use MediaWiki\Json\FormatJson;
13use MediaWiki\Page\WikiPageFactory;
14use MediaWiki\Parser\Sanitizer;
15use MediaWiki\Revision\SlotRecord;
16use MediaWiki\User\Options\UserOptionsLookup;
17use MediaWiki\User\Options\UserOptionsManager;
18use MediaWiki\User\User;
19use MediaWiki\User\UserFactory;
20use MediaWiki\User\UserNameUtils;
21use RuntimeException;
22use Wikimedia\ParamValidator\ParamValidator;
23
30class TranslatorSandboxActionApi extends ApiBase {
31 private UserFactory $userFactory;
32 private UserNameUtils $userNameUtils;
33 private UserOptionsManager $userOptionsManager;
34 private WikiPageFactory $wikiPageFactory;
35 private UserOptionsLookup $userOptionsLookup;
36 private TranslateSandbox $translateSandbox;
37 private bool $isSandboxEnabled;
38 public const CONSTRUCTOR_OPTIONS = [
39 'TranslateUseSandbox',
40 ];
41
42 public function __construct(
43 ApiMain $mainModule,
44 string $moduleName,
45 UserFactory $userFactory,
46 UserNameUtils $userNameUtils,
47 UserOptionsManager $userOptionsManager,
48 WikiPageFactory $wikiPageFactory,
49 UserOptionsLookup $userOptionsLookup,
50 TranslateSandbox $translateSandbox,
51 ServiceOptions $options
52 ) {
53 parent::__construct( $mainModule, $moduleName );
54 $this->userFactory = $userFactory;
55 $this->userNameUtils = $userNameUtils;
56 $this->userOptionsManager = $userOptionsManager;
57 $this->wikiPageFactory = $wikiPageFactory;
58 $this->userOptionsLookup = $userOptionsLookup;
59 $this->translateSandbox = $translateSandbox;
60 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
61 $this->isSandboxEnabled = $options->get( 'TranslateUseSandbox' );
62 }
63
64 public function execute(): void {
65 if ( !$this->isSandboxEnabled ) {
66 $this->dieWithError( 'apierror-translate-sandboxdisabled', 'sandboxdisabled' );
67 }
68
69 $params = $this->extractRequestParams();
70 switch ( $params['do'] ) {
71 case 'create':
72 $this->doCreate();
73 break;
74 case 'delete':
75 $this->doDelete();
76 break;
77 case 'promote':
78 $this->doPromote();
79 break;
80 case 'remind':
81 $this->doRemind();
82 break;
83 default:
84 $this->dieWithError( [ 'apierror-badparameter', 'do' ] );
85 }
86 }
87
88 private function doCreate(): void {
89 $params = $this->extractRequestParams();
90
91 // Do validations
92 foreach ( explode( '|', 'username|password|email' ) as $field ) {
93 if ( !isset( $params[$field] ) ) {
94 $this->dieWithError( [ 'apierror-missingparam', $field ], 'missingparam' );
95 }
96 }
97
98 $username = $params['username'];
99
100 $canonicalName = $this->userNameUtils->getCanonical( $username, UserNameUtils::RIGOR_CREATABLE );
101
102 if ( $canonicalName === false ) {
103 $this->dieWithError( 'noname', 'invalidusername' );
104 }
105
106 $user = $this->userFactory->newFromName( $username );
107 if ( $user->getId() !== 0 ) {
108 $this->dieWithError( 'userexists', 'nonfreeusername' );
109 }
110
111 $password = $params['password'];
112 $passwordValidityStatus = $user->checkPasswordValidity( $password );
113 if ( !$passwordValidityStatus->isGood() ) {
114 $this->dieStatus( $passwordValidityStatus );
115 }
116
117 $email = $params['email'];
118 if ( !Sanitizer::validateEmail( $email ) ) {
119 $this->dieWithError( 'invalidemailaddress', 'invalidemail' );
120 }
121
122 try {
123 $user = $this->translateSandbox->addUser( $username, $email, $password );
124 } catch ( RuntimeException $e ) {
125 // Do not log this error as it might leak private information
126 if ( $e->getCode() === TranslateSandbox::USER_CREATION_FAILURE ) {
127 $this->dieWithError( 'apierror-translate-sandbox-user-add' );
128 }
129
130 throw $e;
131 }
132
133 $output = [ 'user' => [
134 'name' => $user->getName(),
135 'id' => $user->getId(),
136 ] ];
137
138 $this->userOptionsManager->setOption( $user, 'language', $this->getContext()->getLanguage()->getCode() );
139 $this->userOptionsManager->saveOptions( $user );
140
141 $this->getResult()->addValue( null, $this->getModuleName(), $output );
142 }
143
144 private function doDelete(): void {
145 $this->checkUserRightsAny( 'translate-sandboxmanage' );
146
147 $params = $this->extractRequestParams();
148
149 foreach ( $params['userid'] as $userId ) {
150 $user = $this->userFactory->newFromId( $userId );
151 $userPage = $user->getUserPage();
152
153 $this->translateSandbox->sendEmail( $this->getUser(), $user, 'rejection' );
154
155 try {
156 $this->translateSandbox->deleteUser( $user );
157 } catch ( UserNotSandboxedException $e ) {
158 $this->dieWithError(
159 [ 'apierror-translate-sandbox-invalidparam', wfEscapeWikiText( $e->getMessage() ) ],
160 'invalidparam'
161 );
162 }
163
164 $logEntry = new ManualLogEntry( 'translatorsandbox', 'rejected' );
165 $logEntry->setPerformer( $this->getUser() );
166 $logEntry->setTarget( $userPage );
167 $logId = $logEntry->insert();
168 $logEntry->publish( $logId );
169 }
170 }
171
172 private function doPromote(): void {
173 $this->checkUserRightsAny( 'translate-sandboxmanage' );
174
175 $params = $this->extractRequestParams();
176
177 foreach ( $params['userid'] as $userId ) {
178 $user = $this->userFactory->newFromId( $userId );
179
180 try {
181 $this->translateSandbox->promoteUser( $user );
182 } catch ( UserNotSandboxedException $e ) {
183 $this->dieWithError(
184 [ 'apierror-translate-sandbox-invalidparam', wfEscapeWikiText( $e->getMessage() ) ],
185 'invalidparam'
186 );
187 }
188
189 $this->translateSandbox->sendEmail( $this->getUser(), $user, 'promotion' );
190
191 $logEntry = new ManualLogEntry( 'translatorsandbox', 'promoted' );
192 $logEntry->setPerformer( $this->getUser() );
193 $logEntry->setTarget( $user->getUserPage() );
194 $logEntry->setParameters( [
195 '4::userid' => $user->getId(),
196 ] );
197 $logId = $logEntry->insert();
198 $logEntry->publish( $logId );
199
200 $this->createUserPage( $user );
201 }
202 }
203
204 private function doRemind(): void {
205 $params = $this->extractRequestParams();
206
207 foreach ( $params['userid'] as $userId ) {
208 $target = $this->userFactory->newFromId( $userId );
209
210 try {
211 $this->translateSandbox->sendEmail( $this->getUser(), $target, 'reminder' );
212 } catch ( UserNotSandboxedException $e ) {
213 $this->dieWithError(
214 [ 'apierror-translate-sandbox-invalidparam', wfEscapeWikiText( $e->getMessage() ) ],
215 'invalidparam'
216 );
217 }
218 }
219 }
220
222 private function createUserPage( User $user ): void {
223 $userPage = $user->getUserPage();
224
225 if ( $userPage->exists() ) {
226 return;
227 }
228
229 $languagePreferences = FormatJson::decode(
230 $this->userOptionsLookup->getOption( $user, 'translate-sandbox' ),
231 true
232 );
233 $languages = implode( '|', $languagePreferences[ 'languages' ] ?? [] );
234 $babelText = "{{#babel:$languages}}";
235 $summary = $this->msg( 'tsb-create-user-page' )->inContentLanguage()->text();
236
237 $page = $this->wikiPageFactory->newFromTitle( $userPage );
238 $content = ContentHandler::makeContent( $babelText, $userPage );
239
240 $page->newPageUpdater( $user )
241 ->setContent( SlotRecord::MAIN, $content )
242 ->saveRevision( CommentStoreComment::newUnsavedComment( trim( $summary ) ), EDIT_NEW );
243 }
244
245 public function isWriteMode(): bool {
246 return true;
247 }
248
249 public function needsToken(): string {
250 return 'csrf';
251 }
252
253 protected function getAllowedParams(): array {
254 return [
255 'do' => [
256 ParamValidator::PARAM_TYPE => [ 'create', 'delete', 'promote', 'remind' ],
257 ParamValidator::PARAM_REQUIRED => true,
258 ],
259 'userid' => [
260 ParamValidator::PARAM_TYPE => 'integer',
261 ParamValidator::PARAM_DEFAULT => 0,
262 ParamValidator::PARAM_ISMULTI => true,
263 ],
264 'username' => [ ParamValidator::PARAM_TYPE => 'string' ],
265 'password' => [ ParamValidator::PARAM_TYPE => 'string' ],
266 'email' => [ ParamValidator::PARAM_TYPE => 'string' ],
267 ];
268 }
269}
Utility class for the sandbox feature of Translate.
const USER_CREATION_FAILURE
Custom exception code used when user creation fails in order to differentiate between other exception...