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