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;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Permissions\PermissionManager;
18use MediaWiki\SpecialPage\SpecialPage;
19use MediaWiki\User\ActorStore;
20use MediaWiki\User\User;
21use MediaWiki\User\UserArray;
22use MediaWiki\User\UserFactory;
23use MediaWiki\User\UserGroupManager;
24use MediaWiki\User\UserOptionsManager;
27use UnexpectedValueException;
28use Wikimedia\Rdbms\ILoadBalancer;
29use Wikimedia\ScopedCallback;
39 public const CONSTRUCTOR_OPTIONS = [
41 'TranslateSandboxPromotedGroup',
44 private UserFactory $userFactory;
45 private ILoadBalancer $loadBalancer;
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 ILoadBalancer $loadBalancer,
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->loadBalancer = $loadBalancer;
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->loadBalancer->getConnection( DB_PRIMARY );
158 $dbw->delete(
'user', [
'user_id' => $uid ], __METHOD__ );
159 $dbw->delete(
'user_groups', [
'ug_user' => $uid ], __METHOD__ );
160 $dbw->delete(
'user_properties', [
'up_user' => $uid ], __METHOD__ );
162 $this->actorStore->deleteActor( $user, $dbw );
165 $dbw->delete(
'logging', [
'log_actor' => $actorId ], __METHOD__ );
166 $dbw->delete(
'recentchanges', [
'rc_actor' => $actorId ], __METHOD__ );
169 $statsUpdate = SiteStatsUpdate::factory( [
'users' => -1 ] );
170 $statsUpdate->doUpdate();
174 $user->clearInstanceCache(
'defaults' );
178 $user->invalidateCache();
184 $query = User::newQueryBuilder( $dbr );
186 $res = $query->join(
'user_groups',
null,
'ug_user = user_id' )
187 ->where( [
'ug_group' =>
'translate-sandboxed' ] )
188 ->caller( __METHOD__ )
191 return UserArray::newFromResult( $res );
199 $translateSandboxPromotedGroup = $this->options->get(
'TranslateSandboxPromotedGroup' );
201 if ( !self::isSandboxed( $user ) ) {
205 $this->userGroupManager->removeUserFromGroup( $user,
'translate-sandboxed' );
206 if ( $translateSandboxPromotedGroup ) {
207 $this->userGroupManager->addUserToGroup( $user, $translateSandboxPromotedGroup );
210 $this->userOptionsManager->setOption( $user,
'translate-sandbox-reminders',
null );
211 $this->userOptionsManager->saveOptions( $user );
213 $this->hookRunner->onTranslate_TranslatorSandbox_UserPromoted( $user );
223 public function sendEmail( User $sender, User $target,
string $type ): void {
224 $emergencyContact = $this->options->get(
'EmergencyContact' );
226 $targetLang = $this->userOptionsManager->getOption( $target,
'language' );
230 if ( !self::isSandboxed( $target ) ) {
234 $subjectMsg =
'tsb-reminder-title-generic';
235 $bodyMsg =
'tsb-reminder-content-generic';
236 $targetSpecialPage =
'TranslationStash';
240 $subjectMsg =
'tsb-email-promoted-subject';
241 $bodyMsg =
'tsb-email-promoted-body';
242 $targetSpecialPage =
'Translate';
246 $subjectMsg =
'tsb-email-rejected-subject';
247 $bodyMsg =
'tsb-email-rejected-body';
248 $targetSpecialPage =
'TwnMainPage';
252 throw new UnexpectedValueException(
"'$type' is an invalid type of translate sandbox email" );
255 $subject = wfMessage( $subjectMsg )->inLanguage( $targetLang )->text();
259 SpecialPage::getTitleFor( $targetSpecialPage )->getCanonicalURL(),
261 )->inLanguage( $targetLang )->text();
264 'user' => $target->getId(),
265 'to' => MailAddress::newFromUser( $target ),
266 'from' =>
new MailAddress( $emergencyContact ),
267 'replyto' =>
new MailAddress( $emergencyContact ),
270 'emailType' => $type,
273 $reminders = $this->userOptionsManager->getOption( $target,
'translate-sandbox-reminders' );
274 $reminders = $reminders ? explode(
'|', $reminders ) : [];
275 $reminders[] = wfTimestamp();
277 $this->userOptionsManager->setOption( $target,
'translate-sandbox-reminders', implode(
'|', $reminders ) );
278 $this->userOptionsManager->saveOptions( $target );
280 $this->jobQueueGroup->push( TranslateSandboxEmailJob::newJob( $params ) );
285 $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
286 return in_array(
'translate-sandboxed', $userGroupManager->getUserGroups( $user ),
true );