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;
16use MediaWiki\MediaWikiServices;
17use Wikimedia\ScopedCallback;
18
33 public static function addUser( $name, $email, $password ) {
34 $user = User::newFromName( $name, 'creatable' );
35
36 if ( !$user ) {
37 throw new MWException( 'Invalid user name' );
38 }
39
40 $data = [
41 'username' => $user->getName(),
42 'password' => $password,
43 'retype' => $password,
44 'email' => $email,
45 'realname' => '',
46 ];
47
48 $services = MediaWikiServices::getInstance();
49
50 $permissionManager = $services->getPermissionManager();
51 $creator = TranslateUserManager::getUser();
52 $guard = $permissionManager->addTemporaryUserRights( $creator, 'createaccount' );
53
54 $authManager = $services->getAuthManager();
55 $reqs = $authManager->getAuthenticationRequests( AuthManager::ACTION_CREATE );
56 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
57 $res = $authManager->beginAccountCreation( $creator, $reqs, 'null:' );
58
59 ScopedCallback::consume( $guard );
60
61 switch ( $res->status ) {
62 case AuthenticationResponse::PASS:
63 break;
64 case AuthenticationResponse::FAIL:
65 // Unless things are misconfigured, this will handle errors such as username taken,
66 // invalid user name or too short password. The WebAPI is prechecking these to
67 // provide nicer error messages.
68 $reason = $res->message->inLanguage( 'en' )->useDatabase( false )->text();
69 throw new MWException( "Account creation failed: $reason" );
70 default:
71 // A provider requested further user input. Abort but clean up first if it was a
72 // secondary provider (in which case the user was created).
73 if ( $user->getId() ) {
74 self::deleteUser( $user, 'force' );
75 }
76
77 throw new MWException(
78 'AuthManager does not support such simplified account creation'
79 );
80 }
81
82 // group-translate-sandboxed group-translate-sandboxed-member
83 $services->getUserGroupManager()->addUserToGroup( $user, 'translate-sandboxed' );
84
85 return $user;
86 }
87
95 public static function deleteUser( User $user, $force = '' ) {
96 $uid = $user->getId();
97 $actorId = $user->getActorId();
98
99 if ( $force !== 'force' && !self::isSandboxed( $user ) ) {
100 throw new MWException( 'Not a sandboxed user' );
101 }
102
103 // Delete from database
104 $dbw = wfGetDB( DB_PRIMARY );
105 $dbw->delete( 'user', [ 'user_id' => $uid ], __METHOD__ );
106 $dbw->delete( 'user_groups', [ 'ug_user' => $uid ], __METHOD__ );
107 $dbw->delete( 'user_properties', [ 'up_user' => $uid ], __METHOD__ );
108
109 MediaWikiServices::getInstance()->getActorStore()->deleteActor( $user, $dbw );
110
111 // Assume no joins are needed for logging or recentchanges
112 $dbw->delete( 'logging', [ 'log_actor' => $actorId ], __METHOD__ );
113 $dbw->delete( 'recentchanges', [ 'rc_actor' => $actorId ], __METHOD__ );
114
115 // Update the site stats
116 $statsUpdate = SiteStatsUpdate::factory( [ 'users' => -1 ] );
117 $statsUpdate->doUpdate();
118
119 // If someone tries to access still object still, they will get anon user
120 // data.
121 $user->clearInstanceCache( 'defaults' );
122
123 // Nobody should access the user by id anymore, but in case they do, purge
124 // the cache so they wont get stale data
125 $user->invalidateCache();
126 }
127
132 public static function getUsers() {
133 $dbw = Utilities::getSafeReadDB();
134 $userQuery = User::getQueryInfo();
135 $tables = array_merge( $userQuery['tables'], [ 'user_groups' ] );
136 $fields = $userQuery['fields'];
137 $conds = [
138 'ug_group' => 'translate-sandboxed',
139 ];
140 $joins = [
141 'user_groups' => [ 'JOIN', 'ug_user = user_id' ],
142 ] + $userQuery['joins'];
143
144 $res = $dbw->select( $tables, $fields, $conds, __METHOD__, [], $joins );
145
146 return UserArray::newFromResult( $res );
147 }
148
154 public static function promoteUser( User $user ) {
155 global $wgTranslateSandboxPromotedGroup;
156
157 if ( !self::isSandboxed( $user ) ) {
158 throw new MWException( 'Not a sandboxed user' );
159 }
160
161 $services = MediaWikiServices::getInstance();
162
163 $userGroupManager = $services->getUserGroupManager();
164 $userGroupManager->removeUserFromGroup( $user, 'translate-sandboxed' );
165
166 if ( $wgTranslateSandboxPromotedGroup ) {
167 $userGroupManager->addUserToGroup( $user, $wgTranslateSandboxPromotedGroup );
168 }
169
170 $userOptionsManager = $services->getUserOptionsManager();
171 $userOptionsManager->setOption( $user, 'translate-sandbox-reminders', '' );
172 $userOptionsManager->saveOptions( $user );
173 }
174
183 public static function sendEmail( User $sender, User $target, $type ) {
184 global $wgEmergencyContact;
185
186 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
187 $targetLang = $userOptionsLookup->getOption( $target, 'language' );
188
189 switch ( $type ) {
190 case 'reminder':
191 if ( !self::isSandboxed( $target ) ) {
192 throw new MWException( 'Not a sandboxed user' );
193 }
194
195 $subjectMsg = 'tsb-reminder-title-generic';
196 $bodyMsg = 'tsb-reminder-content-generic';
197 $targetSpecialPage = 'TranslationStash';
198
199 break;
200 case 'promotion':
201 $subjectMsg = 'tsb-email-promoted-subject';
202 $bodyMsg = 'tsb-email-promoted-body';
203 $targetSpecialPage = 'Translate';
204
205 break;
206 case 'rejection':
207 $subjectMsg = 'tsb-email-rejected-subject';
208 $bodyMsg = 'tsb-email-rejected-body';
209 $targetSpecialPage = 'TwnMainPage';
210
211 break;
212 default:
213 throw new MWException( "'$type' is an invalid type of translate sandbox email" );
214 }
215
216 $subject = wfMessage( $subjectMsg )->inLanguage( $targetLang )->text();
217 $body = wfMessage(
218 $bodyMsg,
219 $target->getName(),
220 SpecialPage::getTitleFor( $targetSpecialPage )->getCanonicalURL(),
221 $sender->getName()
222 )->inLanguage( $targetLang )->text();
223
224 $params = [
225 'user' => $target->getId(),
226 'to' => MailAddress::newFromUser( $target ),
227 'from' => new MailAddress( $wgEmergencyContact ),
228 'replyto' => new MailAddress( $wgEmergencyContact ),
229 'subj' => $subject,
230 'body' => $body,
231 'emailType' => $type,
232 ];
233
234 $services = MediaWikiServices::getInstance();
235 $userOptionsManager = $services->getUserOptionsManager();
236
237 $reminders = $userOptionsManager->getOption( $target, 'translate-sandbox-reminders' );
238 $reminders = $reminders ? explode( '|', $reminders ) : [];
239 $reminders[] = wfTimestamp();
240
241 $userOptionsManager->setOption( $target, 'translate-sandbox-reminders', implode( '|', $reminders ) );
242 $userOptionsManager->saveOptions( $target );
243
244 $services->getJobQueueGroup()->push( TranslateSandboxEmailJob::newJob( $params ) );
245 }
246
253 public static function isSandboxed( User $user ) {
254 $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
255 return in_array( 'translate-sandboxed', $userGroupManager->getUserGroups( $user ), true );
256 }
257
264 public static function enforcePermissions( User $user, array &$rights ): bool {
265 global $wgTranslateUseSandbox;
266
267 if ( !$wgTranslateUseSandbox ) {
268 return true;
269 }
270
271 if ( !self::isSandboxed( $user ) ) {
272 return true;
273 }
274
275 // right-translate-sandboxaction action-translate-sandboxaction
276 $rights = [
277 'editmyoptions',
278 'editmyprivateinfo',
279 'read',
280 'readapi',
281 'translate-sandboxaction',
282 'viewmyprivateinfo',
283 'writeapi',
284 ];
285
286 // Do not let other hooks add more actions
287 return false;
288 }
289
291 public static function onGetPreferences( $user, &$preferences ) {
292 $preferences['translate-sandbox'] = $preferences['translate-sandbox-reminders'] =
293 [ 'type' => 'api' ];
294
295 return true;
296 }
297
306 public static function onApiCheckCanExecute( ApiBase $module, User $user, &$message ) {
307 $inclusionList = [
308 // Obviously this is needed to get out of the sandbox
309 TranslationStashActionApi::class,
310 // Used by UniversalLanguageSelector for example
311 'ApiOptions'
312 ];
313
314 if ( self::isSandboxed( $user ) ) {
315 $class = get_class( $module );
316 if ( $module->isWriteMode() && !in_array( $class, $inclusionList, true ) ) {
317 $message = ApiMessage::create( 'apierror-writeapidenied' );
318 return false;
319 }
320 }
321
322 return true;
323 }
324}
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:30
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.