Add tags support to patrol, protect, unblock, and undelete
authorGeoffrey Mon <geofbot@gmail.com>
Mon, 25 Jan 2016 04:11:36 +0000 (23:11 -0500)
committerGeoffrey Mon <geofbot@gmail.com>
Mon, 29 Feb 2016 21:59:31 +0000 (16:59 -0500)
- Add 'tags' parameters to appropriate API modules
- Add tag-adding logic to appropriate functions that carry out
  relevant functions
- ManualLogEntry::{set,get}Tags to handle adding tags to log
  entries in a cleaner fashion
- Use ManualLogEntry::setTags in LocalFile::recordUpload2

Bug: T97720
Change-Id: I98c52da7985623bfdafda2dc2dae937b39b72419

14 files changed:
includes/api/ApiPatrol.php
includes/api/ApiProtect.php
includes/api/ApiUnblock.php
includes/api/ApiUndelete.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/changes/RecentChange.php
includes/changetags/ChangeTags.php
includes/filerepo/file/LocalFile.php
includes/logging/LogEntry.php
includes/logging/PatrolLog.php
includes/page/WikiPage.php
includes/specials/SpecialUnblock.php
includes/specials/SpecialUndelete.php

index 1230bab..6252882 100644 (file)
@@ -56,7 +56,18 @@ class ApiPatrol extends ApiBase {
                        }
                }
 
-               $retval = $rc->doMarkPatrolled( $this->getUser() );
+               $user = $this->getUser();
+               $tags = $params['tags'];
+
+               // Check if user can add tags
+               if ( !is_null( $tags ) ) {
+                       $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $user );
+                       if ( !$ableToTag->isOK() ) {
+                               $this->dieStatus( $ableToTag );
+                       }
+               }
+
+               $retval = $rc->doMarkPatrolled( $user, false, $tags );
 
                if ( $retval ) {
                        $this->dieUsageMsg( reset( $retval ) );
@@ -83,6 +94,10 @@ class ApiPatrol extends ApiBase {
                        'revid' => [
                                ApiBase::PARAM_TYPE => 'integer'
                        ],
+                       'tags' => [
+                               ApiBase::PARAM_TYPE => 'tags',
+                               ApiBase::PARAM_ISMULTI => true,
+                       ],
                ];
        }
 
index f1c0121..d289060 100644 (file)
@@ -42,6 +42,17 @@ class ApiProtect extends ApiBase {
                        $this->dieUsageMsg( reset( $errors ) );
                }
 
