Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialGlobalRenameRequest
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 17
1560
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 userCanExecute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 displayRestrictionError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
72
 isGlobalUser
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 alterForm
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preHtml
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getFormFields
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 1
20
 suggestedUsername
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 validateNewname
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 validateEmail
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 onSubmit
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 onSuccess
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 requiresUnblock
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @section LICENSE
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22namespace MediaWiki\Extension\CentralAuth\Special;
23
24use HTMLForm;
25use IDBAccessObject;
26use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameDenylist;
27use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameRequest;
28use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameRequestStore;
29use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
30use MediaWiki\Parser\Sanitizer;
31use MediaWiki\SpecialPage\FormSpecialPage;
32use MediaWiki\Status\Status;
33use MediaWiki\User\User;
34use MediaWiki\User\UserNameUtils;
35use MediaWiki\WikiMap\WikiMap;
36use Message;
37use PermissionsError;
38
39/**
40 * Request an account rename.
41 *
42 * @author Bryan Davis <bd808@wikimedia.org>
43 * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
44 */
45class SpecialGlobalRenameRequest extends FormSpecialPage {
46
47    /** @var GlobalRenameDenylist */
48    private $globalRenameDenylist;
49
50    /** @var UserNameUtils */
51    private $userNameUtils;
52
53    /** @var GlobalRenameRequestStore */
54    private $globalRenameRequestStore;
55
56    /**
57     * @param GlobalRenameDenylist $globalRenameDenylist
58     * @param UserNameUtils $userNameUtils
59     * @param GlobalRenameRequestStore $globalRenameRequestStore
60     */
61    public function __construct(
62        GlobalRenameDenylist $globalRenameDenylist,
63        UserNameUtils $userNameUtils,
64        GlobalRenameRequestStore $globalRenameRequestStore
65    ) {
66        parent::__construct( 'GlobalRenameRequest' );
67        $this->globalRenameDenylist = $globalRenameDenylist;
68        $this->userNameUtils = $userNameUtils;
69        $this->globalRenameRequestStore = $globalRenameRequestStore;
70    }
71
72    public function doesWrites() {
73        return true;
74    }
75
76    /** @inheritDoc */
77    public function userCanExecute( User $user ) {
78        return $this->globalRenameDenylist->checkUser( $user->getName() );
79    }
80
81    public function displayRestrictionError() {
82        throw new PermissionsError( null, [ 'centralauth-badaccess-blacklisted' ] );
83    }
84
85    /**
86     * @param string|null $par Subpage string if one was specified
87     */
88    public function execute( $par ) {
89        $this->requireNamedUser();
90        $this->addHelpLink( 'Help:Extension:CentralAuth/Global_rename' );
91
92        switch ( $par ) {
93            case 'status':
94                // Render status page
95                $user = $this->getUser();
96                $username = $user->getName();
97                $wiki = $this->isGlobalUser() ? null : WikiMap::getCurrentWikiId();
98                $pending = $this->globalRenameRequestStore->newForUser(
99                    $username, $wiki
100                );
101                if ( !$pending->exists() ) {
102                    $this->getOutput()->redirect( $this->getPageTitle()->getFullURL(), 303 );
103                    return;
104                }
105                $out = $this->getOutput();
106                $out->setPageTitleMsg(
107                    $this->msg( 'globalrenamerequest-status-title' )
108                );
109                $out->addWikiMsg( 'globalrenamerequest-status-text',
110                    $username, $pending->getNewName()
111                );
112                break;
113
114            case 'available':
115                // TODO: ajax name availability check (T72623)
116                break;
117
118            default:
119                // Request form
120                $out = $this->getOutput();
121                $user = $this->getUser();
122                $wiki = $this->isGlobalUser() ? null : WikiMap::getCurrentWikiId();
123
124                $pending = $this->globalRenameRequestStore->newForUser(
125                    $user->getName(), $wiki
126                );
127                if ( $pending->exists() ) {
128                    $out->redirect(
129                        $this->getPageTitle( 'status' )->getFullURL(), '303'
130                    );
131                    return;
132                }
133                parent::execute( $par );
134                break;
135        }
136    }
137
138    /**
139     * Is the current user a global user?
140     * @return bool
141     */
142    protected function isGlobalUser() {
143        $user = $this->getUser();
144        $causer = CentralAuthUser::getInstance( $user );
145        return $causer->exists() && $causer->isAttached();
146    }
147
148    /**
149     * @param HTMLForm $form
150     */
151    protected function alterForm( HTMLForm $form ) {
152        $form->setSubmitTextMsg( 'globalrenamerequest-submit-text' );
153    }
154
155    /**
156     * @return string
157     */
158    protected function getDisplayFormat() {
159        return 'ooui';
160    }
161
162    /**
163     * @return string
164     */
165    protected function preHtml() {
166        $msg = $this->msg( 'globalrenamerequest-pretext' )->parse();
167        if ( !$this->isGlobalUser() ) {
168            $msg = $this->msg( 'globalrenamerequest-forced' )->parse() . $msg;
169        }
170        return $msg;
171    }
172
173    /**
174     * @return array[]
175     */
176    public function getFormFields() {
177        $suggestedUsername = $this->suggestedUsername();
178        if ( $suggestedUsername !== false ) {
179            $suggestedHelp = [
180                'globalrenamerequest-newname-help',
181                $suggestedUsername,
182            ];
183        } else {
184            // Help message if we couldn't generate a suggested username
185            $suggestedHelp = 'globalrenamerequest-newname-help-basic';
186        }
187        $fields = [
188            'username' => [
189                'cssclass'      => 'mw-globalrenamerequest-field',
190                'csshelpclass'  => 'mw-globalrenamerequest-help',
191                'default'       => $this->getUser()->getName(),
192                'label-message' => 'globalrenamerequest-username-label',
193                'type'          => 'info',
194            ],
195            'newname' => [
196                'cssclass'     => 'mw-globalrenamerequest-field',
197                'csshelpclass' => 'mw-globalrenamerequest-help',
198                'default'      => $this->getRequest()->getVal( 'newname', $this->par ),
199                'id'            => 'mw-renamerequest-newname',
200                'label-message' => 'globalrenamerequest-newname-label',
201                'name'          => 'newname',
202                'required'      => true,
203                'type'          => 'text',
204                'help-message'  => $suggestedHelp,
205                'validation-callback' => [ $this, 'validateNewname' ],
206            ],
207        ];
208
209        $currentEmail = $this->getUser()->getEmail();
210        if ( $currentEmail === '' ) {
211            $fields['email'] = [
212                'cssclass'      => 'mw-globalrenamerequest-field',
213                'csshelpclass'  => 'mw-globalrenamerequest-help',
214                'default'       => $this->getRequest()->getVal( 'email', $this->par ),
215                'id'            => 'mw-renamerequest-email',
216                'label-message' => 'globalrenamerequest-email-label',
217                'name'          => 'email',
218                'placeholder'   => 'username@example.com',
219                'required'      => true,
220                'type'          => 'email',
221                'help-message'  => 'globalrenamerequest-email-why-explain',
222            ];
223            $fields['email2'] = [
224                'cssclass'      => 'mw-globalrenamerequest-field',
225                'csshelpclass'  => 'mw-globalrenamerequest-help',
226                'default'       => $this->getRequest()->getVal( 'email2', $this->par ),
227                'id'            => 'mw-renamerequest-email2',
228                'label-message' => 'globalrenamerequest-email2-label',
229                'name'          => 'email2',
230                'placeholder'   => 'username@example.com',
231                'required'      => true,
232                'type'          => 'email',
233                'help-message'  => 'globalrenamerequest-email2-help',
234                'validation-callback' => [ $this, 'validateEmail' ],
235            ];
236        }
237
238        if ( $this->isGlobalUser() ) {
239            $fields['reason'] = [
240                'cssclass'      => 'mw-globalrenamerequest-field',
241                'default'       => $this->getRequest()->getVal( 'reason', $this->par ),
242                'id'            => 'mw-renamerequest-reason',
243                'label-message' => 'globalrenamerequest-reason-label',
244                'name'          => 'reason',
245                'required'      => true,
246                'rows'          => 5,
247                'type'          => 'textarea',
248            ];
249        }
250        return $fields;
251    }
252
253    /**
254     * Generate a username that appears to be globally available that an
255     * unimaginative user could use if they like.
256     *
257     * @return string|bool false if can't generate a username
258     */
259    protected function suggestedUsername() {
260        // Only allow 5 tries (T260865)
261        $counter = 0;
262        // Whether we found a username that is available to use
263        $found = false;
264        $rand = '';
265        while ( !$found && $counter < 5 ) {
266            $rand = $this->getUser()->getName() . rand( 123, 999 );
267            $found = GlobalRenameRequest::isNameAvailable( $rand, IDBAccessObject::READ_NORMAL )->isOK();
268            $counter++;
269        }
270        if ( $found ) {
271            return $rand;
272        } else {
273            return false;
274        }
275    }
276
277    /**
278     * @param string|array $value The value the field was submitted with
279     * @param array $alldata The data collected from the form
280     * @param HTMLForm $form
281     * @return bool|Message True on success, or String error to display, or
282     *   false to fail validation without displaying an error.
283     */
284    public function validateNewname( $value, $alldata, HTMLForm $form ) {
285        if ( $value === null ) {
286            // Not submitted yet
287            return true;
288        }
289        $check = GlobalRenameRequest::isNameAvailable( $value );
290        return $check->isGood() ? true : $check->getMessage();
291    }
292
293    /**
294     * @param string|array $value The value the field was submitted with
295     * @param array $alldata The data collected from the form
296     * @param HTMLForm $form
297     * @return bool|Message True on success, or String error to display, or
298     *   false to fail validation without displaying an error.
299     */
300    public function validateEmail( $value, $alldata, HTMLForm $form ) {
301        if ( $alldata['email'] !== $alldata['email2'] ) {
302            return $this->msg( 'globalrenamerequest-email-mismatch' );
303        } elseif ( !Sanitizer::validateEmail( $alldata['email'] ) ) {
304            return $this->msg( 'globalrenamerequest-email-invalid' );
305        }
306        return true;
307    }
308
309    /**
310     * @param array $data
311     * @return Status
312     */
313    public function onSubmit( array $data ) {
314        $wiki = $this->isGlobalUser() ? null : WikiMap::getCurrentWikiId();
315        $reason = $data['reason'] ?? null;
316        $safeName = $this->userNameUtils->getCanonical( $data['newname'], UserNameUtils::RIGOR_CREATABLE );
317
318        $request = $this->globalRenameRequestStore->newBlankRequest();
319        $request->setName( $this->getUser()->getName() );
320        $request->setWiki( $wiki );
321        $request->setNewName( $safeName );
322        $request->setReason( $reason );
323
324        if ( $this->globalRenameRequestStore->save( $request ) ) {
325            $status = Status::newGood();
326
327            if ( isset( $data['email'] ) ) {
328                $user = $this->getUser();
329                $user->setEmail( $data['email'] );
330                $user->saveSettings();
331                $status = $user->sendConfirmationMail( 'set' );
332            }
333        } else {
334            $status = Status::newFatal(
335                $this->msg( 'globalrenamerequest-save-error' )
336            );
337        }
338        return $status;
339    }
340
341    public function onSuccess() {
342        $this->getOutput()->redirect(
343            $this->getPageTitle( 'status' )->getFullURL(), '303'
344        );
345    }
346
347    /**
348     * Even blocked users should be able to ask for a rename.
349     * @return bool
350     */
351    public function requiresUnblock() {
352        return false;
353    }
354
355    /** @inheritDoc */
356    protected function getGroupName() {
357        return 'login';
358    }
359}