1<?php
2/**
3 * Implements Special:Confirmemail
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup SpecialPage
22 */
23
24use MediaWiki\User\UserFactory;
25use Wikimedia\ScopedCallback;
26
27/**
28 * Special page allows users to request email confirmation message, and handles
29 * processing of the confirmation code when the link in the email is followed
30 *
31 * @ingroup SpecialPage
32 * @author Brion Vibber
33 * @author Rob Church <robchur@gmail.com>
34 */
35class SpecialConfirmEmail extends UnlistedSpecialPage {
36
37	/** @var UserFactory */
38	private $userFactory;
39
40	/**
41	 * @param UserFactory $userFactory
42	 */
43	public function __construct( UserFactory $userFactory ) {
44		parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
45
46		$this->userFactory = $userFactory;
47	}
48
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->requireLogin( 'confirmemail_needlogin' );
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();
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 ) {
121				// should never happen, but if so, don't let the user without any message
122				$out->addWikiMsg( 'confirmemail_sent' );
123			} elseif ( $retval instanceof Status && $retval->isGood() ) {
124				$out->addWikiTextAsInterface( $retval->getValue() );
125			}
126		} else {
127			// date and time are separate parameters to facilitate localisation.
128			// $time is kept for backward compat reasons.
129			// 'emailauthenticated' is also used in SpecialPreferences.php
130			$lang = $this->getLanguage();
131			$emailAuthenticated = $user->getEmailAuthenticationTimestamp();
132			$time = $lang->userTimeAndDate( $emailAuthenticated, $user );
133			$d = $lang->userDate( $emailAuthenticated, $user );
134			$t = $lang->userTime( $emailAuthenticated, $user );
135			$out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
136		}
137	}
138
139	/**
140	 * Callback for HTMLForm send confirmation mail.
141	 *
142	 * @return Status Status object with the result
143	 */
144	public function submitSend() {
145		$status = $this->getUser()->sendConfirmationMail();
146		if ( $status->isGood() ) {
147			return Status::newGood( $this->msg( 'confirmemail_sent' )->text() );
148		} else {
149			return Status::newFatal( new RawMessage(
150				$status->getWikiText( 'confirmemail_sendfailed', false, $this->getLanguage() )
151			) );
152		}
153	}
154
155	/**
156	 * Attempt to confirm the user's email address and show success or failure
157	 * as needed; if successful, take the user to log in
158	 *
159	 * @param string $code Confirmation code
160	 */
161	private function attemptConfirm( $code ) {
162		$user = $this->userFactory->newFromConfirmationCode(
163			$code,
164			UserFactory::READ_LATEST
165		);
166
167		if ( !is_object( $user ) ) {
168			$this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
169
170			return;
171		}
172
173		// rate limit email confirmations
174		if ( $user->pingLimiter( 'confirmemail' ) ) {
175			$this->getOutput()->addWikiMsg( 'actionthrottledtext' );
176
177			return;
178		}
179
180		$userLatest = $user->getInstanceForUpdate();
181		$userLatest->confirmEmail();
182		$userLatest->saveSettings();
183		$message = $this->getUser()->isRegistered() ? 'confirmemail_loggedin' : 'confirmemail_success';
184		$this->getOutput()->addWikiMsg( $message );
185
186		if ( !$this->getUser()->isRegistered() ) {
187			$title = SpecialPage::getTitleFor( 'Userlogin' );
188			$this->getOutput()->returnToMain( true, $title );
189		}
190	}
191}
192