summaryrefslogtreecommitdiff
path: root/includes/specials/SpecialTags.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/specials/SpecialTags.php')
-rw-r--r--includes/specials/SpecialTags.php336
1 files changed, 327 insertions, 9 deletions
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index b7627285..0b8147e1 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -31,6 +31,10 @@ class SpecialTags extends SpecialPage {
* @var array List of defined tags
*/
public $definedTags;
+ /**
+ * @var array List of active tags
+ */
+ public $activeTags;
function __construct() {
parent::__construct( 'Tags' );
@@ -40,33 +44,114 @@ class SpecialTags extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
+ $request = $this->getRequest();
+ switch ( $par ) {
+ case 'delete':
+ $this->showDeleteTagForm( $request->getVal( 'tag' ) );
+ break;
+ case 'activate':
+ $this->showActivateDeactivateForm( $request->getVal( 'tag' ), true );
+ break;
+ case 'deactivate':
+ $this->showActivateDeactivateForm( $request->getVal( 'tag' ), false );
+ break;
+ case 'create':
+ // fall through, thanks to HTMLForm's logic
+ default:
+ $this->showTagList();
+ break;
+ }
+ }
+
+ function showTagList() {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'tags-title' ) );
$out->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
+ $user = $this->getUser();
+
+ // Show form to create a tag
+ if ( $user->isAllowed( 'managechangetags' ) ) {
+ $fields = array(
+ 'Tag' => array(
+ 'type' => 'text',
+ 'label' => $this->msg( 'tags-create-tag-name' )->plain(),
+ 'required' => true,
+ ),
+ 'Reason' => array(
+ 'type' => 'text',
+ 'label' => $this->msg( 'tags-create-reason' )->plain(),
+ 'size' => 50,
+ ),
+ 'IgnoreWarnings' => array(
+ 'type' => 'hidden',
+ ),
+ );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setAction( $this->getPageTitle( 'create' )->getLocalURL() );
+ $form->setWrapperLegendMsg( 'tags-create-heading' );
+ $form->setHeaderText( $this->msg( 'tags-create-explanation' )->plain() );
+ $form->setSubmitCallback( array( $this, 'processCreateTagForm' ) );
+ $form->setSubmitTextMsg( 'tags-create-submit' );
+ $form->show();
+
+ // If processCreateTagForm generated a redirect, there's no point
+ // continuing with this, as the user is just going to end up getting sent
+ // somewhere else. Additionally, if we keep going here, we end up
+ // populating the memcache of tag data (see ChangeTags::listDefinedTags)
+ // with out-of-date data from the slave, because the slave hasn't caught
+ // up to the fact that a new tag has been created as part of an implicit,
+ // as yet uncommitted transaction on master.
+ if ( $out->getRedirect() !== '' ) {
+ return;
+ }
+ }
+
+ // Whether to show the "Actions" column in the tag list
+ // If any actions added in the future require other user rights, add those
+ // rights here
+ $showActions = $user->isAllowed( 'managechangetags' );
+
// Write the headers
+ $tagUsageStatistics = ChangeTags::tagUsageStatistics();
+
+ // Show header only if there exists atleast one tag
+ if ( !$tagUsageStatistics ) {
+ return;
+ }
$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, $this->msg( 'tags-tag' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-source-header' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() )
+ Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() ) .
+ ( $showActions ?
+ Xml::tags( 'th', array( 'class' => 'unsortable' ),
+ $this->msg( 'tags-actions-header' )->parse() ) :
+ '' )
);
// Used in #doTagRow()
- $this->definedTags = array_fill_keys( ChangeTags::listDefinedTags(), true );
+ $this->explicitlyDefinedTags = array_fill_keys(
+ ChangeTags::listExplicitlyDefinedTags(), true );
+ $this->extensionDefinedTags = array_fill_keys(
+ ChangeTags::listExtensionDefinedTags(), true );
+ $this->extensionActivatedTags = array_fill_keys(
+ ChangeTags::listExtensionActivatedTags(), true );
- foreach ( ChangeTags::tagUsageStatistics() as $tag => $hitcount ) {
- $html .= $this->doTagRow( $tag, $hitcount );
+ foreach ( $tagUsageStatistics as $tag => $hitcount ) {
+ $html .= $this->doTagRow( $tag, $hitcount, $showActions );
}
$out->addHTML( Xml::tags(
'table',
- array( 'class' => 'wikitable sortable mw-tags-table' ),
+ array( 'class' => 'mw-datatable sortable mw-tags-table' ),
$html
) );
}
- function doTagRow( $tag, $hitcount ) {
+ function doTagRow( $tag, $hitcount, $showActions ) {
$user = $this->getUser();
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) );
@@ -94,9 +179,23 @@ class SpecialTags extends SpecialPage {
}
$newRow .= Xml::tags( 'td', null, $desc );
- $active = isset( $this->definedTags[$tag] ) ? 'tags-active-yes' : 'tags-active-no';
- $active = $this->msg( $active )->escaped();
- $newRow .= Xml::tags( 'td', null, $active );
+ $sourceMsgs = array();
+ $isExtension = isset( $this->extensionDefinedTags[$tag] );
+ $isExplicit = isset( $this->explicitlyDefinedTags[$tag] );
+ if ( $isExtension ) {
+ $sourceMsgs[] = $this->msg( 'tags-source-extension' )->escaped();
+ }
+ if ( $isExplicit ) {
+ $sourceMsgs[] = $this->msg( 'tags-source-manual' )->escaped();
+ }
+ if ( !$sourceMsgs ) {
+ $sourceMsgs[] = $this->msg( 'tags-source-none' )->escaped();
+ }
+ $newRow .= Xml::tags( 'td', null, implode( Xml::element( 'br' ), $sourceMsgs ) );
+
+ $isActive = $isExplicit || isset( $this->extensionActivatedTags[$tag] );
+ $activeMsg = ( $isActive ? 'tags-active-yes' : 'tags-active-no' );
+ $newRow .= Xml::tags( 'td', null, $this->msg( $activeMsg )->escaped() );
$hitcountLabel = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped();
$hitcountLink = Linker::link(
@@ -109,9 +208,228 @@ class SpecialTags extends SpecialPage {
// add raw $hitcount for sorting, because tags-hitcount contains numbers and letters
$newRow .= Xml::tags( 'td', array( 'data-sort-value' => $hitcount ), $hitcountLink );
+ // actions
+ $actionLinks = array();
+ if ( $showActions ) {
+ // delete
+ if ( ChangeTags::canDeleteTag( $tag, $user )->isOK() ) {
+ $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'delete' ),
+ $this->msg( 'tags-delete' )->escaped(),
+ array(),
+ array( 'tag' => $tag ) );
+ }
+
+ // activate
+ if ( ChangeTags::canActivateTag( $tag, $user )->isOK() ) {
+ $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'activate' ),
+ $this->msg( 'tags-activate' )->escaped(),
+ array(),
+ array( 'tag' => $tag ) );
+ }
+
+ // deactivate
+ if ( ChangeTags::canDeactivateTag( $tag, $user )->isOK() ) {
+ $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'deactivate' ),
+ $this->msg( 'tags-deactivate' )->escaped(),
+ array(),
+ array( 'tag' => $tag ) );
+ }
+
+ $newRow .= Xml::tags( 'td', null, $this->getLanguage()->pipeList( $actionLinks ) );
+ }
+
return Xml::tags( 'tr', null, $newRow ) . "\n";
}
+ public function processCreateTagForm( array $data, HTMLForm $form ) {
+ $context = $form->getContext();
+ $out = $context->getOutput();
+
+ $tag = trim( strval( $data['Tag'] ) );
+ $ignoreWarnings = isset( $data['IgnoreWarnings'] ) && $data['IgnoreWarnings'] === '1';
+ $status = ChangeTags::createTagWithChecks( $tag, $data['Reason'],
+ $context->getUser(), $ignoreWarnings );
+
+ if ( $status->isGood() ) {
+ $out->redirect( $this->getPageTitle()->getLocalURL() );
+ return true;
+ } elseif ( $status->isOK() ) {
+ // we have some warnings, so we show a confirmation form
+ $fields = array(
+ 'Tag' => array(
+ 'type' => 'hidden',
+ 'default' => $data['Tag'],
+ ),
+ 'Reason' => array(
+ 'type' => 'hidden',
+ 'default' => $data['Reason'],
+ ),
+ 'IgnoreWarnings' => array(
+ 'type' => 'hidden',
+ 'default' => '1',
+ ),
+ );
+
+ // fool HTMLForm into thinking the form hasn't been submitted yet. Otherwise
+ // we get into an infinite loop!
+ $context->getRequest()->unsetVal( 'wpEditToken' );
+
+ $headerText = $this->msg( 'tags-create-warnings-above', $tag,
+ count( $status->getWarningsArray() ) )->parseAsBlock() .
+ $out->parse( $status->getWikitext() ) .
+ $this->msg( 'tags-create-warnings-below' )->parseAsBlock();
+
+ $subform = new HTMLForm( $fields, $this->getContext() );
+ $subform->setAction( $this->getPageTitle( 'create' )->getLocalURL() );
+ $subform->setWrapperLegendMsg( 'tags-create-heading' );
+ $subform->setHeaderText( $headerText );
+ $subform->setSubmitCallback( array( $this, 'processCreateTagForm' ) );
+ $subform->setSubmitTextMsg( 'htmlform-yes' );
+ $subform->show();
+
+ $out->addBacklinkSubtitle( $this->getPageTitle() );
+ return true;
+ } else {
+ $out->addWikiText( "<div class=\"error\">\n" . $status->getWikitext() .
+ "\n</div>" );
+ return false;
+ }
+ }
+
+ protected function showDeleteTagForm( $tag ) {
+ $user = $this->getUser();
+ if ( !$user->isAllowed( 'managechangetags' ) ) {
+ throw new PermissionsError( 'managechangetags' );
+ }
+
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'tags-delete-title' ) );
+ $out->addBacklinkSubtitle( $this->getPageTitle() );
+
+ // is the tag actually able to be deleted?
+ $canDeleteResult = ChangeTags::canDeleteTag( $tag, $user );
+ if ( !$canDeleteResult->isGood() ) {
+ $out->addWikiText( "<div class=\"error\">\n" . $canDeleteResult->getWikiText() .
+ "\n</div>" );
+ if ( !$canDeleteResult->isOK() ) {
+ return;
+ }
+ }
+
+ $preText = $this->msg( 'tags-delete-explanation-initial', $tag )->parseAsBlock();
+ $tagUsage = ChangeTags::tagUsageStatistics();
+ if ( $tagUsage[$tag] > 0 ) {
+ $preText .= $this->msg( 'tags-delete-explanation-in-use', $tag,
+ $tagUsage[$tag] )->parseAsBlock();
+ }
+ $preText .= $this->msg( 'tags-delete-explanation-warning', $tag )->parseAsBlock();
+
+ // see if the tag is in use
+ $this->extensionActivatedTags = array_fill_keys(
+ ChangeTags::listExtensionActivatedTags(), true );
+ if ( isset( $this->extensionActivatedTags[$tag] ) ) {
+ $preText .= $this->msg( 'tags-delete-explanation-active', $tag )->parseAsBlock();
+ }
+
+ $fields = array();
+ $fields['Reason'] = array(
+ 'type' => 'text',
+ 'label' => $this->msg( 'tags-delete-reason' )->plain(),
+ 'size' => 50,
+ );
+ $fields['HiddenTag'] = array(
+ 'type' => 'hidden',
+ 'name' => 'tag',
+ 'default' => $tag,
+ 'required' => true,
+ );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setAction( $this->getPageTitle( 'delete' )->getLocalURL() );
+ $form->tagAction = 'delete'; // custom property on HTMLForm object
+ $form->setSubmitCallback( array( $this, 'processTagForm' ) );
+ $form->setSubmitTextMsg( 'tags-delete-submit' );
+ $form->setSubmitDestructive(); // nasty!
+ $form->addPreText( $preText );
+ $form->show();
+ }
+
+ protected function showActivateDeactivateForm( $tag, $activate ) {
+ $actionStr = $activate ? 'activate' : 'deactivate';
+
+ $user = $this->getUser();
+ if ( !$user->isAllowed( 'managechangetags' ) ) {
+ throw new PermissionsError( 'managechangetags' );
+ }
+
+ $out = $this->getOutput();
+ // tags-activate-title, tags-deactivate-title
+ $out->setPageTitle( $this->msg( "tags-$actionStr-title" ) );
+ $out->addBacklinkSubtitle( $this->getPageTitle() );
+
+ // is it possible to do this?
+ $func = $activate ? 'canActivateTag' : 'canDeactivateTag';
+ $result = ChangeTags::$func( $tag, $user );
+ if ( !$result->isGood() ) {
+ $out->wrapWikiMsg( "<div class=\"error\">\n$1" . $result->getWikiText() .
+ "\n</div>" );
+ if ( !$result->isOK() ) {
+ return;
+ }
+ }
+
+ // tags-activate-question, tags-deactivate-question
+ $preText = $this->msg( "tags-$actionStr-question", $tag )->parseAsBlock();
+
+ $fields = array();
+ // tags-activate-reason, tags-deactivate-reason
+ $fields['Reason'] = array(
+ 'type' => 'text',
+ 'label' => $this->msg( "tags-$actionStr-reason" )->plain(),
+ 'size' => 50,
+ );
+ $fields['HiddenTag'] = array(
+ 'type' => 'hidden',
+ 'name' => 'tag',
+ 'default' => $tag,
+ 'required' => true,
+ );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setAction( $this->getPageTitle( $actionStr )->getLocalURL() );
+ $form->tagAction = $actionStr;
+ $form->setSubmitCallback( array( $this, 'processTagForm' ) );
+ // tags-activate-submit, tags-deactivate-submit
+ $form->setSubmitTextMsg( "tags-$actionStr-submit" );
+ $form->addPreText( $preText );
+ $form->show();
+ }
+
+ public function processTagForm( array $data, HTMLForm $form ) {
+ $context = $form->getContext();
+ $out = $context->getOutput();
+
+ $tag = $data['HiddenTag'];
+ $status = call_user_func( array( 'ChangeTags', "{$form->tagAction}TagWithChecks" ),
+ $tag, $data['Reason'], $context->getUser(), true );
+
+ if ( $status->isGood() ) {
+ $out->redirect( $this->getPageTitle()->getLocalURL() );
+ return true;
+ } elseif ( $status->isOK() && $form->tagAction === 'delete' ) {
+ // deletion succeeded, but hooks raised a warning
+ $out->addWikiText( $this->msg( 'tags-delete-warnings-after-delete', $tag,
+ count( $status->getWarningsArray() ) )->text() . "\n" .
+ $status->getWikitext() );
+ $out->addReturnTo( $this->getPageTitle() );
+ return true;
+ } else {
+ $out->addWikiText( "<div class=\"error\">\n" . $status->getWikitext() .
+ "\n</div>" );
+ return false;
+ }
+ }
+
protected function getGroupName() {
return 'changes';
}