Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialUserMerge
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 8
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getFormFields
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
20
 validateOldUser
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 validateNewUser
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 alterForm
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSubmit
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
56
 getDisplayFormat
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/** \file
3 * \brief Contains code for the UserMerge Class (extends SpecialPage).
4 */
5
6/**
7 * Special page class for the User Merge and Delete extension
8 * allows sysops to merge references from one user to another user.
9 * It also supports deleting users following merge.
10 *
11 * @ingroup Extensions
12 * @author Tim Laqua <t.laqua@gmail.com>
13 * @author Thomas Gries <mail@tgries.de>
14 * @author Matthew April <Matthew.April@tbs-sct.gc.ca>
15 *
16 */
17
18use MediaWiki\User\UserGroupManager;
19
20class SpecialUserMerge extends FormSpecialPage {
21
22    /** @var UserGroupManager */
23    private $userGroupManager;
24
25    /**
26     * @param UserGroupManager $userGroupManager
27     */
28    public function __construct(
29        UserGroupManager $userGroupManager
30    ) {
31        parent::__construct( 'UserMerge', 'usermerge' );
32        $this->userGroupManager = $userGroupManager;
33    }
34
35    /**
36     * @return array
37     */
38    protected function getFormFields() {
39        return [
40            'olduser' => [
41                'type' => 'user',
42                'exists' => true,
43                'label-message' => 'usermerge-olduser',
44                'required' => true,
45                'validation-callback' => function ( $val ) {
46                    $key = $this->validateOldUser( $val );
47                    if ( is_array( $key ) ) {
48                        return $this->msg( $key )->escaped();
49                    }
50                    return true;
51                },
52            ],
53            'newuser' => [
54                'type' => 'user',
55                'required' => true,
56                'label-message' => 'usermerge-newuser',
57                'validation-callback' => function ( $val ) {
58                    // only pass strings to User::newFromName
59                    if ( !is_string( $val ) ) {
60                        return true;
61                    }
62
63                    $key = $this->validateNewUser( $val );
64                    if ( is_string( $key ) ) {
65                        return $this->msg( $key )->escaped();
66                    }
67                    return true;
68                },
69            ],
70            'delete' => [
71                'type' => 'check',
72                'label-message' => 'usermerge-deleteolduser',
73            ],
74        ];
75    }
76
77    /**
78     * @param string $val user's input for username
79     * @return true|string[] true if valid, a string[] of the error's message key and params
80     *   if validation failed
81     */
82    public function validateOldUser( $val ) {
83        $oldUser = User::newFromName( $val );
84        if ( $this->getUser()->getId() === $oldUser->getId() ) {
85            return [ 'usermerge-noselfdelete', $this->getUser()->getName() ];
86        }
87        $protectedGroups = $this->getConfig()->get( 'UserMergeProtectedGroups' );
88        if ( array_intersect( $this->userGroupManager->getUserGroups( $oldUser ), $protectedGroups ) !== [] ) {
89            return [ 'usermerge-protectedgroup', $oldUser->getName() ];
90        }
91
92        return true;
93    }
94
95    /**
96     * @param string $val user's input for username
97     * @return true|string true if valid, a string of the error's message key if validation failed
98     */
99    public function validateNewUser( $val ) {
100        $enableDelete = $this->getConfig()->get( 'UserMergeEnableDelete' );
101        if ( $enableDelete && $val === 'Anonymous' ) {
102            // Special case
103            return true;
104        }
105        $newUser = User::newFromName( $val );
106        if ( !$newUser || $newUser->getId() === 0 ) {
107            return 'usermerge-badnewuser';
108        }
109
110        return true;
111    }
112
113    /**
114     * @param HTMLForm $form
115     */
116    protected function alterForm( HTMLForm $form ) {
117        $form->setSubmitTextMsg( 'usermerge-submit' );
118    }
119
120    /**
121     * @param array $data
122     * @return Status
123     */
124    public function onSubmit( array $data ) {
125        $enableDelete = $this->getConfig()->get( 'UserMergeEnableDelete' );
126        // Most of the data has been validated using callbacks
127        // still need to check if the users are different
128        $newUser = User::newFromName( $data['newuser'] );
129        // Handle "Anonymous" as a special case for user deletion
130        if ( $enableDelete && $data['newuser'] === 'Anonymous' ) {
131            $newUser->mId = 0;
132        }
133
134        $oldUser = User::newFromName( $data['olduser'] );
135        if ( $newUser->getName() === $oldUser->getName() ) {
136            return Status::newFatal( 'usermerge-same-old-and-new-user' );
137        }
138
139        // Validation passed, let's merge the user now.
140        $um = new MergeUser( $oldUser, $newUser, new UserMergeLogger() );
141        $um->merge( $this->getUser(), __METHOD__ );
142
143        $out = $this->getOutput();
144
145        $out->addWikiMsg(
146            'usermerge-success',
147            $oldUser->getName(), $oldUser->getId(),
148            $newUser->getName(), $newUser->getId()
149        );
150
151        if ( $data['delete'] ) {
152            $failed = $um->delete( $this->getUser(), [ $this, 'msg' ] );
153            $out->addWikiMsg(
154                'usermerge-userdeleted', $oldUser->getName(), $oldUser->getId()
155            );
156
157            if ( $failed ) {
158                // Output an error message for failed moves
159                $out->addHTML( Html::openElement( 'ul' ) );
160                $linkRenderer = $this->getLinkRenderer();
161                foreach ( $failed as $oldTitleText => $newTitle ) {
162                    $oldTitle = Title::newFromText( $oldTitleText );
163                    $out->addHTML(
164                        Html::rawElement( 'li', [],
165                            $this->msg( 'usermerge-page-unmoved' )->rawParams(
166                                $linkRenderer->makeLink( $oldTitle ),
167                                $linkRenderer->makeLink( $newTitle )
168                            )->escaped()
169                        )
170                    );
171                }
172                $out->addHTML( Html::closeElement( 'ul' ) );
173            }
174        }
175
176        return Status::newGood();
177    }
178
179    /**
180     * @inheritDoc
181     */
182    protected function getDisplayFormat() {
183        return 'ooui';
184    }
185
186    /**
187     * @inheritDoc
188     */
189    protected function getGroupName() {
190        return 'users';
191    }
192}