Add new core tags
authorpetarpetkovic <ppetkovic@wikimedia.org>
Thu, 9 Nov 2017 11:13:16 +0000 (12:13 +0100)
committerRoan Kattouw <roan.kattouw@gmail.com>
Thu, 30 Nov 2017 18:29:58 +0000 (10:29 -0800)
Add tags to types of edits that get automatic edit summaries:
- Making a page a redirect
- Changing redirect target
- Changing an existing redirect into a non-redirect
- Blanking of the page
- Removing nearly all (more than 90%) content
- Rolling back an edit

Bug: T167656
Bug: T73236
Change-Id: Ie7f637fcec5ee659c1086e28e8ba21f470c45160

includes/DefaultSettings.php
includes/changetags/ChangeTags.php
includes/content/ContentHandler.php
includes/page/WikiPage.php
languages/i18n/en.json
languages/i18n/qqq.json
tests/phpunit/includes/changetags/ChangeTagsTest.php
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/content/WikitextContentHandlerTest.php
tests/phpunit/includes/page/WikiPageTest.php

index 3cd7ef1..b623a34 100644 (file)
@@ -6961,6 +6961,29 @@ $wgAllowCategorizedRecentChanges = false;
  */
 $wgUseTagFilter = true;
 
+/**
+ * List of core tags to enable. Available tags are:
+ * - 'mw-contentmodelchange': Edit changes content model of a page
+ * - 'mw-new-redirect': Edit makes new redirect page (new page or by changing content page)
+ * - 'mw-removed-redirect': Edit changes an existing redirect into a non-redirect
+ * - 'mw-changed-redirect-target': Edit changes redirect target
+ * - 'mw-blank': Edit completely blanks the page
+ * - 'mw-replace': Edit removes more than 90% of the content
+ * - 'mw-rollback': Edit is a rollback, made through the rollback link or rollback API
+ *
+ * @var array
+ * @since 1.31
+ */
+$wgSoftwareTags = [
+       'mw-contentmodelchange' => true,
+       'mw-new-redirect' => true,
+       'mw-removed-redirect' => true,
+       'mw-changed-redirect-target' => true,
+       'mw-blank' => true,
+       'mw-replace' => true,
+       'mw-rollback' => true
+];
+
 /**
  * If set to an integer, pages that are watched by this many users or more
  * will not require the unwatchedpages permission to view the number of
index fa98124..b4a8ca8 100644 (file)
@@ -32,10 +32,44 @@ class ChangeTags {
         */
        const MAX_DELETE_USES = 5000;
 
+       private static $definedSoftwareTags = [
+               'mw-contentmodelchange',
+               'mw-new-redirect',
+               'mw-removed-redirect',
+               'mw-changed-redirect-target',
+               'mw-blank',
+               'mw-replace',
+               'mw-rollback'
+       ];
+
        /**
-        * @var string[]
+        * Loads defined core tags, checks for invalid types (if not array),
+        * and filters for supported and enabled (if $all is false) tags only.
+        *
+        * @param bool $all If true, return all valid defined tags. Otherwise, return only enabled ones.
+        * @return array Array of all defined/enabled tags.
         */
