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