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(); } public function getName() { return 'delete'; } public function onView() { return null; } public function show() { $this->useTransactionalTimeLimit(); $this->addHelpLink( 'Help:Sysop deleting and undeleting' ); $this->tempDelete(); } protected function tempDelete() { $article = $this->getArticle(); $title = $this->getTitle(); $context = $this->getContext(); $user = $context->getUser(); $request = $context->getRequest(); $outputPage = $context->getOutput(); $this->runExecuteChecks(); $this->prepareOutput( $context->msg( 'delete-confirm', $title->getPrefixedText() ) ); # Better double-check that it hasn't been deleted yet! $article->getPage()->loadPageData( $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL ); if ( !$article->getPage()->exists() ) { $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) ); $outputPage->wrapWikiMsg( "
' . $link . '
' ); } } /** * @return bool */ protected function isSuppressionAllowed(): bool { return $this->getAuthority()->isAllowed( 'suppressrevision' ); } /** * @param string $reason */ protected function showForm( string $reason ): void { $outputPage = $this->getOutput(); $user = $this->getUser(); $title = $this->getTitle(); $checkWatch = $this->userOptionsLookup->getBoolOption( $user, 'watchdeletion' ) || $this->watchlistManager->isWatched( $user, $title ); $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 = Xml::listDropDownOptions( $dropDownReason, [ 'other' => $this->getFormMsg( self::MSG_REASON_DROPDOWN_OTHER )->inContentLanguage()->text() ] ); $options = Xml::listDropDownOptionsOoui( $options ); $fields[] = new OOUI\FieldLayout( new OOUI\DropdownInputWidget( [ 'name' => 'wpDeleteReasonList', 'inputId' => 'wpDeleteReasonList', 'tabIndex' => 1, 'infusable' => true, 'value' => '', 'options' => $options, ] ), [ 'label' => $this->getFormMsg( self::MSG_COMMENT )->text(), 'align' => 'top', ] ); // 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[] = new OOUI\FieldLayout( new OOUI\TextInputWidget( [ 'name' => 'wpReason', 'inputId' => 'wpReason', 'tabIndex' => 2, 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT, 'infusable' => true, 'value' => $reason, 'autofocus' => true, ] ), [ 'label' => $this->getFormMsg( self::MSG_REASON_OTHER )->text(), 'align' => 'top', ] ); $delPage = $this->deletePageFactory->newDeletePage( $this->getWikiPage(), $this->getAuthority() ); if ( $delPage->canProbablyDeleteAssociatedTalk()->isGood() ) { $fields[] = new OOUI\FieldLayout( new OOUI\CheckboxInputWidget( [ 'name' => 'wpDeleteTalk', 'inputId' => 'wpDeleteTalk', 'tabIndex' => 3, 'selected' => false, ] ), [ 'label' => $this->msg( 'deletepage-deletetalk' )->text(), 'align' => 'inline', 'infusable' => true, ] ); } if ( $user->isRegistered() ) { $fields[] = new OOUI\FieldLayout( new OOUI\CheckboxInputWidget( [ 'name' => 'wpWatch', 'inputId' => 'wpWatch', 'tabIndex' => 4, 'selected' => $checkWatch, ] ), [ 'label' => $this->msg( 'watchthis' )->text(), 'align' => 'inline', 'infusable' => true, ] ); } if ( $this->isSuppressionAllowed() ) { $fields[] = new OOUI\FieldLayout( new OOUI\CheckboxInputWidget( [ 'name' => 'wpSuppress', 'inputId' => 'wpSuppress', 'tabIndex' => 5, 'selected' => false, ] ), [ 'label' => $this->msg( 'revdelete-suppress' )->text(), 'align' => 'inline', 'infusable' => true, ] ); } $fields[] = new OOUI\FieldLayout( new OOUI\ButtonInputWidget( [ 'name' => 'wpConfirmB', 'inputId' => 'wpConfirmB', 'tabIndex' => 6, 'value' => $this->getFormMsg( self::MSG_SUBMIT )->text(), 'label' => $this->getFormMsg( self::MSG_SUBMIT )->text(), 'flags' => [ 'primary', 'destructive' ], 'type' => 'submit', ] ), [ 'align' => 'top', ] ); $fieldset = new OOUI\FieldsetLayout( [ 'label' => $this->getFormMsg( self::MSG_LEGEND )->text(), 'id' => 'mw-delete-table', 'items' => $fields, ] ); $form = new OOUI\FormLayout( [ 'method' => 'post', 'action' => $this->getFormAction(), 'id' => 'deleteconfirm', ] ); $form->appendContent( $fieldset, new OOUI\HtmlSnippet( Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) ) ) ); $outputPage->addHTML( new OOUI\PanelLayout( [ 'classes' => [ 'deletepage-wrapper' ], 'expanded' => false, 'padded' => true, 'framed' => true, 'content' => $form, ] ) ); } /** * @todo Should use Action::checkCanExecute instead */ protected function runExecuteChecks(): void { $permissionStatus = PermissionStatus::newEmpty(); if ( !$this->getAuthority()->definitelyCan( 'delete', $this->getTitle(), $permissionStatus ) ) { throw new PermissionsError( 'delete', $permissionStatus ); } if ( $this->readOnlyMode->isReadOnly() ) { throw new ReadOnlyError; } } /** * @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( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) ); LogEventsList::showLogExtract( $outputPage, 'delete', $this->getTitle() ); } /** * @param Message $pageTitle */ protected function prepareOutput( Message $pageTitle ): void { $outputPage = $this->getOutput(); $outputPage->setPageTitle( $pageTitle ); $outputPage->addBacklinkSubtitle( $this->getTitle() ); $outputPage->setRobotPolicy( 'noindex,nofollow' ); } protected function prepareOutputForForm(): void { $outputPage = $this->getOutput(); $outputPage->addModules( 'mediawiki.action.delete' ); $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 = wfGetDB( DB_REPLICA ); $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; } public function doesWrites() { return true; } }