Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 133 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
TranslatorSandboxActionApi | |
0.00% |
0 / 133 |
|
0.00% |
0 / 10 |
992 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
doCreate | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
90 | |||
doDelete | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
12 | |||
doPromote | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
doRemind | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
createUserPage | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
isWriteMode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
needsToken | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllowedParams | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\TranslatorSandbox; |
5 | |
6 | use ManualLogEntry; |
7 | use MediaWiki\Api\ApiBase; |
8 | use MediaWiki\Api\ApiMain; |
9 | use MediaWiki\CommentStore\CommentStoreComment; |
10 | use MediaWiki\Config\ServiceOptions; |
11 | use MediaWiki\Content\ContentHandler; |
12 | use MediaWiki\Json\FormatJson; |
13 | use MediaWiki\Page\WikiPageFactory; |
14 | use MediaWiki\Parser\Sanitizer; |
15 | use MediaWiki\Revision\SlotRecord; |
16 | use MediaWiki\User\Options\UserOptionsLookup; |
17 | use MediaWiki\User\Options\UserOptionsManager; |
18 | use MediaWiki\User\User; |
19 | use MediaWiki\User\UserFactory; |
20 | use MediaWiki\User\UserNameUtils; |
21 | use RuntimeException; |
22 | use Wikimedia\ParamValidator\ParamValidator; |
23 | |
24 | /** |
25 | * WebAPI for the sandbox feature of Translate. |
26 | * @author Niklas Laxström |
27 | * @license GPL-2.0-or-later |
28 | * @ingroup API TranslateAPI |
29 | */ |
30 | class 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 | |
221 | /** Create a user page for a user with a babel template based on the signup preferences. */ |
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 | } |