permissionManager = $permissionManager; } public function doesWrites() { return true; } public function execute( $par ) { $this->checkPermissions(); $this->checkReadOnly(); $output = $this->getOutput(); $user = $this->getUser(); $request = $this->getRequest(); $this->setHeaders(); $this->outputHeader(); $output->addModules( [ 'mediawiki.special.edittags' ] ); $output->addModuleStyles( [ 'mediawiki.interface.helpers.styles', 'mediawiki.special' ] ); $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' ); // Handle our many different possible input types $ids = $request->getVal( 'ids' ); if ( $ids !== null ) { // Allow CSV from the form hidden field, or a single ID for show/hide links $this->ids = explode( ',', $ids ); } else { // Array input $this->ids = array_keys( $request->getArray( 'ids', [] ) ); } $this->ids = array_unique( array_filter( $this->ids ) ); // No targets? if ( count( $this->ids ) == 0 ) { throw new ErrorPageError( 'tags-edit-nooldid-title', 'tags-edit-nooldid-text' ); } $this->typeName = $request->getVal( 'type' ); $this->targetObj = Title::newFromText( $request->getText( 'target' ) ); // sanity check of parameter switch ( $this->typeName ) { case 'logentry': case 'logging': $this->typeName = 'logentry'; break; default: $this->typeName = 'revision'; break; } // Allow the list type to adjust the passed target // Yuck! Copied straight out of SpecialRevisiondelete, but it does exactly // what we want $this->targetObj = RevisionDeleter::suggestTarget( $this->typeName === 'revision' ? 'revision' : 'logging', $this->targetObj, $this->ids ); $this->isAllowed = $this->permissionManager->userHasRight( $user, 'changetags' ); $this->reason = $request->getVal( 'wpReason' ); // We need a target page! if ( $this->targetObj === null ) { $output->addWikiMsg( 'undelete-header' ); return; } // Check blocks if ( $this->permissionManager->isBlockedFrom( $user, $this->targetObj ) ) { throw new UserBlockedError( $user->getBlock(), $user, $this->getLanguage(), $request->getIP() ); } // Give a link to the logs/hist for this page $this->showConvenienceLinks(); // Either submit or create our form if ( $this->isAllowed && $this->submitClicked ) { $this->submit(); } else { $this->showForm(); } // Show relevant lines from the tag log $tagLogPage = new LogPage( 'tag' ); $output->addHTML( "

" . $tagLogPage->getName()->escaped() . "

\n" ); LogEventsList::showLogExtract( $output, 'tag', $this->targetObj, '', /* user */ [ 'lim' => 25, 'conds' => [], 'useMaster' => $this->wasSaved ] ); } /** * Show some useful links in the subtitle */ protected function showConvenienceLinks() { // Give a link to the logs/hist for this page if ( $this->targetObj ) { // Also set header tabs to be for the target. $this->getSkin()->setRelevantTitle( $this->targetObj ); $linkRenderer = $this->getLinkRenderer(); $links = []; $links[] = $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'Log' ), $this->msg( 'viewpagelogs' )->text(), [], [ 'page' => $this->targetObj->getPrefixedText(), 'wpfilters' => [ 'tag' ], ] ); if ( !$this->targetObj->isSpecialPage() ) { // Give a link to the page history $links[] = $linkRenderer->makeKnownLink( $this->targetObj, $this->msg( 'pagehist' )->text(), [], [ 'action' => 'history' ] ); } // Link to Special:Tags $links[] = $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'Tags' ), $this->msg( 'tags-edit-manage-link' )->text() ); // Logs themselves don't have histories or archived revisions $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) ); } } /** * Get the list object for this request * @return ChangeTagsList */ protected function getList() { if ( $this->revList === null ) { $this->revList = ChangeTagsList::factory( $this->typeName, $this->getContext(), $this->targetObj, $this->ids ); } return $this->revList; } /** * Show a list of items that we will operate on, and show a form which allows * the user to modify the tags applied to those items. */ protected function showForm() { $out = $this->getOutput(); // Messages: tags-edit-revision-selected, tags-edit-logentry-selected $out->wrapWikiMsg( "$1", [ "tags-edit-{$this->typeName}-selected", $this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ] ); $this->addHelpLink( 'Help:Tags' ); $out->addHTML( "" ); // Explanation text $out->wrapWikiMsg( '

$1

', "tags-edit-{$this->typeName}-explanation" ); // Show form if the user can submit if ( $this->isAllowed ) { $form = Xml::openElement( 'form', [ 'method' => 'post', 'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ), 'id' => 'mw-revdel-form-revisions' ] ) . Xml::fieldset( $this->msg( "tags-edit-{$this->typeName}-legend", count( $this->ids ) )->text() ) . $this->buildCheckBoxes() . Xml::openElement( 'table' ) . "\n" . '' . Xml::label( $this->msg( 'tags-edit-reason' )->text(), 'wpReason' ) . '' . '' . Xml::input( 'wpReason', 60, $this->reason, [ 'id' => 'wpReason', // 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. 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT, ] ) . '' . "\n" . '' . '' . Xml::submitButton( $this->msg( "tags-edit-{$this->typeName}-submit", $numRevisions )->text(), [ 'name' => 'wpSubmit' ] ) . '' . "\n" . Xml::closeElement( 'table' ) . Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) . Html::hidden( 'target', $this->targetObj->getPrefixedText() ) . Html::hidden( 'type', $this->typeName ) . Html::hidden( 'ids', implode( ',', $this->ids ) ) . Xml::closeElement( 'fieldset' ) . "\n" . Xml::closeElement( 'form' ) . "\n"; } else { $form = ''; } $out->addHTML( $form ); } /** * @return string HTML */ protected function buildCheckBoxes() { // If there is just one item, provide the user with a multi-select field $list = $this->getList(); $tags = []; if ( $list->length() == 1 ) { $list->reset(); $tags = $list->current()->getTags(); if ( $tags ) { $tags = explode( ',', $tags ); } else { $tags = []; } $html = ''; $html .= ''; $tagSelect = $this->getTagSelect( $tags, $this->msg( 'tags-edit-new-tags' )->plain() ); $html .= '
' . $this->msg( 'tags-edit-existing-tags' )->escaped() . ''; if ( $tags ) { $html .= $this->getLanguage()->commaList( array_map( 'htmlspecialchars', $tags ) ); } else { $html .= $this->msg( 'tags-edit-existing-tags-none' )->parse(); } $html .= '
' . $tagSelect[0] . '' . $tagSelect[1]; } else { // Otherwise, use a multi-select field for adding tags, and a list of // checkboxes for removing them for ( $list->reset(); $list->current(); $list->next() ) { $currentTags = $list->current()->getTags(); if ( $currentTags ) { $tags = array_merge( $tags, explode( ',', $currentTags ) ); } } $tags = array_unique( $tags ); $html = '
'; $tagSelect = $this->getTagSelect( [], $this->msg( 'tags-edit-add' )->plain() ); $html .= '

' . $tagSelect[0] . '

' . $tagSelect[1] . '
'; $html .= Xml::element( 'p', null, $this->msg( 'tags-edit-remove' )->plain() ); $html .= Xml::checkLabel( $this->msg( 'tags-edit-remove-all-tags' )->plain(), 'wpRemoveAllTags', 'mw-edittags-remove-all' ); $i = 0; // used for generating checkbox IDs only foreach ( $tags as $tag ) { $html .= Xml::element( 'br' ) . "\n" . Xml::checkLabel( $tag, 'wpTagsToRemove[]', 'mw-edittags-remove-' . $i++, false, [ 'value' => $tag, 'class' => 'mw-edittags-remove-checkbox', ] ); } } // also output the tags currently applied as a hidden form field, so we // know what to remove from the revision/log entry when the form is submitted $html .= Html::hidden( 'wpExistingTags', implode( ',', $tags ) ); $html .= '
'; return $html; } /** * Returns a * element. * @param string $label The text of a