Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 343 |
|
0.00% |
0 / 28 |
CRAP | |
0.00% |
0 / 1 |
DeleteAction | |
0.00% |
0 / 343 |
|
0.00% |
0 / 28 |
4692 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onSubmit | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onSuccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
usesOOUI | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageTitle | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRestriction | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
alterForm | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
show | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
tempDelete | |
0.00% |
0 / 70 |
|
0.00% |
0 / 1 |
132 | |||
showSuccessMessages | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
72 | |||
showEditedWarning | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
showHistoryWarnings | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
6 | |||
showFormWarnings | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
showBacklinksWarning | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
showSubpagesWarnings | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
tempConfirmDelete | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
showEditReasonsLinks | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
12 | |||
isSuppressionAllowed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFormFields | |
0.00% |
0 / 70 |
|
0.00% |
0 / 1 |
42 | |||
getDeleteReason | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
showLogEntries | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
prepareOutputForForm | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getFormMessages | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getFormMsg | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getFormAction | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultReason | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
pageHasHistory | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License |
14 | * along with this program; if not, write to the Free Software |
15 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
16 | * |
17 | * @file |
18 | * @ingroup Actions |
19 | */ |
20 | |
21 | use MediaWiki\Cache\BacklinkCacheFactory; |
22 | use MediaWiki\CommentStore\CommentStore; |
23 | use MediaWiki\Context\IContextSource; |
24 | use MediaWiki\Html\Html; |
25 | use MediaWiki\Linker\LinkRenderer; |
26 | use MediaWiki\MainConfigNames; |
27 | use MediaWiki\MediaWikiServices; |
28 | use MediaWiki\Message\Message; |
29 | use MediaWiki\Page\DeletePage; |
30 | use MediaWiki\Page\DeletePageFactory; |
31 | use MediaWiki\Revision\RevisionRecord; |
32 | use MediaWiki\Status\Status; |
33 | use MediaWiki\Title\NamespaceInfo; |
34 | use MediaWiki\Title\TitleFactory; |
35 | use MediaWiki\Title\TitleFormatter; |
36 | use MediaWiki\User\Options\UserOptionsLookup; |
37 | use MediaWiki\Watchlist\WatchlistManager; |
38 | use Wikimedia\Rdbms\IConnectionProvider; |
39 | use Wikimedia\Rdbms\ReadOnlyMode; |
40 | use Wikimedia\RequestTimeout\TimeoutException; |
41 | |
42 | /** |
43 | * Handle page deletion |
44 | * |
45 | * @ingroup Actions |
46 | */ |
47 | class DeleteAction extends FormAction { |
48 | |
49 | /** |
50 | * Constants used to localize form fields |
51 | */ |
52 | protected const MSG_REASON_DROPDOWN = 'reason-dropdown'; |
53 | protected const MSG_REASON_DROPDOWN_SUPPRESS = 'reason-dropdown-suppress'; |
54 | protected const MSG_REASON_DROPDOWN_OTHER = 'reason-dropdown-other'; |
55 | protected const MSG_COMMENT = 'comment'; |
56 | protected const MSG_REASON_OTHER = 'reason-other'; |
57 | protected const MSG_SUBMIT = 'submit'; |
58 | protected const MSG_LEGEND = 'legend'; |
59 | protected const MSG_EDIT_REASONS = 'edit-reasons'; |
60 | protected const MSG_EDIT_REASONS_SUPPRESS = 'edit-reasons-suppress'; |
61 | |
62 | protected WatchlistManager $watchlistManager; |
63 | protected LinkRenderer $linkRenderer; |
64 | private BacklinkCacheFactory $backlinkCacheFactory; |
65 | protected ReadOnlyMode $readOnlyMode; |
66 | protected UserOptionsLookup $userOptionsLookup; |
67 | private DeletePageFactory $deletePageFactory; |
68 | private int $deleteRevisionsLimit; |
69 | private NamespaceInfo $namespaceInfo; |
70 | private TitleFormatter $titleFormatter; |
71 | private TitleFactory $titleFactory; |
72 | |
73 | private IConnectionProvider $dbProvider; |
74 | |
75 | /** |
76 | * @inheritDoc |
77 | */ |
78 | public function __construct( Article $article, IContextSource $context ) { |
79 | parent::__construct( $article, $context ); |
80 | $services = MediaWikiServices::getInstance(); |
81 | $this->watchlistManager = $services->getWatchlistManager(); |
82 | $this->linkRenderer = $services->getLinkRenderer(); |
83 | $this->backlinkCacheFactory = $services->getBacklinkCacheFactory(); |
84 | $this->readOnlyMode = $services->getReadOnlyMode(); |
85 | $this->userOptionsLookup = $services->getUserOptionsLookup(); |
86 | $this->deletePageFactory = $services->getDeletePageFactory(); |
87 | $this->deleteRevisionsLimit = $services->getMainConfig()->get( MainConfigNames::DeleteRevisionsLimit ); |
88 | $this->namespaceInfo = $services->getNamespaceInfo(); |
89 | $this->titleFormatter = $services->getTitleFormatter(); |
90 | $this->titleFactory = $services->getTitleFactory(); |
91 | $this->dbProvider = $services->getConnectionProvider(); |
92 | } |
93 | |
94 | public function getName() { |
95 | return 'delete'; |
96 | } |
97 | |
98 | public function onSubmit( $data ) { |
99 | return false; |
100 | } |
101 | |
102 | public function onSuccess() { |
103 | return false; |
104 | } |
105 | |
106 | protected function usesOOUI() { |
107 | return true; |
108 | } |
109 | |
110 | protected function getPageTitle() { |
111 | $title = $this->getTitle(); |
112 | return $this->msg( 'delete-confirm' )->plaintextParams( $title->getPrefixedText() ); |
113 | } |
114 | |
115 | public function getRestriction() { |
116 | return 'delete'; |
117 | } |
118 | |
119 | protected function alterForm( HTMLForm $form ) { |
120 | $title = $this->getTitle(); |
121 | $form |
122 | ->setAction( $this->getFormAction() ) |
123 | ->setWrapperLegendMsg( $this->getFormMsg( self::MSG_LEGEND ) ) |
124 | ->setWrapperAttributes( [ 'id' => 'mw-delete-table' ] ) |
125 | ->suppressDefaultSubmit() |
126 | ->setId( 'deleteconfirm' ) |
127 | ->setTokenSalt( [ 'delete', $title->getPrefixedText() ] ); |
128 | } |
129 | |
130 | public function show() { |
131 | $this->setHeaders(); |
132 | $this->useTransactionalTimeLimit(); |
133 | $this->addHelpLink( 'Help:Sysop deleting and undeleting' ); |
134 | |
135 | // This will throw exceptions if there's a problem |
136 | $this->checkCanExecute( $this->getUser() ); |
137 | |
138 | $this->tempDelete(); |
139 | } |
140 | |
141 | protected function tempDelete() { |
142 | $article = $this->getArticle(); |
143 | $title = $this->getTitle(); |
144 | $context = $this->getContext(); |
145 | $user = $context->getUser(); |
146 | $request = $context->getRequest(); |
147 | $outputPage = $context->getOutput(); |
148 | |
149 | # Better double-check that it hasn't been deleted yet! |
150 | $article->getPage()->loadPageData( |
151 | $request->wasPosted() ? IDBAccessObject::READ_LATEST : IDBAccessObject::READ_NORMAL |
152 | ); |
153 | if ( !$article->getPage()->exists() ) { |
154 | $outputPage->setPageTitleMsg( |
155 | $context->msg( 'cannotdelete-title' )->plaintextParams( $title->getPrefixedText() ) |
156 | ); |
157 | $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>", |
158 | [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ] |
159 | ); |
160 | $this->showLogEntries(); |
161 | |
162 | return; |
163 | } |
164 | |
165 | # If we are not processing the results of the deletion confirmation dialog, show the form |
166 | $token = $request->getVal( 'wpEditToken' ); |
167 | if ( !$request->wasPosted() || !$user->matchEditToken( $token, [ 'delete', $title->getPrefixedText() ] ) ) { |
168 | $this->tempConfirmDelete(); |
169 | return; |
170 | } |
171 | |
172 | # Check to make sure the page has not been edited while the deletion was being confirmed |
173 | if ( $article->getRevIdFetched() !== $request->getIntOrNull( 'wpConfirmationRevId' ) ) { |
174 | $this->showEditedWarning(); |
175 | $this->tempConfirmDelete(); |
176 | return; |
177 | } |
178 | |
179 | # Flag to hide all contents of the archived revisions |
180 | $suppress = $request->getCheck( 'wpSuppress' ) && |
181 | $context->getAuthority()->isAllowed( 'suppressrevision' ); |
182 | |
183 | $context = $this->getContext(); |
184 | $deletePage = $this->deletePageFactory->newDeletePage( |
185 | $this->getWikiPage(), |
186 | $context->getAuthority() |
187 | ); |
188 | $shouldDeleteTalk = $request->getCheck( 'wpDeleteTalk' ) && |
189 | $deletePage->canProbablyDeleteAssociatedTalk()->isGood(); |
190 | $deletePage->setDeleteAssociatedTalk( $shouldDeleteTalk ); |
191 | $status = $deletePage |
192 | ->setSuppress( $suppress ) |
193 | ->deleteIfAllowed( $this->getDeleteReason() ); |
194 | |
195 | if ( $status->isOK() ) { |
196 | $outputPage->setPageTitleMsg( $this->msg( 'actioncomplete' ) ); |
197 | $outputPage->setRobotPolicy( 'noindex,nofollow' ); |
198 | |
199 | if ( !$status->isGood() ) { |
200 | // If the page (and/or its talk) couldn't be found (e.g. because it was deleted in another request), |
201 | // let the user know. |
202 | $outputPage->addHTML( |
203 | Html::warningBox( |
204 | $outputPage->parseAsContent( |
205 | Status::wrap( $status )->getWikiText( |
206 | false, |
207 | false, |
208 | $context->getLanguage() |
209 | ) |
210 | ) |
211 | ) |
212 | ); |
213 | } |
214 | |
215 | $this->showSuccessMessages( |
216 | $deletePage->getSuccessfulDeletionsIDs(), |
217 | $deletePage->deletionsWereScheduled() |
218 | ); |
219 | |
220 | if ( !$status->isGood() ) { |
221 | $this->showLogEntries(); |
222 | } |
223 | $outputPage->returnToMain(); |
224 | } else { |
225 | $outputPage->setPageTitleMsg( |
226 | $this->msg( 'cannotdelete-title' )->plaintextParams( $this->getTitle()->getPrefixedText() ) |
227 | ); |
228 | |
229 | $outputPage->wrapWikiTextAsInterface( |
230 | 'error mw-error-cannotdelete', |
231 | Status::wrap( $status )->getWikiText( false, false, $context->getLanguage() ) |
232 | ); |
233 | $this->showLogEntries(); |
234 | } |
235 | |
236 | $this->watchlistManager->setWatch( $request->getCheck( 'wpWatch' ), $context->getAuthority(), $title ); |
237 | } |
238 | |
239 | /** |
240 | * Display success messages |
241 | * |
242 | * @param array $deleted |
243 | * @param array $scheduled |
244 | * @return void |
245 | */ |
246 | private function showSuccessMessages( array $deleted, array $scheduled ): void { |
247 | $outputPage = $this->getContext()->getOutput(); |
248 | $loglink = '[[Special:Log/delete|' . $this->msg( 'deletionlog' )->text() . ']]'; |
249 | $pageBaseDisplayTitle = wfEscapeWikiText( $this->getTitle()->getPrefixedText() ); |
250 | $pageTalkDisplayTitle = wfEscapeWikiText( $this->titleFormatter->getPrefixedText( |
251 | $this->namespaceInfo->getTalkPage( $this->getTitle() ) |
252 | ) ); |
253 | |
254 | $deletedTalk = $deleted[DeletePage::PAGE_TALK] ?? false; |
255 | $deletedBase = $deleted[DeletePage::PAGE_BASE]; |
256 | $scheduledTalk = $scheduled[DeletePage::PAGE_TALK] ?? false; |
257 | $scheduledBase = $scheduled[DeletePage::PAGE_BASE]; |
258 | |
259 | if ( $deletedBase && $deletedTalk ) { |
260 | $outputPage->addWikiMsg( 'deleted-page-and-talkpage', |
261 | $pageBaseDisplayTitle, |
262 | $pageTalkDisplayTitle, |
263 | $loglink ); |
264 | } elseif ( $deletedBase ) { |
265 | $outputPage->addWikiMsg( 'deletedtext', $pageBaseDisplayTitle, $loglink ); |
266 | } elseif ( $deletedTalk ) { |
267 | $outputPage->addWikiMsg( 'deletedtext', $pageTalkDisplayTitle, $loglink ); |
268 | } |
269 | |
270 | // run hook if article was deleted |
271 | if ( $deletedBase ) { |
272 | $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage ); |
273 | } |
274 | |
275 | if ( $scheduledBase ) { |
276 | $outputPage->addWikiMsg( 'delete-scheduled', $pageBaseDisplayTitle ); |
277 | } |
278 | |
279 | if ( $scheduledTalk ) { |
280 | $outputPage->addWikiMsg( 'delete-scheduled', $pageTalkDisplayTitle ); |
281 | } |
282 | } |
283 | |
284 | protected function showEditedWarning(): void { |
285 | $this->getOutput()->addHTML( |
286 | Html::warningBox( $this->getContext()->msg( 'editedwhiledeleting' )->parse() ) |
287 | ); |
288 | } |
289 | |
290 | private function showHistoryWarnings(): void { |
291 | $context = $this->getContext(); |
292 | $title = $this->getTitle(); |
293 | |
294 | // The following can use the real revision count as this is only being shown for users |
295 | // that can delete this page. |
296 | // This, as a side-effect, also makes sure that the following query isn't being run for |
297 | // pages with a larger history, unless the user has the 'bigdelete' right |
298 | // (and is about to delete this page). |
299 | $revisions = (int)$this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder() |
300 | ->select( 'COUNT(rev_page)' ) |
301 | ->from( 'revision' ) |
302 | ->where( [ 'rev_page' => $title->getArticleID() ] ) |
303 | ->caller( __METHOD__ ) |
304 | ->fetchField(); |
305 | |
306 | // @todo i18n issue/patchwork message |
307 | $context->getOutput()->addHTML( |
308 | '<strong class="mw-delete-warning-revisions">' . |
309 | $context->msg( 'historywarning' )->numParams( $revisions )->parse() . |
310 | $context->msg( 'word-separator' )->escaped() . $this->linkRenderer->makeKnownLink( |
311 | $title, |
312 | $context->msg( 'history' )->text(), |
313 | [], |
314 | [ 'action' => 'history' ] ) . |
315 | '</strong>' |
316 | ); |
317 | |
318 | if ( $title->isBigDeletion() ) { |
319 | $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", |
320 | [ |
321 | 'delete-warning-toobig', |
322 | $context->getLanguage()->formatNum( $this->deleteRevisionsLimit ) |
323 | ] |
324 | ); |
325 | } |
326 | } |
327 | |
328 | protected function showFormWarnings(): void { |
329 | $this->showBacklinksWarning(); |
330 | $this->showSubpagesWarnings(); |
331 | } |
332 | |
333 | private function showBacklinksWarning(): void { |
334 | $backlinkCache = $this->backlinkCacheFactory->getBacklinkCache( $this->getTitle() ); |
335 | if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) { |
336 | $this->getOutput()->addHTML( |
337 | Html::warningBox( |
338 | $this->msg( 'deleting-backlinks-warning' )->parse(), |
339 | 'plainlinks' |
340 | ) |
341 | ); |
342 | } |
343 | } |
344 | |
345 | protected function showSubpagesWarnings(): void { |
346 | $title = $this->getTitle(); |
347 | $subpageCount = count( $title->getSubpages( 51 ) ); |
348 | if ( $subpageCount ) { |
349 | $this->getOutput()->addHTML( |
350 | Html::warningBox( |
351 | $this->msg( 'deleting-subpages-warning' )->numParams( $subpageCount )->parse(), |
352 | 'plainlinks' |
353 | ) |
354 | ); |
355 | } |
356 | |
357 | if ( !$title->isTalkPage() ) { |
358 | $talkPageTitle = $this->titleFactory->newFromLinkTarget( $this->namespaceInfo->getTalkPage( $title ) ); |
359 | $subpageCount = count( $talkPageTitle->getSubpages( 51 ) ); |
360 | if ( $subpageCount ) { |
361 | $this->getOutput()->addHTML( |
362 | Html::warningBox( |
363 | $this->msg( 'deleting-talkpage-subpages-warning' )->numParams( $subpageCount )->parse(), |
364 | 'plainlinks' |
365 | ) |
366 | ); |
367 | } |
368 | } |
369 | } |
370 | |
371 | private function tempConfirmDelete(): void { |
372 | $this->prepareOutputForForm(); |
373 | $context = $this->getContext(); |
374 | $outputPage = $context->getOutput(); |
375 | $article = $this->getArticle(); |
376 | |
377 | $reason = $this->getDefaultReason(); |
378 | |
379 | // oldid is set to the revision id of the page when the page was displayed. |
380 | // Check to make sure the page has not been edited between loading the page |
381 | // and clicking the delete link |
382 | $oldid = $context->getRequest()->getIntOrNull( 'oldid' ); |
383 | if ( $oldid !== null && $oldid !== $article->getRevIdFetched() ) { |
384 | $this->showEditedWarning(); |
385 | } |
386 | // If the page has a history, insert a warning |
387 | if ( $this->pageHasHistory() ) { |
388 | $this->showHistoryWarnings(); |
389 | } |
390 | $this->showFormWarnings(); |
391 | |
392 | $outputPage->addWikiMsg( 'confirmdeletetext' ); |
393 | |
394 | // FIXME: Replace (or at least rename) this hook |
395 | $this->getHookRunner()->onArticleConfirmDelete( $this->getArticle(), $outputPage, $reason ); |
396 | |
397 | $form = $this->getForm(); |
398 | if ( $form->show() ) { |
399 | $this->onSuccess(); |
400 | } |
401 | |
402 | $this->showEditReasonsLinks(); |
403 | $this->showLogEntries(); |
404 | } |
405 | |
406 | protected function showEditReasonsLinks(): void { |
407 | if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) { |
408 | $link = ''; |
409 | if ( $this->isSuppressionAllowed() ) { |
410 | $link .= $this->linkRenderer->makeKnownLink( |
411 | $this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS )->inContentLanguage()->getTitle(), |
412 | $this->getFormMsg( self::MSG_EDIT_REASONS_SUPPRESS )->text(), |
413 | [], |
414 | [ 'action' => 'edit' ] |
415 | ); |
416 | $link .= $this->msg( 'pipe-separator' )->escaped(); |
417 | } |
418 | $link .= $this->linkRenderer->makeKnownLink( |
419 | $this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->getTitle(), |
420 | $this->getFormMsg( self::MSG_EDIT_REASONS )->text(), |
421 | [], |
422 | [ 'action' => 'edit' ] |
423 | ); |
424 | $this->getOutput()->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' ); |
425 | } |
426 | } |
427 | |
428 | /** |
429 | * @return bool |
430 | */ |
431 | protected function isSuppressionAllowed(): bool { |
432 | return $this->getAuthority()->isAllowed( 'suppressrevision' ); |
433 | } |
434 | |
435 | /** |
436 | * @return array |
437 | */ |
438 | protected function getFormFields(): array { |
439 | $user = $this->getUser(); |
440 | $title = $this->getTitle(); |
441 | $article = $this->getArticle(); |
442 | |
443 | $fields = []; |
444 | |
445 | $dropdownReason = $this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->text(); |
446 | // Add additional specific reasons for suppress |
447 | if ( $this->isSuppressionAllowed() ) { |
448 | $dropdownReason .= "\n" . $this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS ) |
449 | ->inContentLanguage()->text(); |
450 | } |
451 | |
452 | $options = Html::listDropdownOptions( |
453 | $dropdownReason, |
454 | [ 'other' => $this->getFormMsg( self::MSG_REASON_DROPDOWN_OTHER )->text() ] |
455 | ); |
456 | |
457 | $fields['DeleteReasonList'] = [ |
458 | 'type' => 'select', |
459 | 'id' => 'wpDeleteReasonList', |
460 | 'tabindex' => 1, |
461 | 'infusable' => true, |
462 | 'options' => $options, |
463 | 'label' => $this->getFormMsg( self::MSG_COMMENT )->text(), |
464 | ]; |
465 | |
466 | // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP |
467 | // (e.g. emojis) count for two each. This limit is overridden in JS to instead count |
468 | // Unicode codepoints. |
469 | $fields['Reason'] = [ |
470 | 'type' => 'text', |
471 | 'id' => 'wpReason', |
472 | 'tabindex' => 2, |
473 | 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT, |
474 | 'infusable' => true, |
475 | 'default' => $this->getDefaultReason(), |
476 | 'autofocus' => true, |
477 | 'label' => $this->getFormMsg( self::MSG_REASON_OTHER )->text(), |
478 | ]; |
479 | |
480 | $delPage = $this->deletePageFactory->newDeletePage( $this->getWikiPage(), $this->getAuthority() ); |
481 | if ( $delPage->canProbablyDeleteAssociatedTalk()->isGood() ) { |
482 | $fields['DeleteTalk'] = [ |
483 | 'type' => 'check', |
484 | 'id' => 'wpDeleteTalk', |
485 | 'tabindex' => 3, |
486 | 'default' => false, |
487 | 'label-message' => 'deletepage-deletetalk', |
488 | ]; |
489 | } |
490 | |
491 | if ( $user->isRegistered() ) { |
492 | $checkWatch = $this->userOptionsLookup->getBoolOption( $user, 'watchdeletion' ) || |
493 | $this->watchlistManager->isWatched( $user, $title ); |
494 | $fields['Watch'] = [ |
495 | 'type' => 'check', |
496 | 'id' => 'wpWatch', |
497 | 'tabindex' => 4, |
498 | 'default' => $checkWatch, |
499 | 'label-message' => 'watchthis', |
500 | ]; |
501 | } |
502 | if ( $this->isSuppressionAllowed() ) { |
503 | $fields['Suppress'] = [ |
504 | 'type' => 'check', |
505 | 'id' => 'wpSuppress', |
506 | 'tabindex' => 5, |
507 | 'default' => false, |
508 | 'label-message' => 'revdelete-suppress', |
509 | ]; |
510 | } |
511 | |
512 | $fields['ConfirmB'] = [ |
513 | 'type' => 'submit', |
514 | 'id' => 'wpConfirmB', |
515 | 'tabindex' => 6, |
516 | 'buttonlabel' => $this->getFormMsg( self::MSG_SUBMIT )->text(), |
517 | 'flags' => [ 'primary', 'destructive' ], |
518 | ]; |
519 | |
520 | $fields['ConfirmationRevId'] = [ |
521 | 'type' => 'hidden', |
522 | 'id' => 'wpConfirmationRevId', |
523 | 'default' => $article->getRevIdFetched(), |
524 | ]; |
525 | |
526 | return $fields; |
527 | } |
528 | |
529 | /** |
530 | * @return string |
531 | */ |
532 | protected function getDeleteReason(): string { |
533 | $deleteReasonList = $this->getRequest()->getText( 'wpDeleteReasonList', 'other' ); |
534 | $deleteReason = $this->getRequest()->getText( 'wpReason' ); |
535 | |
536 | if ( $deleteReasonList === 'other' ) { |
537 | return $deleteReason; |
538 | } elseif ( $deleteReason !== '' ) { |
539 | // Entry from drop down menu + additional comment |
540 | $colonseparator = $this->msg( 'colon-separator' )->inContentLanguage()->text(); |
541 | return $deleteReasonList . $colonseparator . $deleteReason; |
542 | } else { |
543 | return $deleteReasonList; |
544 | } |
545 | } |
546 | |
547 | /** |
548 | * Show deletion log fragments pertaining to the current page |
549 | */ |
550 | protected function showLogEntries(): void { |
551 | $deleteLogPage = new LogPage( 'delete' ); |
552 | $outputPage = $this->getContext()->getOutput(); |
553 | $outputPage->addHTML( Html::element( 'h2', [], $deleteLogPage->getName()->text() ) ); |
554 | LogEventsList::showLogExtract( $outputPage, 'delete', $this->getTitle() ); |
555 | } |
556 | |
557 | protected function prepareOutputForForm(): void { |
558 | $outputPage = $this->getOutput(); |
559 | $outputPage->addModules( 'mediawiki.misc-authed-ooui' ); |
560 | $outputPage->addModuleStyles( 'mediawiki.action.styles' ); |
561 | $outputPage->enableOOUI(); |
562 | } |
563 | |
564 | /** |
565 | * @return string[] |
566 | */ |
567 | protected function getFormMessages(): array { |
568 | return [ |
569 | self::MSG_REASON_DROPDOWN => 'deletereason-dropdown', |
570 | self::MSG_REASON_DROPDOWN_SUPPRESS => 'deletereason-dropdown-suppress', |
571 | self::MSG_REASON_DROPDOWN_OTHER => 'deletereasonotherlist', |
572 | self::MSG_COMMENT => 'deletecomment', |
573 | self::MSG_REASON_OTHER => 'deleteotherreason', |
574 | self::MSG_SUBMIT => 'deletepage-submit', |
575 | self::MSG_LEGEND => 'delete-legend', |
576 | self::MSG_EDIT_REASONS => 'delete-edit-reasonlist', |
577 | self::MSG_EDIT_REASONS_SUPPRESS => 'delete-edit-reasonlist-suppress', |
578 | ]; |
579 | } |
580 | |
581 | /** |
582 | * @param string $field One of the self::MSG_* constants |
583 | * @return Message |
584 | */ |
585 | protected function getFormMsg( string $field ): Message { |
586 | $messages = $this->getFormMessages(); |
587 | if ( !isset( $messages[$field] ) ) { |
588 | throw new InvalidArgumentException( "Invalid field $field" ); |
589 | } |
590 | return $this->msg( $messages[$field] ); |
591 | } |
592 | |
593 | /** |
594 | * @return string |
595 | */ |
596 | protected function getFormAction(): string { |
597 | return $this->getTitle()->getLocalURL( 'action=delete' ); |
598 | } |
599 | |
600 | /** |
601 | * Default reason to be used for the deletion form |
602 | * |
603 | * @return string |
604 | */ |
605 | protected function getDefaultReason(): string { |
606 | $requestReason = $this->getRequest()->getText( 'wpReason' ); |
607 | if ( $requestReason ) { |
608 | return $requestReason; |
609 | } |
610 | |
611 | try { |
612 | return $this->getArticle()->getPage()->getAutoDeleteReason(); |
613 | } catch ( TimeoutException $e ) { |
614 | throw $e; |
615 | } catch ( Exception $e ) { |
616 | # if a page is horribly broken, we still want to be able to |
617 | # delete it. So be lenient about errors here. |
618 | # For example, WMF logs show MWException thrown from |
619 | # ContentHandler::checkModelID(). |
620 | MWExceptionHandler::logException( $e ); |
621 | return ''; |
622 | } |
623 | } |
624 | |
625 | /** |
626 | * Determines whether a page has a history of more than one revision. |
627 | * @fixme We should use WikiPage::isNew() here, but it doesn't work right for undeleted pages (T289008) |
628 | * @return bool |
629 | */ |
630 | private function pageHasHistory(): bool { |
631 | $dbr = $this->dbProvider->getReplicaDatabase(); |
632 | $res = $dbr->newSelectQueryBuilder() |
633 | ->select( '*' ) |
634 | ->from( 'revision' ) |
635 | ->where( [ 'rev_page' => $this->getTitle()->getArticleID() ] ) |
636 | ->andWhere( |
637 | [ $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0' ] |
638 | )->limit( 2 ) |
639 | ->caller( __METHOD__ ) |
640 | ->fetchRowCount(); |
641 | |
642 | return $res > 1; |
643 | } |
644 | } |