+               $user = $this->getUser();
+               $tags = $params['tags'];
+
+               // Check if user can add tags
+               if ( !is_null( $tags ) ) {
+                       $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $user );
+                       if ( !$ableToTag->isOK() ) {
+                               $this->dieStatus( $ableToTag );
+                       }
+               }
+
                $expiry = (array)$params['expiry'];
                if ( count( $expiry ) != count( $params['protections'] ) ) {
                        if ( count( $expiry ) == 1 ) {
@@ -108,7 +119,8 @@ class ApiProtect extends ApiBase {
                        $expiryarray,
                        $cascade,
                        $params['reason'],
-                       $this->getUser()
+                       $user,
+                       $tags
                );
 
                if ( !$status->isOK() ) {
@@ -153,6 +165,10 @@ class ApiProtect extends ApiBase {
                                ApiBase::PARAM_DFLT => 'infinite',
                        ],
                        'reason' => '',
+                       'tags' => [
+                               ApiBase::PARAM_TYPE => 'tags',
+                               ApiBase::PARAM_ISMULTI => true,
+                       ],
                        'cascade' => false,
                        'watch' => [
                                ApiBase::PARAM_DFLT => false,
index 5f268c5..ace41a4 100644 (file)
@@ -63,9 +63,18 @@ class ApiUnblock extends ApiBase {
                        }
                }
 
+               // Check if user can add tags
+               if ( !is_null( $params['tags'] ) ) {
+                       $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
+                       if ( !$ableToTag->isOK() ) {
+                               $this->dieStatus( $ableToTag );
+                       }
+               }
+
                $data = [
                        'Target' => is_null( $params['id'] ) ? $params['user'] : "#{$params['id']}",
-                       'Reason' => $params['reason']
+                       'Reason' => $params['reason'],
+                       'Tags' => $params['tags']
                ];
                $block = Block::newFromTarget( $data['Target'] );
                $retval = SpecialUnblock::processUnblock( $data, $this->getContext() );
@@ -96,6 +105,10 @@ class ApiUnblock extends ApiBase {
                        ],
                        'user' => null,
                        'reason' => '',
+                       'tags' => [
+                               ApiBase::PARAM_TYPE => 'tags',
+                               ApiBase::PARAM_ISMULTI => true,
+                       ],
                ];
        }
 
index f4fcb06..e24f2ce 100644 (file)
@@ -47,6 +47,14 @@ class ApiUndelete extends ApiBase {
                        $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
                }
 
+               // Check if user can add tags
+               if ( !is_null( $params['tags'] ) ) {
+                       $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
+                       if ( !$ableToTag->isOK() ) {
+                               $this->dieStatus( $ableToTag );
+                       }
+               }
+
                // Convert timestamps
                if ( !isset( $params['timestamps'] ) ) {
                        $params['timestamps'] = [];
@@ -64,7 +72,8 @@ class ApiUndelete extends ApiBase {
                        $params['reason'],
                        $params['fileids'],
                        false,
-                       $this->getUser()
+                       $user,
+                       $params['tags']
                );
                if ( !is_array( $retval ) ) {
                        $this->dieUsageMsg( 'cannotundelete' );
@@ -99,6 +108,10 @@ class ApiUndelete extends ApiBase {
                                ApiBase::PARAM_REQUIRED => true
                        ],
                        'reason' => '',
+                       'tags' => [
+                               ApiBase::PARAM_TYPE => 'tags',
+                               ApiBase::PARAM_ISMULTI => true,
+                       ],
                        'timestamps' => [
                                ApiBase::PARAM_TYPE => 'timestamp',
                                ApiBase::PARAM_ISMULTI => true,
index 6ea643a..2d1542e 100644 (file)
        "apihelp-patrol-description": "Patrol a page or revision.",
        "apihelp-patrol-param-rcid": "Recentchanges ID to patrol.",
        "apihelp-patrol-param-revid": "Revision ID to patrol.",
+       "apihelp-patrol-param-tags": "Change tags to apply to the entry in the patrol log.",
        "apihelp-patrol-example-rcid": "Patrol a recent change.",
        "apihelp-patrol-example-revid": "Patrol a revision.",
 
        "apihelp-protect-param-protections": "List of protection levels, formatted <kbd>action=level</kbd> (e.g. <kbd>edit=sysop</kbd>).\n\n<strong>Note:</strong> Any actions not listed will have restrictions removed.",
        "apihelp-protect-param-expiry": "Expiry timestamps. If only one timestamp is set, it'll be used for all protections. Use <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd>, or <kbd>never</kbd>, for a never-expiring protection.",
        "apihelp-protect-param-reason": "Reason for (un)protecting.",
+       "apihelp-protect-param-tags": "Change tags to apply to the entry in the protection log.",
        "apihelp-protect-param-cascade": "Enable cascading protection (i.e. protect transcluded templates and images used in this page). Ignored if none of the given protection levels support cascading.",
        "apihelp-protect-param-watch": "If set, add the page being (un)protected to the current user's watchlist.",
        "apihelp-protect-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
        "apihelp-unblock-param-id": "ID of the block to unblock (obtained through <kbd>list=blocks</kbd>). Cannot be used together with <var>$1user</var>.",
        "apihelp-unblock-param-user": "Username, IP address or IP range to unblock. Cannot be used together with <var>$1id</var>.",
        "apihelp-unblock-param-reason": "Reason for unblock.",
+       "apihelp-unblock-param-tags": "Change tags to apply to the entry in the block log.",
        "apihelp-unblock-example-id": "Unblock block ID #<kbd>105</kbd>.",
        "apihelp-unblock-example-user": "Unblock user <kbd>Bob</kbd> with reason <kbd>Sorry Bob</kbd>.",
 
        "apihelp-undelete-description": "Restore revisions of a deleted page.\n\nA list of deleted revisions (including timestamps) can be retrieved through [[Special:ApiHelp/query+deletedrevs|list=deletedrevs]], and a list of deleted file IDs can be retrieved through [[Special:ApiHelp/query+filearchive|list=filearchive]].",
        "apihelp-undelete-param-title": "Title of the page to restore.",
        "apihelp-undelete-param-reason": "Reason for restoring.",
+       "apihelp-undelete-param-tags": "Change tags to apply to the entry in the deletion log.",
        "apihelp-undelete-param-timestamps": "Timestamps of the revisions to restore. If both <var>$1timestamps</var> and <var>$1fileids</var> are empty, all will be restored.",
        "apihelp-undelete-param-fileids": "IDs of the file revisions to restore. If both <var>$1timestamps</var> and <var>$1fileids</var> are empty, all will be restored.",
        "apihelp-undelete-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
index 2108b33..26fe02d 100644 (file)
        "apihelp-patrol-description": "{{doc-apihelp-description|patrol}}",
        "apihelp-patrol-param-rcid": "{{doc-apihelp-param|patrol|rcid}}",
        "apihelp-patrol-param-revid": "{{doc-apihelp-param|patrol|revid}}",
+       "apihelp-patrol-param-tags": "{{doc-apihelp-param|patrol|tags}}",
        "apihelp-patrol-example-rcid": "{{doc-apihelp-example|patrol}}",
        "apihelp-patrol-example-revid": "{{doc-apihelp-example|patrol}}",
        "apihelp-protect-description": "{{doc-apihelp-description|protect}}",
        "apihelp-protect-param-protections": "{{doc-apihelp-param|protect|protections}}",
        "apihelp-protect-param-expiry": "{{doc-apihelp-param|protect|expiry}}",
        "apihelp-protect-param-reason": "{{doc-apihelp-param|protect|reason}}",
+       "apihelp-protect-param-tags": "{{doc-apihelp-param|protect|tags}}",
        "apihelp-protect-param-cascade": "{{doc-apihelp-param|protect|cascade}}",
        "apihelp-protect-param-watch": "{{doc-apihelp-param|protect|watch}}",
        "apihelp-protect-param-watchlist": "{{doc-apihelp-param|protect|watchlist}}",
        "apihelp-unblock-param-id": "{{doc-apihelp-param|unblock|id}}",
        "apihelp-unblock-param-user": "{{doc-apihelp-param|unblock|user}}",
        "apihelp-unblock-param-reason": "{{doc-apihelp-param|unblock|reason}}",
+       "apihelp-unblock-param-tags": "{{doc-apihelp-param|unblock|tags}}",
        "apihelp-unblock-example-id": "{{doc-apihelp-example|unblock}}",
        "apihelp-unblock-example-user": "{{doc-apihelp-example|unblock}}",
        "apihelp-undelete-description": "{{doc-apihelp-description|undelete}}",
        "apihelp-undelete-param-title": "{{doc-apihelp-param|undelete|title}}",
        "apihelp-undelete-param-reason": "{{doc-apihelp-param|undelete|reason}}",
+       "apihelp-undelete-param-tags": "{{doc-apihelp-param|undelete|tags}}",
        "apihelp-undelete-param-timestamps": "{{doc-apihelp-param|undelete|timestamps}}",
        "apihelp-undelete-param-fileids": "{{doc-apihelp-param|undelete|fileids}}",
        "apihelp-undelete-param-watchlist": "{{doc-apihelp-param|undelete|watchlist}}",
index 2c95928..b6a0868 100644 (file)
@@ -430,9 +430,11 @@ class RecentChange {
         *
         * @param RecentChange|int $change RecentChange or corresponding rc_id
         * @param bool $auto For automatic patrol
+        * @param string|string[] $tags Change tags to add to the patrol log entry
+        *   ($user should be able to add the specified tags before this is called)
         * @return array See doMarkPatrolled(), or null if $change is not an existing rc_id
         */
-       public static function markPatrolled( $change, $auto = false ) {
+       public static function markPatrolled( $change, $auto = false, $tags = null ) {
                global $wgUser;
 
                $change = $change instanceof RecentChange
@@ -443,7 +445,7 @@ class RecentChange {
                        return null;
                }
 
-               return $change->doMarkPatrolled( $wgUser, $auto );
+               return $change->doMarkPatrolled( $wgUser, $auto, $tags );
        }
 
        /**
@@ -453,9 +455,11 @@ class RecentChange {
         * 'markedaspatrollederror-noautopatrol' as errors
         * @param User $user User object doing the action
         * @param bool $auto For automatic patrol
+        * @param string|string[] $tags Change tags to add to the patrol log entry
+        *   ($user should be able to add the specified tags before this is called)
         * @return array Array of permissions errors, see Title::getUserPermissionsErrors()
         */
-       public function doMarkPatrolled( User $user, $auto = false ) {
+       public function doMarkPatrolled( User $user, $auto = false, $tags = null ) {
                global $wgUseRCPatrol, $wgUseNPPatrol, $wgUseFilePatrol;
                $errors = [];
                // If recentchanges patrol is disabled, only new pages or new file versions
@@ -490,7 +494,8 @@ class RecentChange {
                // Actually set the 'patrolled' flag in RC
                $this->reallyMarkPatrolled();
                // Log this patrol event
-               PatrolLog::record( $this, $auto, $user );
+               PatrolLog::record( $this, $auto, $user, $tags );
+
                Hooks::run(
                                        'MarkPatrolledComplete',
                                        [ $this->getAttribute( 'rc_id' ), &$user, false, $auto ]
index af2f66b..9db1697 100644 (file)
@@ -114,7 +114,7 @@ class ChangeTags {
        /**
         * Add tags to a change given its rc_id, rev_id and/or log_id
         *
-        * @param string|array $tags Tags to add to the change
+        * @param string|string[] $tags Tags to add to the change
         * @param int|null $rc_id The rc_id of the change to add the tags to
         * @param int|null $rev_id The rev_id of the change to add the tags to
         * @param int|null $log_id The log_id of the change to add the tags to
index c232d82..c97f38f 100644 (file)
@@ -1479,18 +1479,14 @@ class LocalFile extends File {
                                __METHOD__
                        );
 
-                       # Now that the log entry is up-to-date, make an RC entry.
-                       $recentChange = $logEntry->publish( $logId );
-
+                       # Add change tags, if any
                        if ( $tags ) {
-                               ChangeTags::addTags(
-                                       $tags,
-                                       $recentChange ? $recentChange->getAttribute( 'rc_id' ) : null,
-                                       $logEntry->getAssociatedRevId(),
-                                       $logId
-                               );
+                               $logEntry->setTags( $tags );
                        }
 
+                       # Now that the log entry is up-to-date, make an RC entry.
+                       $logEntry->publish( $logId );
+
                        # Run hook for other updates (typically more cache purging)
                        Hooks::run( 'FileUpload', [ $that, $reupload, !$newPageContent ] );
 
index e76aa29..1d0a543 100644 (file)
@@ -419,6 +419,9 @@ class ManualLogEntry extends LogEntryBase {
        /** @var int A rev id associated to the log entry */
        protected $revId = 0;
 
+       /** @var array Change tags add to the log entry */
+       protected $tags = null;
+
        /** @var int Deletion state of the log entry */
        protected $deleted;
 
@@ -529,6 +532,19 @@ class ManualLogEntry extends LogEntryBase {
                $this->revId = $revId;
        }
 
+       /**
+        * Set change tags for the log entry.
+        *
+        * @since 1.27
+        * @param string|string[] $tags
+        */
+       public function setTags( $tags ) {
+               if ( is_string( $tags ) ) {
+                       $tags = [ $tags ];
+               }
+               $this->tags = $tags;
+       }
+
        /**
         * Set the 'legacy' flag
         *
@@ -696,6 +712,14 @@ class ManualLogEntry extends LogEntryBase {
                        PatrolLog::record( $rc, true, $this->getPerformer() );
                }
 
+               // Add change tags to the log entry and (if applicable) the associated revision
+               $tags = $this->getTags();
+               if ( !is_null( $tags ) ) {
+                       $rcId = $rc->getAttribute( 'rc_id' );
+                       $revId = $this->getAssociatedRevId(); // Use null if $revId is 0
+                       ChangeTags::addTags( $tags, $rcId, $revId > 0 ? $revId : null, $newId );
+               }
+
                return $rc;
        }
 
@@ -743,6 +767,14 @@ class ManualLogEntry extends LogEntryBase {
                return $this->revId;
        }
 
+       /**
+        * @since 1.27
+        * @return array
+        */
+       public function getTags() {
+               return $this->tags;
+       }
+
        /**
         * @since 1.25
         * @return bool
index 510c711..f6ecc50 100644 (file)
@@ -33,10 +33,12 @@ class PatrolLog {
         * @param int|RecentChange $rc Change identifier or RecentChange object
         * @param bool $auto Was this patrol event automatic?
         * @param User $user User performing the action or null to use $wgUser
+        * @param string|string[] $tags Change tags to add to the patrol log entry
+        *   ($user should be able to add the specified tags before this is called)
         *
         * @return bool
         */
-       public static function record( $rc, $auto = false, User $user = null ) {
+       public static function record( $rc, $auto = false, User $user = null, $tags = null ) {
                global $wgLogAutopatrol;
 
                // do not log autopatrolled edits if setting disables it
@@ -60,6 +62,7 @@ class PatrolLog {
                $entry->setTarget( $rc->getTitle() );
                $entry->setParameters( self::buildParams( $rc, $auto ) );
                $entry->setPerformer( $user );
+               $entry->setTags( $tags );
                $logid = $entry->insert();
                if ( !$auto ) {
                        $entry->publish( $logid, 'udp' );
index 3308890..fb4f247 100644 (file)
@@ -2403,10 +2403,13 @@ class WikiPage implements Page, IDBAccessObject {
         * @param int &$cascade Set to false if cascading protection isn't allowed.
         * @param string $reason
         * @param User $user The user updating the restrictions
-        * @return Status
+        * @param string|string[] $tags Change tags to add to the pages and protection log entries
+        *   ($user should be able to add the specified tags before this is called)
+        * @return Status Status object; if action is taken, $status->value is the log_id of the
+        *   protection log entry.
         */
        public function doUpdateRestrictions( array $limit, array $expiry,
-               &$cascade, $reason, User $user
+               &$cascade, $reason, User $user, $tags = null
        ) {
                global $wgCascadingRestrictionLevels, $wgContLang;
 
@@ -2488,6 +2491,9 @@ class WikiPage implements Page, IDBAccessObject {
                $logRelationsField = null;
                $logParamsDetails = [];
 
+               // Null revision (used for change tag insertion)
+               $nullRevision = null;
+
                if ( $id ) { // Protection of existing page
                        if ( !Hooks::run( 'ArticleProtect', [ &$this, &$user, $limit, $reason ] ) ) {
                                return Status::newGood();
@@ -2631,13 +2637,17 @@ class WikiPage implements Page, IDBAccessObject {
                $logEntry->setComment( $reason );
                $logEntry->setPerformer( $user );
                $logEntry->setParameters( $params );
+               if ( !is_null( $nullRevision ) ) {
+                       $logEntry->setAssociatedRevId( $nullRevision->getId() );
+               }
+               $logEntry->setTags( $tags );
                if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
                        $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
                }
                $logId = $logEntry->insert();
                $logEntry->publish( $logId );
 
-               return Status::newGood();
+               return Status::newGood( $logId );
        }
 
        /**
index 4a5dd55..d14e02f 100644 (file)
@@ -169,6 +169,9 @@ class SpecialUnblock extends SpecialPage {
        /**
         * Process the form
         *
+        * Change tags can be provided via $data['Tags'], but the calling function
+        * must check if the tags can be added by the user prior to this function.
+        *
         * @param array $data
         * @param IContextSource $context
         * @throws ErrorPageError
@@ -235,6 +238,7 @@ class SpecialUnblock extends SpecialPage {
                $logEntry->setTarget( $page );
                $logEntry->setComment( $data['Reason'] );
                $logEntry->setPerformer( $performer );
+               $logEntry->setTags( $data['Tags'] );
                $logId = $logEntry->insert();
                $logEntry->publish( $logId );
 
index 81ec4cc..52a3d17 100644 (file)
@@ -360,11 +360,13 @@ class PageArchive {
         * @param array $fileVersions
         * @param bool $unsuppress
         * @param User $user User performing the action, or null to use $wgUser
+        * @param string|string[] $tags Change tags to add to log entry
+        *   ($user should be able to add the specified tags before this is called)
         * @return array(number of file revisions restored, number of image revisions
         *   restored, log message) on success, false on failure.
         */
        function undelete( $timestamps, $comment = '', $fileVersions = [],
-               $unsuppress = false, User $user = null
+               $unsuppress = false, User $user = null, $tags = null
        ) {
                // If both the set of text revisions and file revisions are empty,
                // restore everything. Otherwise, just restore the requested items.
@@ -426,6 +428,7 @@ class PageArchive {
                $logEntry->setPerformer( $user );
                $logEntry->setTarget( $this->title );
                $logEntry->setComment( $reason );
+               $logEntry->setTags( $tags );
 
                Hooks::run( 'ArticleUndeleteLogEntry', [ $this, &$logEntry, $user ] );