-       private static $coreTags = [ 'mw-contentmodelchange' ];
+       public static function getSoftwareTags( $all = false ) {
+               global $wgSoftwareTags;
+               $softwareTags = [];
+
+               if ( !is_array( $wgSoftwareTags ) ) {
+                       wfWarn( 'wgSoftwareTags should be associative array of enabled tags.
+                       Please refer to documentation for the list of tags you can enable' );
+                       return $softwareTags;
+               }
+
+               $availableSoftwareTags = !$all ?
+                       array_keys( array_filter( $wgSoftwareTags ) ) :
+                       array_keys( $wgSoftwareTags );
+
+               $softwareTags = array_intersect(
+                       $availableSoftwareTags,
+                       self::$definedSoftwareTags
+               );
+
+               return $softwareTags;
+       }
 
        /**
         * Creates HTML for the given tags
@@ -1210,7 +1244,7 @@ class ChangeTags {
         */
        public static function listSoftwareActivatedTags() {
                // core active tags
-               $tags = self::$coreTags;
+               $tags = self::getSoftwareTags();
                if ( !Hooks::isRegistered( 'ChangeTagsListActive' ) ) {
                        return $tags;
                }
@@ -1301,7 +1335,7 @@ class ChangeTags {
         */
        public static function listSoftwareDefinedTags() {
                // core defined tags
-               $tags = self::$coreTags;
+               $tags = self::getSoftwareTags( true );
                if ( !Hooks::isRegistered( 'ListDefinedTags' ) ) {
                        return $tags;
                }
index 0509e29..a7b97a5 100644 (file)
@@ -754,82 +754,190 @@ abstract class ContentHandler {
                return false;
        }
 
+       /**
+        * Return type of change if one exists for the given edit.
+        *
+        * @since 1.31
+        *
+        * @param Content|null $oldContent The previous text of the page.
+        * @param Content|null $newContent The submitted text of the page.
+        * @param int $flags Bit mask: a bit mask of flags submitted for the edit.
+        *
+        * @return string|null String key representing type of change, or null.
+        */
+       private function getChangeType(
+               Content $oldContent = null,
+               Content $newContent = null,
+               $flags = 0
+       ) {
+               $oldTarget = $oldContent !== null ? $oldContent->getRedirectTarget() : null;
+               $newTarget = $newContent !== null ? $newContent->getRedirectTarget() : null;
+
+               // We check for the type of change in the given edit, and return string key accordingly
+
+               // Blanking of a page
+               if ( $oldContent && $oldContent->getSize() > 0 &&
+                       $newContent && $newContent->getSize() === 0
+               ) {
+                       return 'blank';
+               }
+
+               // Redirects
+               if ( $newTarget ) {
+                       if ( !$oldTarget ) {
+                               // New redirect page (by creating new page or by changing content page)
+                               return 'new-redirect';
+                       } elseif ( !$newTarget->equals( $oldTarget ) ||
+                               $oldTarget->getFragment() !== $newTarget->getFragment()
+                       ) {
+                               // Redirect target changed
+                               return 'changed-redirect-target';
+                       }
+               } elseif ( $oldTarget ) {
+                       // Changing an existing redirect into a non-redirect
+                       return 'removed-redirect';
+               }
+
+               // New page created
+               if ( $flags & EDIT_NEW && $newContent && $newContent->getSize() > 0 ) {
+                       return 'newpage';
+               }
+
+               // New blank page
+               if ( $flags & EDIT_NEW && $newContent && $newContent->getSize() === 0 ) {
+                       return 'newblank';
+               }
+
+               // Removing more than 90% of the page
+               if ( $oldContent && $newContent && $oldContent->getSize() > 10 * $newContent->getSize() ) {
+                       return 'replace';
+               }
+
+               // Content model changed
+               if ( $oldContent && $newContent && $oldContent->getModel() !== $newContent->getModel() ) {
+                       return 'contentmodelchange';
+               }
+
+               return null;
+       }
+
        /**
         * Return an applicable auto-summary if one exists for the given edit.
         *
         * @since 1.21
         *
-        * @param Content $oldContent The previous text of the page.
-        * @param Content $newContent The submitted text of the page.
+        * @param Content|null $oldContent The previous text of the page.
+        * @param Content|null $newContent The submitted text of the page.
         * @param int $flags Bit mask: a bit mask of flags submitted for the edit.
         *
         * @return string An appropriate auto-summary, or an empty string.
         */
-       public function getAutosummary( Content $oldContent = null, Content $newContent = null,
-               $flags ) {
-               // Decide what kind of auto-summary is needed.
-
-               // Redirect auto-summaries
-
-               /**
-                * @var $ot Title
-                * @var $rt Title
-                */
+       public function getAutosummary(
+               Content $oldContent = null,
+               Content $newContent = null,
+               $flags = 0
+       ) {
+               $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
 
-               $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null;
-               $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null;
+               // There's no applicable auto-summary for our case, so our auto-summary is empty.
+               if ( !$changeType ) {
+                       return '';
+               }
 
-               if ( is_object( $rt ) ) {
-                       if ( !is_object( $ot )
-                               || !$rt->equals( $ot )
-                               || $ot->getFragment() != $rt->getFragment()
-                       ) {
+               // Decide what kind of auto-summary is needed.
+               switch ( $changeType ) {
+                       case 'new-redirect':
+                               $newTarget = $newContent->getRedirectTarget();
                                $truncatedtext = $newContent->getTextForSummary(
                                        250
                                        - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
-                                       - strlen( $rt->getFullText() ) );
+                                       - strlen( $newTarget->getFullText() )
+                               );
 
-                               return wfMessage( 'autoredircomment', $rt->getFullText() )
+                               return wfMessage( 'autoredircomment', $newTarget->getFullText() )
                                        ->rawParams( $truncatedtext )->inContentLanguage()->text();
-                       }
-               }
+                       case 'changed-redirect-target':
+                               $oldTarget = $oldContent->getRedirectTarget();
+                               $newTarget = $newContent->getRedirectTarget();
 
-               // New page auto-summaries
-               if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) {
-                       // If they're making a new article, give its text, truncated, in
-                       // the summary.
+                               $truncatedtext = $newContent->getTextForSummary(
+                                       250
+                                       - strlen( wfMessage( 'autosumm-changed-redirect-target' )
+                                               ->inContentLanguage()->text() )
+                                       - strlen( $oldTarget->getFullText() )
+                                       - strlen( $newTarget->getFullText() )
+                               );
+
+                               return wfMessage( 'autosumm-changed-redirect-target',
+                                               $oldTarget->getFullText(),
+                                               $newTarget->getFullText() )
+                                       ->rawParams( $truncatedtext )->inContentLanguage()->text();
+                       case 'removed-redirect':
+                               $oldTarget = $oldContent->getRedirectTarget();
+                               $truncatedtext = $newContent->getTextForSummary(
+                                       250
+                                       - strlen( wfMessage( 'autosumm-removed-redirect' )
+                                               ->inContentLanguage()->text() )
+                                       - strlen( $oldTarget->getFullText() ) );
 
-                       $truncatedtext = $newContent->getTextForSummary(
-                               200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
+                               return wfMessage( 'autosumm-removed-redirect', $oldTarget->getFullText() )
+                                       ->rawParams( $truncatedtext )->inContentLanguage()->text();
+                       case 'newpage':
+                               // If they're making a new article, give its text, truncated, in the summary.
+                               $truncatedtext = $newContent->getTextForSummary(
+                                       200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
 
-                       return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
-                               ->inContentLanguage()->text();
+                               return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
+                                       ->inContentLanguage()->text();
+                       case 'blank':
+                               return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
+                       case 'replace':
+                               $truncatedtext = $newContent->getTextForSummary(
+                                       200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
+
+                               return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
+                                       ->inContentLanguage()->text();
+                       case 'newblank':
+                               return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
+                       default:
+                               return '';
                }
+       }
 
-               // Blanking auto-summaries
-               if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) {
-                       return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
-               } elseif ( !empty( $oldContent )
-                       && $oldContent->getSize() > 10 * $newContent->getSize()
-                       && $newContent->getSize() < 500
-               ) {
-                       // Removing more than 90% of the article
-
-                       $truncatedtext = $newContent->getTextForSummary(
-                               200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
+       /**
+        * Return an applicable tag if one exists for the given edit or return null.
+        *
+        * @since 1.31
+        *
+        * @param Content|null $oldContent The previous text of the page.
+        * @param Content|null $newContent The submitted text of the page.
+        * @param int $flags Bit mask: a bit mask of flags submitted for the edit.
+        *
+        * @return string|null An appropriate tag, or null.
+        */
+       public function getChangeTag(
+               Content $oldContent = null,
+               Content $newContent = null,
+               $flags = 0
+       ) {
+               $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
 
-                       return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
-                               ->inContentLanguage()->text();
+               // There's no applicable tag for this change.
+               if ( !$changeType ) {
+                       return null;
                }
 
-               // New blank article auto-summary
-               if ( $flags & EDIT_NEW && $newContent->isEmpty() ) {
-                       return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
+               // Core tags use the same keys as ones returned from $this->getChangeType()
+               // but prefixed with pseudo namespace 'mw-', so we add the prefix before checking
+               // if this type of change should be tagged
+               $tag = 'mw-' . $changeType;
+
+               // Not all change types are tagged, so we check against the list of defined tags.
+               if ( in_array( $tag, ChangeTags::getSoftwareTags() ) ) {
+                       return $tag;
                }
 
-               // If we reach this point, there's no applicable auto-summary for our
-               // case, so our auto-summary is empty.
-               return '';
+               return null;
        }
 
        /**
index 8b34928..9df3d8c 100644 (file)
@@ -1617,13 +1617,15 @@ class WikiPage implements Page, IDBAccessObject {
                $old_revision = $this->getRevision(); // current revision
                $old_content = $this->getContent( Revision::RAW ); // current revision's content
 
-               if ( $old_content && $old_content->getModel() !== $content->getModel() ) {
-                       $tags[] = 'mw-contentmodelchange';
+               $handler = $content->getContentHandler();
+               $tag = $handler->getChangeTag( $old_content, $content, $flags );
+               // If there is no applicable tag, null is returned, so we need to check
+               if ( $tag ) {
+                       $tags[] = $tag;
                }
 
-               // Provide autosummaries if one is not provided and autosummaries are enabled
+               // Provide autosummaries if summary is not provided and autosummaries are enabled
                if ( $wgUseAutomaticEditSummaries && ( $flags & EDIT_AUTOSUMMARY ) && $summary == '' ) {
-                       $handler = $content->getContentHandler();
                        $summary = $handler->getAutosummary( $old_content, $content, $flags );
                }
 
@@ -3211,6 +3213,10 @@ class WikiPage implements Page, IDBAccessObject {
                $targetContent = $target->getContent();
                $changingContentModel = $targetContent->getModel() !== $current->getContentModel();
 
+               if ( in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
+                       $tags[] = 'mw-rollback';
+               }
+
                // Actually store the edit
                $status = $this->doEditContent(
                        $targetContent,
@@ -3287,7 +3293,8 @@ class WikiPage implements Page, IDBAccessObject {
                        'summary' => $summary,
                        'current' => $current,
                        'target' => $target,
-                       'newid' => $revId
+                       'newid' => $revId,
+                       'tags' => $tags
                ];
 
                return [];
index 52ac447..3eb5a88 100644 (file)
        "autosumm-blank": "Blanked the page",
        "autosumm-replace": "Replaced content with \"$1\"",
        "autoredircomment": "Redirected page to [[$1]]",
+       "autosumm-removed-redirect": "Removed redirect to [[$1]]",
+       "autosumm-changed-redirect-target": "Changed redirect target from [[$1]] to [[$2]]",
        "autosumm-new": "Created page with \"$1\"",
        "autosumm-newblank": "Created blank page",
        "autoblock_whitelist": "",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2)",
        "tag-mw-contentmodelchange": "content model change",
        "tag-mw-contentmodelchange-description": "Edits that [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel change the content model] of a page",
+       "tag-mw-new-redirect": "New redirect",
+       "tag-mw-new-redirect-description": "Edits that create a new redirect or change a page to a redirect",
+       "tag-mw-removed-redirect": "Removed redirect",
+       "tag-mw-removed-redirect-description": "Edits that change an existing redirect to a non-redirect",
+       "tag-mw-changed-redirect-target": "Redirect target changed",
+       "tag-mw-changed-redirect-target-description": "Edits that change the target of a redirect",
+       "tag-mw-blank": "Blanking",
+       "tag-mw-blank-description": "Edits that blank a page",
+       "tag-mw-replace": "Replaced",
+       "tag-mw-replace-description": "Edits that remove more than 90% of the content of a page",
+       "tag-mw-rollback": "Rollback",
+       "tag-mw-rollback-description": "Edits that roll back previous edits using the rollback link",
        "tags-title": "Tags",
        "tags-intro": "This page lists the tags that the software may mark an edit with, and their meaning.",
        "tags-tag": "Tag name",
index e5d4984..9f365ee 100644 (file)
        "autosumm-blank": "The auto summary when blanking the whole page. This is not the same as deleting the page.",
        "autosumm-replace": "The auto summary when a user removes a lot of characters in the page.\n\nParameters:\n* $1 - truncated text",
        "autoredircomment": "The auto summary when making a redirect. Parameters:\n* $1 - the page where it redirects to\n* $2 - (Optional) the first X number of characters of the redirect ($2 is usually only used when end users customize the message)\n{{Identical|Redirect}}",
+       "autosumm-removed-redirect": "The auto summary when making a redirect. Parameters:\n* $1 - the page where it redirects to\n* $2 - (Optional) the first X number of characters of the redirect ($2 is usually only used when end users customize the message)\n{{Identical|Redirect}}",
+       "autosumm-changed-redirect-target": "The auto summary when making a redirect. Parameters:\n* $1 - the page where it redirects to\n* $2 - (Optional) the first X number of characters of the redirect ($2 is usually only used when end users customize the message)\n{{Identical|Redirect}}",
        "autosumm-new": "The auto summary when creating a new page. $1 are the first X number of characters of the new page.",
        "autosumm-newblank": "The automatic edit summary when creating a blank page. This is not the same as blanking a page.",
        "autoblock_whitelist": "{{notranslate}}",
        "tag-list-wrapper": "Wrapper for the list of tags shown on recent changes, watchlists, history pages and diffs.\n\nParameters:\n* $1 - number of distinct tags for given edit\n* $2 - comma-separated list of tags for given edit",
        "tag-mw-contentmodelchange": "Change tag for edits that change the content model of a page",
        "tag-mw-contentmodelchange-description": "Description for \"content model change\" change tag",
+       "tag-mw-new-redirect": "Change tag for edits that make the page a redirect (either redirect creation or editing an existing page)",
+       "tag-mw-new-redirect-description": "Description for \"New redirect\" change tag",
+       "tag-mw-removed-redirect": "Change tag for edits that change an existing redirect into a non-redirect",
+       "tag-mw-removed-redirect-description": "Description for \"Removed redirect\" change tag",
+       "tag-mw-changed-redirect-target": "Change tag for edits that change the target of a redirect",
+       "tag-mw-changed-redirect-target-description": "Description for \"Redirect target changed\" change tag",
+       "tag-mw-blank": "Change tag for edits that blank a page with existing content",
+       "tag-mw-blank-description": "Description for \"blank\" change tag",
+       "tag-mw-replace": "Change tag for edits removing more than 90% of the content of a page",
+       "tag-mw-replace-description": "Description for \"replace\" change tag",
+       "tag-mw-rollback": "Change tag for rolling back an edit",
+       "tag-mw-rollback-description": "Description for \"rollback\" change tag",
        "tags-title": "The title of [[Special:Tags]].\n{{Identical|Tag}}",
        "tags-intro": "Explanation on top of [[Special:Tags]]. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
        "tags-tag": "Caption of a column in [[Special:Tags]]. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
index 723d685..63e0ec2 100644 (file)
@@ -5,7 +5,7 @@
  */
 class ChangeTagsTest extends MediaWikiTestCase {
 
-       // TODO only modifyDisplayQuery is tested, nothing else is
+       // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
 
        /** @dataProvider provideModifyDisplayQuery */
        public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
@@ -244,4 +244,66 @@ class ChangeTagsTest extends MediaWikiTestCase {
                ];
        }
 
+       public static function dataGetSoftwareTags() {
+               return [
+                       [
+                               [
+                                       'mw-contentModelChange' => true,
+                                       'mw-redirect' => true,
+                                       'mw-rollback' => true,
+                                       'mw-blank' => true,
+                                       'mw-replace' => true
+                               ],
+                               [
+                                       'mw-rollback',
+                                       'mw-replace',
+                                       'mw-blank'
+                               ]
+                       ],
+
+                       [
+                               [
+                                       'mw-contentmodelchanged' => true,
+                                       'mw-replace' => true,
+                                       'mw-new-redirects' => true,
+                                       'mw-changed-redirect-target' => true,
+                                       'mw-rolback' => true,
+                                       'mw-blanking' => false
+                               ],
+                               [
+                                       'mw-replace',
+                                       'mw-changed-redirect-target'
+                               ]
+                       ],
+
+                       [
+                               [
+                                       null,
+                                       false,
+                                       'Lorem ipsum',
+                                       'mw-translation'
+                               ],
+                               []
+                       ],
+
+                       [
+                               [],
+                               []
+                       ]
+               ];
+       }
+
+       /**
+        * @dataProvider dataGetSoftwareTags
+        * @covers ChangeTags::getSoftwareTags
+        */
+       public function testGetSoftwareTags( $softwareTags, $expected ) {
+               $this->setMwGlobals( 'wgSoftwareTags', $softwareTags );
+
+               $actual = ChangeTags::getSoftwareTags();
+               // Order of tags in arrays is not important
+               sort( $expected );
+               sort( $actual );
+               $this->assertEquals( $expected, $actual );
+       }
 }
index 2422e79..1bd2eb0 100644 (file)
@@ -342,15 +342,32 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
                $title = Title::newFromText( 'Help:Test' );
                // Create a new content object with no content
-               $newContent = ContentHandler::makeContent( '', $title, null, null, CONTENT_MODEL_WIKITEXT );
+               $newContent = ContentHandler::makeContent( '', $title, CONTENT_MODEL_WIKITEXT, null );
                // first check, if we become a blank page created summary with the right bitmask
                $autoSummary = $content->getAutosummary( null, $newContent, 97 );
-               $this->assertEquals( $autoSummary, 'Created blank page' );
+               $this->assertEquals( $autoSummary,
+                       wfMessage( 'autosumm-newblank' )->inContentLanguage()->text() );
                // now check, what we become with another bitmask
                $autoSummary = $content->getAutosummary( null, $newContent, 92 );
                $this->assertEquals( $autoSummary, '' );
        }
 
+       /**
+        * Test software tag that is added when content model of the page changes
+        * @covers ContentHandler::getChangeTag
+        */
+       public function testGetChangeTag() {
+               $this->setMwGlobals( 'wgSoftwareTags', [ 'mw-contentmodelchange' => true ] );
+               $wikitextContentHandler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
+               // Create old content object with javascript content model
+               $oldContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_JAVASCRIPT, null );
+               // Create new content object with wikitext content model
+               $newContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_WIKITEXT, null );
+               // Get the tag for this edit
+               $tag = $wikitextContentHandler->getChangeTag( $oldContent, $newContent, EDIT_UPDATE );
+               $this->assertSame( $tag, 'mw-contentmodelchange' );
+       }
+
        /*
        public function testSupportsSections() {
                $this->markTestIncomplete( "not yet implemented" );
index 290b11a..77cfb92 100644 (file)
@@ -185,6 +185,13 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
                                '/^Created page .*Hello/'
                        ],
 
+                       [
+                               null,
+                               '',
+                               EDIT_NEW,
+                               '/^Created blank page$/'
+                       ],
+
                        [
                                'Hello there, world!',
                                '',
@@ -227,6 +234,104 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
                );
        }
 
+       public static function dataGetChangeTag() {
+               return [
+                       [
+                               null,
+                               '#REDIRECT [[Foo]]',
+                               0,
+                               'mw-new-redirect'
+                       ],
+
+                       [
+                               'Lorem ipsum dolor',
+                               '#REDIRECT [[Foo]]',
+                               0,
+                               'mw-new-redirect'
+                       ],
+
+                       [
+                               '#REDIRECT [[Foo]]',
+                               'Lorem ipsum dolor',
+                               0,
+                               'mw-removed-redirect'
+                       ],
+
+                       [
+                               '#REDIRECT [[Foo]]',
+                               '#REDIRECT [[Bar]]',
+                               0,
+                               'mw-changed-redirect-target'
+                       ],
+
+                       [
+                               null,
+                               'Lorem ipsum dolor',
+                               EDIT_NEW,
+                               null // mw-newpage is not defined as a tag
+                       ],
+
+                       [
+                               null,
+                               '',
+                               EDIT_NEW,
+                               null // mw-newblank is not defined as a tag
+                       ],
+
+                       [
+                               'Lorem ipsum dolor',
+                               '',
+                               0,
+                               'mw-blank'
+                       ],
+
+                       [
+                               'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+                               eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+                               voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+                               clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+                               'Ipsum',
+                               0,
+                               'mw-replace'
+                       ],
+
+                       [
+                               'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+                               eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+                               voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+                               clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+                               'Duis purus odio, rhoncus et finibus dapibus, facilisis ac urna. Pellentesque
+                               arcu, tristique nec tempus nec, suscipit vel arcu. Sed non dolor nec ligula
+                               congue tempor. Quisque pellentesque finibus orci a molestie. Nam maximus, purus
+                               euismod finibus mollis, dui ante malesuada felis, dignissim rutrum diam sapien.',
+                               0,
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider dataGetChangeTag
+        * @covers WikitextContentHandler::getChangeTag
+        */
+       public function testGetChangeTag( $old, $new, $flags, $expected ) {
+               $this->setMwGlobals( 'wgSoftwareTags', [
+                       'mw-new-redirect' => true,
+                       'mw-removed-redirect' => true,
+                       'mw-changed-redirect-target' => true,
+                       'mw-newpage' => true,
+                       'mw-newblank' => true,
+                       'mw-blank' => true,
+                       'mw-replace' => true,
+               ] );
+               $oldContent = is_null( $old ) ? null : new WikitextContent( $old );
+               $newContent = is_null( $new ) ? null : new WikitextContent( $new );
+
+               $tag = $this->handler->getChangeTag( $oldContent, $newContent, $flags );
+
+               $this->assertSame( $expected, $tag );
+       }
+
        /**
         * @todo Text case requires database, should be done by a test class in the Database group
         */
index 386f142..d36f8b5 100644 (file)
@@ -993,6 +993,60 @@ more stuff
                $this->assertEquals( "one", $page->getContent()->getNativeData() );
        }
 
+       /**
+        * Tests tagging for edits that do rollback action
+        * @covers WikiPage::doRollback
+        */
+       public function testDoRollbackTagging() {
+               if ( !in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
+                       $this->markTestSkipped( 'Rollback tag deactivated, skipped the test.' );
+               }
+
+               $admin = new User();
+               $admin->setName( 'Administrator' );
+               $admin->addToDatabase();
+
+               $text = 'First line';
+               $page = $this->newPage( 'WikiPageTest_testDoRollbackTagging' );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+                       'Added first line',
+                       EDIT_NEW,
+                       false,
+                       $admin
+               );
+
+               $secondUser = new User();
+               $secondUser->setName( '92.65.217.32' );
+               $text .= '\n\nSecond line';
+               $page = new WikiPage( $page->getTitle() );
+               $page->doEditContent(
+                       ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
+                       'Adding second line',
+                       0,
+                       false,
+                       $secondUser
+               );
+
+               // Now, try the rollback
+               $admin->addGroup( 'sysop' ); // Make the test user a sysop
+               $token = $admin->getEditToken( 'rollback' );
+               $errors = $page->doRollback(
+                       $secondUser->getName(),
+                       'testing rollback',
+                       $token,
+                       false,
+                       $resultDetails,
+                       $admin
+               );
+
+               // If doRollback completed without errors
+               if ( $errors === [] ) {
+                       $tags = $resultDetails[ 'tags' ];
+                       $this->assertContains( 'mw-rollback', $tags );
+               }
+       }
+
        public static function provideGetAutoDeleteReason() {
                return [
                        [