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