Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
33.78% covered (danger)
33.78%
25 / 74
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialConfirmEmail
34.25% covered (danger)
34.25%
25 / 73
28.57% covered (danger)
28.57%
2 / 7
146.37
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRestriction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
6.14
 showRequestForm
45.16% covered (danger)
45.16%
14 / 31
0.00% covered (danger)
0.00%
0 / 1
11.94
 submitSend
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 attemptConfirm
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use LogicException;
10use MediaWiki\Exception\PermissionsError;
11use MediaWiki\Exception\ReadOnlyError;
12use MediaWiki\Exception\UserNotLoggedIn;
13use MediaWiki\Html\Html;
14use MediaWiki\HTMLForm\HTMLForm;
15use MediaWiki\Language\RawMessage;
16use MediaWiki\Parser\Sanitizer;
17use MediaWiki\Profiler\Profiler;
18use MediaWiki\SpecialPage\SpecialPage;
19use MediaWiki\SpecialPage\UnlistedSpecialPage;
20use MediaWiki\Status\Status;
21use MediaWiki\User\User;
22use MediaWiki\User\UserFactory;
23use Wikimedia\Rdbms\IDBAccessObject;
24use Wikimedia\ScopedCallback;
25
26/**
27 * Email confirmation for registered users.
28 *
29 * This page responds to the link with the confirmation code
30 * that is sent in the confirmation email.
31 *
32 * This page can also be accessed directly at any later time
33 * to re-send the confirmation email.
34 *
35 * @ingroup SpecialPage
36 * @author Brooke Vibber
37 * @author Rob Church <robchur@gmail.com>
38 */
39class SpecialConfirmEmail extends UnlistedSpecialPage {
40
41    public function __construct(
42        private readonly UserFactory $userFactory
43    ) {
44        parent::__construct( 'Confirmemail' );
45    }
46
47    /** @inheritDoc */
48    public function getRestriction(): string {
49        return 'editmyprivateinfo';
50    }
51
52    /** @inheritDoc */
53    public function doesWrites() {
54        return true;
55    }
56
57    /**
58     * Main execution point
59     *
60     * @param null|string $code Confirmation code passed to the page
61     * @throws PermissionsError
62     * @throws ReadOnlyError
63     * @throws UserNotLoggedIn
64     */
65    public function execute( $code ) {
66        // Ignore things like primary queries/connections on GET requests.
67        // It's very convenient to just allow formless link usage.
68        $trxProfiler = Profiler::instance()->getTransactionProfiler();
69
70        $this->setHeaders();
71        $this->checkReadOnly();
72        $this->checkPermissions();
73
74        // This could also let someone check the current email address, so
75        // require both permissions.
76        if ( !$this->getAuthority()->isAllowed( 'viewmyprivateinfo' ) ) {
77            throw new PermissionsError( 'viewmyprivateinfo' );
78        }
79
80        if ( $code === null || $code === '' ) {
81            $this->requireNamedUser( 'confirmemail_needlogin', 'exception-nologin', true );
82            if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
83                $this->showRequestForm();
84            } else {
85                $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
86            }
87        } else {
88            $scope = $trxProfiler->silenceForScope( $trxProfiler::EXPECTATION_REPLICAS_ONLY );
89            $this->attemptConfirm( $code );
90            ScopedCallback::consume( $scope );
91        }
92    }
93
94    /**
95     * Show a nice form for the user to request a confirmation mail
96     */
97    private function showRequestForm() {
98        $user = $this->getUser();
99        $out = $this->getOutput();
100        $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
101
102        if ( !$user->isEmailConfirmed() ) {
103            $descriptor = [];
104            if ( $user->isEmailConfirmationPending() ) {
105                $descriptor += [
106                    'pending' => [
107                        'type' => 'info',
108                        'raw' => true,
109                        'default' => Html::warningBox(
110                            $this->msg( 'confirmemail_pending' )->escaped(),
111                            'mw-confirmemail-pending'
112                        ),
113                    ],
114                ];
115            }
116
117            $form = HTMLForm::factory( 'ooui', $descriptor, $this->getContext() );
118            $form
119                ->setAction( $this->getPageTitle()->getLocalURL() )
120                ->setHeaderHtml( $this->msg( 'confirmemail_text' )->parse() )
121                ->setSubmitTextMsg( 'confirmemail_send' )
122                ->setSubmitCallback( $this->submitSend( ... ) );
123
124            $retval = $form->show();
125
126            if ( $retval === true || ( $retval instanceof Status && $retval->isGood() ) ) {
127                $out->addWikiMsg( 'confirmemail_sent' );
128            }
129        } else {
130            // date and time are separate parameters to facilitate localisation.
131            // $time is kept for backward compat reasons.
132            // 'emailauthenticated' is also used in SpecialPreferences.php
133            $lang = $this->getLanguage();
134            $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
135            $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
136            $d = $lang->userDate( $emailAuthenticated, $user );
137            $t = $lang->userTime( $emailAuthenticated, $user );
138            $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
139        }
140    }
141
142    /**
143     * Callback for HTMLForm send confirmation mail.
144     *
145     * @return Status Status object with the result
146     */
147    private function submitSend() {
148        $status = $this->getUser()->sendConfirmationMail();
149        if ( $status->isGood() ) {
150            return Status::newGood();
151        } else {
152            return Status::newFatal( new RawMessage(
153                $status->getWikiText( 'confirmemail_sendfailed', false, $this->getLanguage() )
154            ) );
155        }
156    }
157
158    /**
159     * Attempt to confirm the user's email address and show success or failure
160     * as needed; if successful, take the user to log in
161     *
162     * @param string $code Confirmation code
163     */
164    private function attemptConfirm( $code ) {
165        $user = $this->userFactory->newFromConfirmationCode(
166            $code,
167            IDBAccessObject::READ_LATEST
168        );
169
170        if ( !is_object( $user ) ) {
171            if ( User::isWellFormedConfirmationToken( $code ) ) {
172                $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
173            } else {
174                $this->getOutput()->addWikiMsg( 'confirmemail_invalid_format' );
175            }
176
177            return;
178        }
179
180        // Enforce permissions, user blocks, and rate limits
181        $this->authorizeAction( 'confirmemail' )->throwErrorPageError();
182
183        $userLatest = $user->getInstanceFromPrimary( IDBAccessObject::READ_EXCLUSIVE )
184            ?? throw new LogicException( 'No user' );
185        $userLatest->confirmEmail();
186        $userLatest->saveSettings();
187        $message = $this->getUser()->isNamed() ? 'confirmemail_loggedin' : 'confirmemail_success';
188        $this->getOutput()->addWikiMsg( $message );
189
190        if ( !$this->getUser()->isNamed() ) {
191            $title = SpecialPage::getTitleFor( 'Userlogin' );
192            $this->getOutput()->returnToMain( true, $title );
193        }
194    }
195}
196
197/** @deprecated class alias since 1.41 */
198class_alias( SpecialConfirmEmail::class, 'SpecialConfirmEmail' );