2declare( strict_types = 1 );
4namespace MediaWiki\Extension\Translate\TranslatorSandbox;
6use InvalidArgumentException;
9use MediaWiki\Auth\AuthenticationRequest;
10use MediaWiki\Auth\AuthenticationResponse;
11use MediaWiki\Auth\AuthManager;
12use MediaWiki\Config\ServiceOptions;
13use MediaWiki\Deferred\SiteStatsUpdate;
17use MediaWiki\MediaWikiServices;
18use MediaWiki\Permissions\PermissionManager;
19use MediaWiki\SpecialPage\SpecialPage;
20use MediaWiki\User\ActorStore;
21use MediaWiki\User\Options\UserOptionsManager;
22use MediaWiki\User\User;
23use MediaWiki\User\UserArray;
24use MediaWiki\User\UserFactory;
25use MediaWiki\User\UserGroupManager;
27use UnexpectedValueException;
28use Wikimedia\Rdbms\IConnectionProvider;
29use Wikimedia\ScopedCallback;
39 public const CONSTRUCTOR_OPTIONS = [
41 'TranslateSandboxPromotedGroup',
44 private UserFactory $userFactory;
45 private IConnectionProvider $dbProvider;
46 private PermissionManager $permissionManager;
47 private AuthManager $authManager;
48 private UserGroupManager $userGroupManager;
49 private ActorStore $actorStore;
50 private UserOptionsManager $userOptionsManager;
51 private JobQueueGroup $jobQueueGroup;
53 private ServiceOptions $options;
55 public function __construct(
56 UserFactory $userFactory,
57 IConnectionProvider $dbProvider,
58 PermissionManager $permissionManager,
59 AuthManager $authManager,
60 UserGroupManager $userGroupManager,
61 ActorStore $actorStore,
62 UserOptionsManager $userOptionsManager,
63 JobQueueGroup $jobQueueGroup,
65 ServiceOptions $options
67 $this->userFactory = $userFactory;
68 $this->dbProvider = $dbProvider;
69 $this->permissionManager = $permissionManager;
70 $this->authManager = $authManager;
71 $this->userGroupManager = $userGroupManager;
72 $this->actorStore = $actorStore;
73 $this->userOptionsManager = $userOptionsManager;
74 $this->jobQueueGroup = $jobQueueGroup;
75 $this->hookRunner = $hookRunner;
76 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
77 $this->options = $options;
87 public function addUser(
string $name,
string $email,
string $password ): User {
88 $user = $this->userFactory->newFromName( $name, UserFactory::RIGOR_CREATABLE );
91 throw new InvalidArgumentException(
'Invalid user name' );
95 'username' => $user->getName(),
96 'password' => $password,
97 'retype' => $password,
102 $creator = TranslateUserManager::getUser();
103 $guard = $this->permissionManager->addTemporaryUserRights( $creator,
'createaccount' );
105 $reqs = $this->authManager->getAuthenticationRequests( AuthManager::ACTION_CREATE );
106 $reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
107 $res = $this->authManager->beginAccountCreation( $creator, $reqs,
'null:' );
109 ScopedCallback::consume( $guard );
111 switch ( $res->status ) {
112 case AuthenticationResponse::PASS:
114 case AuthenticationResponse::FAIL:
118 $reason = $res->message->inLanguage(
'en' )->useDatabase(
false )->text();
119 throw new RuntimeException(
120 "Account creation failed: $reason",
121 self::USER_CREATION_FAILURE
126 if ( $user->getId() ) {
130 throw new RuntimeException(
131 'AuthManager does not support such simplified account creation'
136 $this->userGroupManager->addUserToGroup( $user,
'translate-sandboxed' );
148 public function deleteUser( User $user,
string $force =
'' ): void {
149 $uid = $user->getId();
150 $actorId = $user->getActorId();
152 if ( $force !==
'force' && !self::isSandboxed( $user ) ) {
157 $dbw = $this->dbProvider->getPrimaryDatabase();
158 $dbw->newDeleteQueryBuilder()
159 ->deleteFrom(
'user' )
160 ->where( [
'user_id' => $uid ] )
161 ->caller( __METHOD__ )
163 $dbw->newDeleteQueryBuilder()
164 ->deleteFrom(
'user_groups' )
165 ->where( [
'ug_user' => $uid ] )
166 ->caller( __METHOD__ )
168 $dbw->newDeleteQueryBuilder()
169 ->deleteFrom(
'user_properties' )
170 ->where( [
'up_user' => $uid ] )
171 ->caller( __METHOD__ )
174 $this->actorStore->deleteActor( $user, $dbw );
177 $dbw->newDeleteQueryBuilder()
178 ->deleteFrom(
'logging' )
179 ->where( [
'log_actor' => $actorId ] )
180 ->caller( __METHOD__ )
182 $dbw->newDeleteQueryBuilder()
183 ->deleteFrom(
'recentchanges' )
184 ->where( [
'rc_actor' => $actorId ] )
185 ->caller( __METHOD__ )
189 $statsUpdate = SiteStatsUpdate::factory( [
'users' => -1 ] );
190 $statsUpdate->doUpdate();
194 $user->clearInstanceCache(
'defaults' );
198 $user->invalidateCache();
204 $query = User::newQueryBuilder( $dbr );
206 $res = $query->join(
'user_groups',
null,
'ug_user = user_id' )
207 ->where( [
'ug_group' =>
'translate-sandboxed' ] )
208 ->caller( __METHOD__ )
211 return UserArray::newFromResult( $res );
219 $translateSandboxPromotedGroup = $this->options->get(
'TranslateSandboxPromotedGroup' );
221 if ( !self::isSandboxed( $user ) ) {
225 $this->userGroupManager->removeUserFromGroup( $user,
'translate-sandboxed' );
226 if ( $translateSandboxPromotedGroup ) {
227 $this->userGroupManager->addUserToGroup( $user, $translateSandboxPromotedGroup );
230 $this->userOptionsManager->setOption( $user,
'translate-sandbox-reminders',
null );
231 $this->userOptionsManager->saveOptions( $user );
233 $this->hookRunner->onTranslate_TranslatorSandbox_UserPromoted( $user );
243 public function sendEmail( User $sender, User $target,
string $type ): void {
244 $emergencyContact = $this->options->get(
'EmergencyContact' );
246 $targetLang = $this->userOptionsManager->getOption( $target,
'language' );
250 if ( !self::isSandboxed( $target ) ) {
254 $subjectMsg =
'tsb-reminder-title-generic';
255 $bodyMsg =
'tsb-reminder-content-generic';
256 $targetSpecialPage =
'TranslationStash';
260 $subjectMsg =
'tsb-email-promoted-subject';
261 $bodyMsg =
'tsb-email-promoted-body';
262 $targetSpecialPage =
'Translate';
266 $subjectMsg =
'tsb-email-rejected-subject';
267 $bodyMsg =
'tsb-email-rejected-body';
268 $targetSpecialPage =
'TwnMainPage';
272 throw new UnexpectedValueException(
"'$type' is an invalid type of translate sandbox email" );
275 $subject = wfMessage( $subjectMsg )->inLanguage( $targetLang )->text();
279 SpecialPage::getTitleFor( $targetSpecialPage )->getCanonicalURL(),
281 )->inLanguage( $targetLang )->text();
284 'user' => $target->getId(),
285 'to' => MailAddress::newFromUser( $target ),
286 'from' =>
new MailAddress( $emergencyContact ),
287 'replyto' =>
new MailAddress( $emergencyContact ),
290 'emailType' => $type,
293 $reminders = $this->userOptionsManager->getOption( $target,
'translate-sandbox-reminders' );
294 $reminders = $reminders ? explode(
'|', $reminders ) : [];
295 $reminders[] = wfTimestamp();
297 $this->userOptionsManager->setOption( $target,
'translate-sandbox-reminders', implode(
'|', $reminders ) );
298 $this->userOptionsManager->saveOptions( $target );
300 $this->jobQueueGroup->push( TranslateSandboxEmailJob::newJob( $params ) );
305 $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
306 return in_array(
'translate-sandboxed', $userGroupManager->getUserGroups( $user ),
true );