1<?php
2/**
3 * Implements Special:PasswordReset
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
24/**
25 * Special page for requesting a password reset email.
26 *
27 * Requires the TemporaryPasswordPrimaryAuthenticationProvider and the
28 * EmailNotificationSecondaryAuthenticationProvider (or something providing equivalent
29 * functionality) to be enabled.
30 *
31 * @ingroup SpecialPage
32 */
33class SpecialPasswordReset extends FormSpecialPage {
34	/** @var PasswordReset */
35	private $passwordReset;
36
37	/**
38	 * @var Status
39	 */
40	private $result;
41
42	/**
43	 * @var string Identifies which password reset field was specified by the user.
44	 */
45	private $method;
46
47	/**
48	 * @param PasswordReset $passwordReset
49	 */
50	public function __construct( PasswordReset $passwordReset ) {
51		parent::__construct( 'PasswordReset', 'editmyprivateinfo' );
52
53		$this->passwordReset = $passwordReset;
54	}
55
56	public function doesWrites() {
57		return true;
58	}
59
60	public function userCanExecute( User $user ) {
61		return $this->passwordReset->isAllowed( $user )->isGood();
62	}
63
64	public function checkExecutePermissions( User $user ) {
65		$status = Status::wrap( $this->passwordReset->isAllowed( $user ) );
66		if ( !$status->isGood() ) {
67			throw new ErrorPageError( 'internalerror', $status->getMessage() );
68		}
69
70		parent::checkExecutePermissions( $user );
71	}
72
73	/**
74	 * @param string|null $par
75	 */
76	public function execute( $par ) {
77		$out = $this->getOutput();
78		$out->disallowUserJs();
79		parent::execute( $par );
80	}
81
82	protected function getFormFields() {
83		$resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
84		$a = [];
85		if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
86			$a['Username'] = [
87				'type' => 'text',
88				'default' => $this->getRequest()->getSession()->suggestLoginUsername(),
89				'label-message' => 'passwordreset-username',
90			];
91
92			if ( $this->getUser()->isRegistered() ) {
93				$a['Username']['default'] = $this->getUser()->getName();
94			}
95		}
96
97		if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
98			$a['Email'] = [
99				'type' => 'email',
100				'label-message' => 'passwordreset-email',
101			];
102		}
103
104		return $a;
105	}
106
107	protected function getDisplayFormat() {
108		return 'ooui';
109	}
110
111	public function alterForm( HTMLForm $form ) {
112		$resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
113
114		$form->setSubmitDestructive();
115
116		$form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
117
118		$i = 0;
119		if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
120			$i++;
121		}
122		if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
123			$i++;
124		}
125
126		$message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
127
128		$form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() );
129		$form->setSubmitTextMsg( 'mailmypassword' );
130	}
131
132	/**
133	 * Process the form.  At this point we know that the user passes all the criteria in
134	 * userCanExecute(), and if the data array contains 'Username', etc, then Username
135	 * resets are allowed.
136	 * @param array $data
137	 * @throws MWException
138	 * @throws ThrottledError|PermissionsError
139	 * @return Status
140	 */
141	public function onSubmit( array $data ) {
142		$username = $data['Username'] ?? null;
143		$email = $data['Email'] ?? null;
144
145		$this->method = $username ? 'username' : 'email';
146		$this->result = Status::wrap(
147			$this->passwordReset->execute( $this->getUser(), $username, $email ) );
148
149		if ( $this->result->hasMessage( 'actionthrottledtext' ) ) {
150			throw new ThrottledError;
151		}
152
153		return $this->result;
154	}
155
156	/**
157	 * Show a message on the successful processing of the form.
158	 * This doesn't necessarily mean a reset email was sent.
159	 */
160	public function onSuccess() {
161		$output = $this->getOutput();
162
163		// Information messages.
164		$output->addWikiMsg( 'passwordreset-success' );
165		$output->addWikiMsg( 'passwordreset-success-details-generic',
166			$this->getConfig()->get( 'PasswordReminderResendTime' ) );
167
168		// Confirmation of what the user has just submitted.
169		$info = "\n";
170		$postVals = $this->getRequest()->getPostValues();
171		if ( isset( $postVals['wpUsername'] ) && $postVals['wpUsername'] !== '' ) {
172			$info .= "* " . $this->msg( 'passwordreset-username' ) . ' '
173				. wfEscapeWikiText( $postVals['wpUsername'] ) . "\n";
174		}
175		if ( isset( $postVals['wpEmail'] ) && $postVals['wpEmail'] !== '' ) {
176			$info .= "* " . $this->msg( 'passwordreset-email' ) . ' '
177				. wfEscapeWikiText( $postVals['wpEmail'] ) . "\n";
178		}
179		$output->addWikiMsg( 'passwordreset-success-info', $info );
180
181		// Link to main page.
182		$output->returnToMain();
183	}
184
185	/**
186	 * Hide the password reset page if resets are disabled.
187	 * @return bool
188	 */
189	public function isListed() {
190		if ( $this->passwordReset->isAllowed( $this->getUser() )->isGood() ) {
191			return parent::isListed();
192		}
193
194		return false;
195	}
196
197	protected function getGroupName() {
198		return 'users';
199	}
200}
201