Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslateSandbox.php
Go to the documentation of this file.
1<?php
10use MediaWiki\Auth\AuthenticationRequest;
11use MediaWiki\Auth\AuthenticationResponse;
12use MediaWiki\Auth\AuthManager;
18use MediaWiki\MediaWikiServices;
19use Wikimedia\ScopedCallback;
20
26
31 public const USER_CREATION_FAILURE = 56739;
32
41 public static function addUser( $name, $email, $password ) {
42 $user = User::newFromName( $name, 'creatable' );
43
44 if ( !$user ) {
45 throw new InvalidArgumentException( 'Invalid user name' );
46 }
47
48 $data = [
49 'username' => $user->getName(),
50 'password' => $password,
51 'retype' => $password,
52 'email' => $email,
53 'realname' => '',
54 ];
55
56 $services = MediaWikiServices::getInstance();
57
58 $permissionManager = $services->getPermissionManager();
59 $creator = TranslateUserManager::getUser();
60 $guard = $permissionManager->addTemporaryUserRights( $creator, 'createaccount' );
61
62 $authManager = $services->getAuthManager();
63 $reqs = $authManager->getAuthenticationRequests( AuthManager::ACTION_CREATE );
64 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
65 $res = $authManager->beginAccountCreation( $creator, $reqs, 'null:' );
66
67 ScopedCallback::consume( $guard );
68
69 switch ( $res->status ) {
70 case AuthenticationResponse::PASS:
71 break;
72 case AuthenticationResponse::FAIL:
73 // Unless things are misconfigured, this will handle errors such as username taken,
74 // invalid user name or too short password. The WebAPI is prechecking these to
75 // provide nicer error messages.
76 $reason = $res->message->inLanguage( 'en' )->useDatabase( false )->text();
77 throw new RuntimeException(
78 "Account creation failed: $reason",
79 self::USER_CREATION_FAILURE
80 );
81 default:
82 // A provider requested further user input. Abort but clean up first if it was a
83 // secondary provider (in which case the user was created).
84 if ( $user->getId() ) {
85 self::deleteUser( $user, 'force' );
86 }
87
88 throw new RuntimeException(
89 'AuthManager does not support such simplified account creation'
90 );
91 }
92
93 // group-translate-sandboxed group-translate-sandboxed-member
94 $services->getUserGroupManager()->addUserToGroup( $user, 'translate-sandboxed' );
95
96 return $user;
97 }
98
106 public static function deleteUser( User $user, $force = '' ) {
107 $uid = $user->getId();
108 $actorId = $user->getActorId();
109
110 if ( $force !== 'force' && !self::isSandboxed( $user ) ) {
111 throw new UserNotSandboxedException();
112 }
113
114 // Delete from database
115 $dbw = wfGetDB( DB_PRIMARY );
116 $dbw->delete( 'user', [ 'user_id' => $uid ], __METHOD__ );
117 $dbw->delete( 'user_groups', [ 'ug_user' => $uid ], __METHOD__ );
118 $dbw->delete( 'user_properties', [ 'up_user' => $uid ], __METHOD__ );
119
120 MediaWikiServices::getInstance()->getActorStore()->deleteActor( $user, $dbw );
121
122 // Assume no joins are needed for logging or recentchanges
123 $dbw->delete( 'logging', [ 'log_actor' => $actorId ], __METHOD__ );
124 $dbw->delete( 'recentchanges', [ 'rc_actor' => $actorId ], __METHOD__ );
125
126 // Update the site stats
127 $statsUpdate = SiteStatsUpdate::factory( [ 'users' => -1 ] );
128 $statsUpdate->doUpdate();
129
130 // If someone tries to access still object still, they will get anon user
131 // data.
132 $user->clearInstanceCache( 'defaults' );
133
134 // Nobody should access the user by id anymore, but in case they do, purge
135 // the cache so they wont get stale data
136 $user->invalidateCache();
137 }
138
143 public static function getUsers() {
144 $dbw = Utilities::getSafeReadDB();
145 $userQuery = User::getQueryInfo();
146 $tables = array_merge( $userQuery['tables'], [ 'user_groups' ] );
147 $fields = $userQuery['fields'];
148 $conds = [
149 'ug_group' => 'translate-sandboxed',
150 ];
151 $joins = [
152 'user_groups' => [ 'JOIN', 'ug_user = user_id' ],
153 ] + $userQuery['joins'];
154
155 $res = $dbw->select( $tables, $fields, $conds, __METHOD__, [], $joins );
156
157 return UserArray::newFromResult( $res );
158 }
159
165 public static function promoteUser( User $user ) {
166 global $wgTranslateSandboxPromotedGroup;
167
168 if ( !self::isSandboxed( $user ) ) {
169 throw new UserNotSandboxedException();
170 }
171
172 $mwServices = MediaWikiServices::getInstance();
173
174 $userGroupManager = $mwServices->getUserGroupManager();
175 $userGroupManager->removeUserFromGroup( $user, 'translate-sandboxed' );
176
177 if ( $wgTranslateSandboxPromotedGroup ) {
178 $userGroupManager->addUserToGroup( $user, $wgTranslateSandboxPromotedGroup );
179 }
180
181 $userOptionsManager = $mwServices->getUserOptionsManager();
182 $userOptionsManager->setOption( $user, 'translate-sandbox-reminders', '' );
183 $userOptionsManager->saveOptions( $user );
184
185 $hookRunner = Services::getInstance()->getHookRunner();
186 $hookRunner->onTranslate_TranslatorSandbox_UserPromoted( $user );
187 }
188
197 public static function sendEmail( User $sender, User $target, $type ) {
198 global $wgEmergencyContact;
199
200 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
201 $targetLang = $userOptionsLookup->getOption( $target, 'language' );
202
203 switch ( $type ) {
204 case 'reminder':
205 if ( !self::isSandboxed( $target ) ) {
206 throw new UserNotSandboxedException();
207 }
208
209 $subjectMsg = 'tsb-reminder-title-generic';
210 $bodyMsg = 'tsb-reminder-content-generic';
211 $targetSpecialPage = 'TranslationStash';
212
213 break;
214 case 'promotion':
215 $subjectMsg = 'tsb-email-promoted-subject';
216 $bodyMsg = 'tsb-email-promoted-body';
217 $targetSpecialPage = 'Translate';
218
219 break;
220 case 'rejection':
221 $subjectMsg = 'tsb-email-rejected-subject';
222 $bodyMsg = 'tsb-email-rejected-body';
223 $targetSpecialPage = 'TwnMainPage';
224
225 break;
226 default:
227 throw new UnexpectedValueException( "'$type' is an invalid type of translate sandbox email" );
228 }
229
230 $subject = wfMessage( $subjectMsg )->inLanguage( $targetLang )->text();
231 $body = wfMessage(
232 $bodyMsg,
233 $target->getName(),
234 SpecialPage::getTitleFor( $targetSpecialPage )->getCanonicalURL(),
235 $sender->getName()
236 )->inLanguage( $targetLang )->text();
237
238 $params = [
239 'user' => $target->getId(),
240 'to' => MailAddress::newFromUser( $target ),
241 'from' => new MailAddress( $wgEmergencyContact ),
242 'replyto' => new MailAddress( $wgEmergencyContact ),
243 'subj' => $subject,
244 'body' => $body,
245 'emailType' => $type,
246 ];
247
248 $services = MediaWikiServices::getInstance();
249 $userOptionsManager = $services->getUserOptionsManager();
250
251 $reminders = $userOptionsManager->getOption( $target, 'translate-sandbox-reminders' );
252 $reminders = $reminders ? explode( '|', $reminders ) : [];
253 $reminders[] = wfTimestamp();
254
255 $userOptionsManager->setOption( $target, 'translate-sandbox-reminders', implode( '|', $reminders ) );
256 $userOptionsManager->saveOptions( $target );
257
258 $services->getJobQueueGroup()->push( TranslateSandboxEmailJob::newJob( $params ) );
259 }
260
267 public static function isSandboxed( User $user ) {
268 $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
269 return in_array( 'translate-sandboxed', $userGroupManager->getUserGroups( $user ), true );
270 }
271
278 public static function enforcePermissions( User $user, array &$rights ): bool {
279 global $wgTranslateUseSandbox;
280
281 if ( !$wgTranslateUseSandbox ) {
282 return true;
283 }
284
285 if ( !self::isSandboxed( $user ) ) {
286 return true;
287 }
288
289 // right-translate-sandboxaction action-translate-sandboxaction
290 $rights = [
291 'editmyoptions',
292 'editmyprivateinfo',
293 'read',
294 'readapi',
295 'translate-sandboxaction',
296 'viewmyprivateinfo',
297 'writeapi',
298 ];
299
300 // Do not let other hooks add more actions
301 return false;
302 }
303
305 public static function onGetPreferences( $user, &$preferences ) {
306 $preferences['translate-sandbox'] = $preferences['translate-sandbox-reminders'] =
307 [ 'type' => 'api' ];
308
309 return true;
310 }
311
320 public static function onApiCheckCanExecute( ApiBase $module, User $user, &$message ) {
321 $inclusionList = [
322 // Obviously this is needed to get out of the sandbox
323 TranslationStashActionApi::class,
324 // Used by UniversalLanguageSelector for example
325 'ApiOptions'
326 ];
327
328 if ( self::isSandboxed( $user ) ) {
329 $class = get_class( $module );
330 if ( $module->isWriteMode() && !in_array( $class, $inclusionList, true ) ) {
331 $message = ApiMessage::create( 'apierror-writeapidenied' );
332 return false;
333 }
334 }
335
336 return true;
337 }
338}
Minimal service container.
Definition Services.php:44
WebAPI module for storing translations for users who are in a sandbox.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
Utility class for the sandbox feature of Translate.
static sendEmail(User $sender, User $target, $type)
Sends a reminder to the user.
static addUser( $name, $email, $password)
Adds a new user without doing much validation.
static enforcePermissions(User $user, array &$rights)
Hook: UserGetRights.
static promoteUser(User $user)
Removes the user from the sandbox.
static deleteUser(User $user, $force='')
Deletes a sandboxed user without doing much validation.
static isSandboxed(User $user)
Shortcut for checking if given user is in the sandbox.
static onApiCheckCanExecute(ApiBase $module, User $user, &$message)
Inclusion listing for certain API modules.
static onGetPreferences( $user, &$preferences)
Hook: onGetPreferences.
static getUsers()
Get all sandboxed users.
const USER_CREATION_FAILURE
Custom exception code used when user creation fails in order to differentiate between other exception...