watchlistManager = $services->getWatchlistManager(); $this->linkRenderer = $services->getLinkRenderer(); $this->backlinkCacheFactory = $services->getBacklinkCacheFactory(); $this->readOnlyMode = $services->getReadOnlyMode(); $this->userOptionsLookup = $services->getUserOptionsLookup(); $this->deletePageFactory = $services->getDeletePageFactory(); $this->deleteRevisionsLimit = $services->getMainConfig()->get( MainConfigNames::DeleteRevisionsLimit ); $this->namespaceInfo = $services->getNamespaceInfo(); $this->titleFormatter = $services->getTitleFormatter(); $this->titleFactory = $services->getTitleFactory(); $this->dbProvider = $services->getConnectionProvider(); } public function getName() { return 'delete'; } public function onSubmit( $data ) { return false; } public function onSuccess() { return false; } protected function usesOOUI() { return true; } protected function getPageTitle() { $title = $this->getTitle(); return $this->msg( 'delete-confirm' )->plaintextParams( $title->getPrefixedText() ); } public function getRestriction() { return 'delete'; } protected function alterForm( HTMLForm $form ) { $title = $this->getTitle(); $form ->setAction( $this->getFormAction() ) ->setWrapperLegendMsg( $this->getFormMsg( self::MSG_LEGEND ) ) ->setWrapperAttributes( [ 'id' => 'mw-delete-table' ] ) ->suppressDefaultSubmit() ->setId( 'deleteconfirm' ) ->setTokenSalt( [ 'delete', $title->getPrefixedText() ] ); } public function show() { $this->setHeaders(); $this->useTransactionalTimeLimit(); $this->addHelpLink( 'Help:Sysop deleting and undeleting' ); // This will throw exceptions if there's a problem $this->checkCanExecute( $this->getUser() ); $this->tempDelete(); } protected function tempDelete() { $article = $this->getArticle(); $title = $this->getTitle(); $context = $this->getContext(); $user = $context->getUser(); $request = $context->getRequest(); $outputPage = $context->getOutput(); # Better double-check that it hasn't been deleted yet! $article->getPage()->loadPageData( $request->wasPosted() ? IDBAccessObject::READ_LATEST : IDBAccessObject::READ_NORMAL ); if ( !$article->getPage()->exists() ) { $outputPage->setPageTitleMsg( $context->msg( 'cannotdelete-title' )->plaintextParams( $title->getPrefixedText() ) ); $outputPage->wrapWikiMsg( "
' . $link . '
' ); } } /** * @return bool */ protected function isSuppressionAllowed(): bool { return $this->getAuthority()->isAllowed( 'suppressrevision' ); } /** * @return array */ protected function getFormFields(): array { $user = $this->getUser(); $title = $this->getTitle(); $article = $this->getArticle(); $fields = []; $dropdownReason = $this->getFormMsg( self::MSG_REASON_DROPDOWN )->inContentLanguage()->text(); // Add additional specific reasons for suppress if ( $this->isSuppressionAllowed() ) { $dropdownReason .= "\n" . $this->getFormMsg( self::MSG_REASON_DROPDOWN_SUPPRESS ) ->inContentLanguage()->text(); } $options = Html::listDropdownOptions( $dropdownReason, [ 'other' => $this->getFormMsg( self::MSG_REASON_DROPDOWN_OTHER )->text() ] ); $fields['DeleteReasonList'] = [ 'type' => 'select', 'id' => 'wpDeleteReasonList', 'tabindex' => 1, 'infusable' => true, 'options' => $options, 'label' => $this->getFormMsg( self::MSG_COMMENT )->text(), ]; // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP // (e.g. emojis) count for two each. This limit is overridden in JS to instead count // Unicode codepoints. $fields['Reason'] = [ 'type' => 'text', 'id' => 'wpReason', 'tabindex' => 2, 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT, 'infusable' => true, 'default' => $this->getDefaultReason(), 'autofocus' => true, 'label' => $this->getFormMsg( self::MSG_REASON_OTHER )->text(), ]; $delPage = $this->deletePageFactory->newDeletePage( $this->getWikiPage(), $this->getAuthority() ); if ( $delPage->canProbablyDeleteAssociatedTalk()->isGood() ) { $fields['DeleteTalk'] = [ 'type' => 'check', 'id' => 'wpDeleteTalk', 'tabindex' => 3, 'default' => false, 'label-message' => 'deletepage-deletetalk', ]; } if ( $user->isRegistered() ) { $checkWatch = $this->userOptionsLookup->getBoolOption( $user, 'watchdeletion' ) || $this->watchlistManager->isWatched( $user, $title ); $fields['Watch'] = [ 'type' => 'check', 'id' => 'wpWatch', 'tabindex' => 4, 'default' => $checkWatch, 'label-message' => 'watchthis', ]; } if ( $this->isSuppressionAllowed() ) { $fields['Suppress'] = [ 'type' => 'check', 'id' => 'wpSuppress', 'tabindex' => 5, 'default' => false, 'label-message' => 'revdelete-suppress', ]; } $fields['ConfirmB'] = [ 'type' => 'submit', 'id' => 'wpConfirmB', 'tabindex' => 6, 'buttonlabel' => $this->getFormMsg( self::MSG_SUBMIT )->text(), 'flags' => [ 'primary', 'destructive' ], ]; $fields['ConfirmationRevId'] = [ 'type' => 'hidden', 'id' => 'wpConfirmationRevId', 'default' => $article->getRevIdFetched(), ]; return $fields; } /** * @return string */ protected function getDeleteReason(): string { $deleteReasonList = $this->getRequest()->getText( 'wpDeleteReasonList', 'other' ); $deleteReason = $this->getRequest()->getText( 'wpReason' ); if ( $deleteReasonList === 'other' ) { return $deleteReason; } elseif ( $deleteReason !== '' ) { // Entry from drop down menu + additional comment $colonseparator = $this->msg( 'colon-separator' )->inContentLanguage()->text(); return $deleteReasonList . $colonseparator . $deleteReason; } else { return $deleteReasonList; } } /** * Show deletion log fragments pertaining to the current page */ protected function showLogEntries(): void { $deleteLogPage = new LogPage( 'delete' ); $outputPage = $this->getContext()->getOutput(); $outputPage->addHTML( Html::element( 'h2', [], $deleteLogPage->getName()->text() ) ); LogEventsList::showLogExtract( $outputPage, 'delete', $this->getTitle() ); } protected function prepareOutputForForm(): void { $outputPage = $this->getOutput(); $outputPage->addModules( 'mediawiki.misc-authed-ooui' ); $outputPage->addModuleStyles( 'mediawiki.action.styles' ); $outputPage->enableOOUI(); } /** * @return string[] */ protected function getFormMessages(): array { return [ self::MSG_REASON_DROPDOWN => 'deletereason-dropdown', self::MSG_REASON_DROPDOWN_SUPPRESS => 'deletereason-dropdown-suppress', self::MSG_REASON_DROPDOWN_OTHER => 'deletereasonotherlist', self::MSG_COMMENT => 'deletecomment', self::MSG_REASON_OTHER => 'deleteotherreason', self::MSG_SUBMIT => 'deletepage-submit', self::MSG_LEGEND => 'delete-legend', self::MSG_EDIT_REASONS => 'delete-edit-reasonlist', self::MSG_EDIT_REASONS_SUPPRESS => 'delete-edit-reasonlist-suppress', ]; } /** * @param string $field One of the self::MSG_* constants * @return Message */ protected function getFormMsg( string $field ): Message { $messages = $this->getFormMessages(); if ( !isset( $messages[$field] ) ) { throw new InvalidArgumentException( "Invalid field $field" ); } return $this->msg( $messages[$field] ); } /** * @return string */ protected function getFormAction(): string { return $this->getTitle()->getLocalURL( 'action=delete' ); } /** * Default reason to be used for the deletion form * * @return string */ protected function getDefaultReason(): string { $requestReason = $this->getRequest()->getText( 'wpReason' ); if ( $requestReason ) { return $requestReason; } try { return $this->getArticle()->getPage()->getAutoDeleteReason(); } catch ( TimeoutException $e ) { throw $e; } catch ( Exception $e ) { # if a page is horribly broken, we still want to be able to # delete it. So be lenient about errors here. # For example, WMF logs show MWException thrown from # ContentHandler::checkModelID(). MWExceptionHandler::logException( $e ); return ''; } } /** * Determines whether a page has a history of more than one revision. * @fixme We should use WikiPage::isNew() here, but it doesn't work right for undeleted pages (T289008) * @return bool */ private function pageHasHistory(): bool { $dbr = $this->dbProvider->getReplicaDatabase(); $res = $dbr->newSelectQueryBuilder() ->select( '*' ) ->from( 'revision' ) ->where( [ 'rev_page' => $this->getTitle()->getArticleID() ] ) ->andWhere( [ $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0' ] )->limit( 2 ) ->caller( __METHOD__ ) ->fetchRowCount(); return $res > 1; } }