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