Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.97% |
131 / 144 |
|
68.75% |
11 / 16 |
CRAP | |
0.00% |
0 / 1 |
SpecialMergeLexemes | |
90.97% |
131 / 144 |
|
68.75% |
11 / 16 |
33.80 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
94.74% |
18 / 19 |
|
0.00% |
0 / 1 |
9.01 | |||
setHeaders | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
checkBlocked | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
factory | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
showMergeForm | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
getFormElements | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
1 | |||
anonymousEditWarning | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
2.01 | |||
mergeLexemes | |
100.00% |
35 / 35 |
|
100.00% |
1 / 1 |
5 | |||
getTextParam | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getLexemeId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
showSuccessMessage | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
showInvalidLexemeIdError | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showErrorHTML | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDescription | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace Wikibase\Lexeme\MediaWiki\Specials; |
6 | |
7 | use Exception; |
8 | use InvalidArgumentException; |
9 | use MediaWiki\Html\Html; |
10 | use MediaWiki\HTMLForm\HTMLForm; |
11 | use MediaWiki\Message\Message; |
12 | use MediaWiki\Permissions\PermissionManager; |
13 | use MediaWiki\SpecialPage\SpecialPage; |
14 | use UserBlockedError; |
15 | use Wikibase\Lexeme\Domain\Merge\Exceptions\MergingException; |
16 | use Wikibase\Lexeme\Domain\Model\LexemeId; |
17 | use Wikibase\Lexeme\Interactors\MergeLexemes\MergeLexemesInteractor; |
18 | use Wikibase\Lib\SettingsArray; |
19 | use Wikibase\Lib\Store\EntityTitleLookup; |
20 | use Wikibase\Repo\AnonymousEditWarningBuilder; |
21 | use Wikibase\Repo\Interactors\TokenCheckException; |
22 | use Wikibase\Repo\Interactors\TokenCheckInteractor; |
23 | use Wikibase\Repo\Localizer\ExceptionLocalizer; |
24 | |
25 | /** |
26 | * Special page for merging one lexeme into another. |
27 | * |
28 | * @license GPL-2.0-or-later |
29 | */ |
30 | class SpecialMergeLexemes extends SpecialPage { |
31 | |
32 | private const FROM_ID = 'from-id'; |
33 | private const TO_ID = 'to-id'; |
34 | private const SUCCESS = 'success'; |
35 | |
36 | /** @var string[] */ |
37 | private array $tags; |
38 | |
39 | private MergeLexemesInteractor $mergeInteractor; |
40 | |
41 | private TokenCheckInteractor $tokenCheckInteractor; |
42 | |
43 | private EntityTitleLookup $titleLookup; |
44 | |
45 | private ExceptionLocalizer $exceptionLocalizer; |
46 | |
47 | private PermissionManager $permissionManager; |
48 | |
49 | private AnonymousEditWarningBuilder $anonymousEditWarningBuilder; |
50 | |
51 | public function __construct( |
52 | array $tags, |
53 | MergeLexemesInteractor $mergeInteractor, |
54 | TokenCheckInteractor $tokenCheckInteractor, |
55 | EntityTitleLookup $titleLookup, |
56 | ExceptionLocalizer $exceptionLocalizer, |
57 | PermissionManager $permissionManager, |
58 | AnonymousEditWarningBuilder $anonymousEditWarningBuilder |
59 | ) { |
60 | parent::__construct( 'MergeLexemes', 'item-merge' ); |
61 | $this->tags = $tags; |
62 | $this->mergeInteractor = $mergeInteractor; |
63 | $this->tokenCheckInteractor = $tokenCheckInteractor; |
64 | $this->titleLookup = $titleLookup; |
65 | $this->exceptionLocalizer = $exceptionLocalizer; |
66 | $this->permissionManager = $permissionManager; |
67 | $this->anonymousEditWarningBuilder = $anonymousEditWarningBuilder; |
68 | } |
69 | |
70 | /** @inheritDoc */ |
71 | public function execute( $subPage ): void { |
72 | $this->setHeaders(); |
73 | $this->outputHeader( 'wikibase-mergelexemes-summary' ); |
74 | |
75 | if ( !$this->userCanExecute( $this->getUser() ) ) { |
76 | $this->displayRestrictionError(); |
77 | } |
78 | $this->checkBlocked(); |
79 | |
80 | $sourceId = $this->getTextParam( self::FROM_ID ); |
81 | $targetId = $this->getTextParam( self::TO_ID ); |
82 | |
83 | if ( $sourceId && $targetId ) { |
84 | $sourceLexemeId = $this->getLexemeId( $sourceId ); |
85 | $targetLexemeId = $this->getLexemeId( $targetId ); |
86 | if ( $sourceLexemeId && $targetLexemeId ) { |
87 | if ( $this->getRequest()->getBool( self::SUCCESS ) ) { |
88 | // redirected back here after a successful edit + temp user, show success now |
89 | // (the success may be inaccurate if users created this URL manually, but that’s harmless) |
90 | $this->showSuccessMessage( $sourceLexemeId, $targetLexemeId ); |
91 | } else { |
92 | $this->mergeLexemes( $sourceLexemeId, $targetLexemeId ); |
93 | } |
94 | } else { |
95 | if ( !$sourceLexemeId ) { |
96 | $this->showInvalidLexemeIdError( $sourceId ); |
97 | } |
98 | if ( !$targetLexemeId ) { |
99 | $this->showInvalidLexemeIdError( $targetId ); |
100 | } |
101 | } |
102 | } |
103 | |
104 | $this->showMergeForm(); |
105 | } |
106 | |
107 | public function setHeaders(): void { |
108 | $out = $this->getOutput(); |
109 | $out->setArticleRelated( false ); |
110 | $out->setPageTitleMsg( $this->getDescription() ); |
111 | } |
112 | |
113 | private function checkBlocked(): void { |
114 | $checkReplica = !$this->getRequest()->wasPosted(); |
115 | $userBlock = $this->getUser()->getBlock( $checkReplica ); |
116 | if ( |
117 | $userBlock !== null && |
118 | $this->permissionManager->isBlockedFrom( |
119 | $this->getUser(), |
120 | $this->getFullTitle(), |
121 | $checkReplica |
122 | ) |
123 | ) { |
124 | throw new UserBlockedError( $userBlock ); |
125 | } |
126 | } |
127 | |
128 | public static function factory( |
129 | PermissionManager $permissionManager, |
130 | AnonymousEditWarningBuilder $anonymousEditWarningBuilder, |
131 | EntityTitleLookup $entityTitleLookup, |
132 | ExceptionLocalizer $exceptionLocalizer, |
133 | SettingsArray $repoSettings, |
134 | TokenCheckInteractor $tokenCheckInteractor, |
135 | MergeLexemesInteractor $mergeLexemesInteractor |
136 | ): self { |
137 | return new self( |
138 | $repoSettings->getSetting( 'specialPageTags' ), |
139 | $mergeLexemesInteractor, |
140 | $tokenCheckInteractor, |
141 | $entityTitleLookup, |
142 | $exceptionLocalizer, |
143 | $permissionManager, |
144 | $anonymousEditWarningBuilder |
145 | ); |
146 | } |
147 | |
148 | private function showMergeForm(): void { |
149 | HTMLForm::factory( 'ooui', $this->getFormElements(), $this->getContext() ) |
150 | ->setId( 'wb-mergelexemes' ) |
151 | ->setPreHtml( $this->anonymousEditWarning() ) |
152 | ->setHeaderHtml( $this->msg( 'wikibase-lexeme-mergelexemes-intro' )->parse() ) |
153 | ->setSubmitID( 'wb-mergelexemes-submit' ) |
154 | ->setSubmitName( 'wikibase-lexeme-mergelexemes-submit' ) |
155 | ->setSubmitTextMsg( 'wikibase-lexeme-mergelexemes-submit' ) |
156 | ->setWrapperLegendMsg( 'special-mergelexemes' ) |
157 | ->setSubmitCallback( static function () { |
158 | } ) |
159 | ->show(); |
160 | } |
161 | |
162 | private function getFormElements(): array { |
163 | return [ |
164 | self::FROM_ID => [ |
165 | 'name' => self::FROM_ID, |
166 | 'default' => $this->getRequest()->getVal( self::FROM_ID ), |
167 | 'type' => 'text', |
168 | 'id' => 'wb-mergelexemes-from-id', |
169 | 'label-message' => 'wikibase-lexeme-mergelexemes-from-id', |
170 | ], |
171 | self::TO_ID => [ |
172 | 'name' => self::TO_ID, |
173 | 'default' => $this->getRequest()->getVal( self::TO_ID ), |
174 | 'type' => 'text', |
175 | 'id' => 'wb-mergelexemes-to-id', |
176 | 'label-message' => 'wikibase-lexeme-mergelexemes-to-id', |
177 | ], |
178 | ]; |
179 | } |
180 | |
181 | private function anonymousEditWarning(): string { |
182 | if ( !$this->getUser()->isRegistered() ) { |
183 | $fullTitle = $this->getPageTitle(); |
184 | return Html::rawElement( |
185 | 'p', |
186 | [ 'class' => 'warning' ], |
187 | $this->anonymousEditWarningBuilder->buildAnonymousEditWarningHTML( $fullTitle->getPrefixedText() ) |
188 | ); |
189 | } |
190 | |
191 | return ''; |
192 | } |
193 | |
194 | private function mergeLexemes( LexemeId $sourceId, LexemeId $targetId ): void { |
195 | try { |
196 | $this->tokenCheckInteractor->checkRequestToken( $this->getContext(), 'wpEditToken' ); |
197 | } catch ( TokenCheckException $e ) { |
198 | $message = $this->exceptionLocalizer->getExceptionMessage( $e ); |
199 | $this->showErrorHTML( $message->parse() ); |
200 | return; |
201 | } |
202 | |
203 | try { |
204 | $status = $this->mergeInteractor->mergeLexemes( |
205 | $sourceId, |
206 | $targetId, |
207 | $this->getContext(), |
208 | null, |
209 | false, |
210 | $this->tags |
211 | ); |
212 | $savedTempUser = $status->getSavedTempUser(); |
213 | } catch ( MergingException $e ) { |
214 | $this->showErrorHTML( $e->getErrorMessage()->escaped() ); |
215 | return; |
216 | } |
217 | |
218 | if ( $savedTempUser !== null ) { |
219 | $redirectUrl = ''; |
220 | $this->getHookRunner()->onTempUserCreatedRedirect( |
221 | $this->getRequest()->getSession(), |
222 | $savedTempUser, |
223 | $this->getPageTitle()->getPrefixedDBkey(), |
224 | wfArrayToCgi( [ |
225 | self::FROM_ID => $sourceId->getSerialization(), |
226 | self::TO_ID => $targetId->getSerialization(), |
227 | self::SUCCESS => '1', |
228 | ] ), |
229 | '', |
230 | $redirectUrl |
231 | ); |
232 | if ( $redirectUrl ) { |
233 | $this->getOutput()->redirect( $redirectUrl ); |
234 | return; // success will be shown when returning here from redirect |
235 | } |
236 | } |
237 | |
238 | $this->showSuccessMessage( $sourceId, $targetId ); |
239 | } |
240 | |
241 | private function getTextParam( string $name ): string { |
242 | $value = $this->getRequest()->getText( $name, '' ); |
243 | return trim( $value ); |
244 | } |
245 | |
246 | /** |
247 | * @param string $idSerialization |
248 | * |
249 | * @return LexemeId|false |
250 | */ |
251 | private function getLexemeId( string $idSerialization ) { |
252 | try { |
253 | return new LexemeId( $idSerialization ); |
254 | } catch ( InvalidArgumentException $e ) { |
255 | return false; |
256 | } |
257 | } |
258 | |
259 | private function showSuccessMessage( LexemeId $sourceId, LexemeId $targetId ): void { |
260 | try { |
261 | $sourceTitle = $this->titleLookup->getTitleForId( $sourceId ); |
262 | $targetTitle = $this->titleLookup->getTitleForId( $targetId ); |
263 | } catch ( Exception $e ) { |
264 | $this->showErrorHTML( $this->exceptionLocalizer->getExceptionMessage( $e )->escaped() ); |
265 | return; |
266 | } |
267 | |
268 | $this->getOutput()->addWikiMsg( |
269 | 'wikibase-lexeme-mergelexemes-success', |
270 | Message::rawParam( |
271 | $this->getLinkRenderer()->makePreloadedLink( $sourceTitle ) |
272 | ), |
273 | Message::rawParam( |
274 | $this->getLinkRenderer()->makePreloadedLink( $targetTitle ) |
275 | ) |
276 | ); |
277 | } |
278 | |
279 | private function showInvalidLexemeIdError( $id ): void { |
280 | $this->showErrorHTML( |
281 | ( new Message( 'wikibase-lexeme-mergelexemes-error-invalid-id', [ $id ] ) ) |
282 | ->escaped() |
283 | ); |
284 | } |
285 | |
286 | protected function getGroupName(): string { |
287 | return 'wikibase'; |
288 | } |
289 | |
290 | protected function showErrorHTML( $error ): void { |
291 | $this->getOutput()->addHTML( '<p class="error">' . $error . '</p>' ); |
292 | } |
293 | |
294 | public function getDescription(): Message { |
295 | return $this->msg( 'special-mergelexemes' ); |
296 | } |
297 | |
298 | } |