From e70c4eb66443583f9cea37f3209325e49b0296ac Mon Sep 17 00:00:00 2001 From: Geoffrey Mon Date: Sun, 24 Jan 2016 23:11:36 -0500 Subject: [PATCH] Add tags support to patrol, protect, unblock, and undelete - 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 --- includes/api/ApiPatrol.php | 17 +++++++++++++- includes/api/ApiProtect.php | 18 ++++++++++++++- includes/api/ApiUnblock.php | 15 ++++++++++++- includes/api/ApiUndelete.php | 15 ++++++++++++- includes/api/i18n/en.json | 4 ++++ includes/api/i18n/qqq.json | 4 ++++ includes/changes/RecentChange.php | 13 +++++++---- includes/changetags/ChangeTags.php | 2 +- includes/filerepo/file/LocalFile.php | 14 +++++------- includes/logging/LogEntry.php | 32 +++++++++++++++++++++++++++ includes/logging/PatrolLog.php | 5 ++++- includes/page/WikiPage.php | 16 +++++++++++--- includes/specials/SpecialUnblock.php | 4 ++++ includes/specials/SpecialUndelete.php | 5 ++++- 14 files changed, 141 insertions(+), 23 deletions(-) diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php index 1230babb9b..62528825de 100644 --- a/includes/api/ApiPatrol.php +++ b/includes/api/ApiPatrol.php @@ -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, + ], ]; } diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index f1c01210dc..d28906069f 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -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, diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php index 5f268c5b47..ace41a4e36 100644 --- a/includes/api/ApiUnblock.php +++ b/includes/api/ApiUnblock.php @@ -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, + ], ]; } diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php index f4fcb06b4d..e24f2ced59 100644 --- a/includes/api/ApiUndelete.php +++ b/includes/api/ApiUndelete.php @@ -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, diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 6ea643a9bc..2d1542ead6 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -343,6 +343,7 @@ "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.", @@ -352,6 +353,7 @@ "apihelp-protect-param-protections": "List of protection levels, formatted action=level (e.g. edit=sysop).\n\nNote: 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 infinite, indefinite, infinity, or never, 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.", @@ -1331,12 +1333,14 @@ "apihelp-unblock-param-id": "ID of the block to unblock (obtained through list=blocks). Cannot be used together with $1user.", "apihelp-unblock-param-user": "Username, IP address or IP range to unblock. Cannot be used together with $1id.", "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 #105.", "apihelp-unblock-example-user": "Unblock user Bob with reason Sorry Bob.", "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 $1timestamps and $1fileids are empty, all will be restored.", "apihelp-undelete-param-fileids": "IDs of the file revisions to restore. If both $1timestamps and $1fileids 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.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 2108b332de..26fe02d17b 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -325,6 +325,7 @@ "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}}", @@ -333,6 +334,7 @@ "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}}", @@ -1240,11 +1242,13 @@ "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}}", diff --git a/includes/changes/RecentChange.php b/includes/changes/RecentChange.php index 2c95928509..b6a086805e 100644 --- a/includes/changes/RecentChange.php +++ b/includes/changes/RecentChange.php @@ -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 ] diff --git a/includes/changetags/ChangeTags.php b/includes/changetags/ChangeTags.php index af2f66ba9c..9db169755f 100644 --- a/includes/changetags/ChangeTags.php +++ b/includes/changetags/ChangeTags.php @@ -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 diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index c232d8266d..c97f38f771 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -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 ] ); diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php index e76aa29476..1d0a54361f 100644 --- a/includes/logging/LogEntry.php +++ b/includes/logging/LogEntry.php @@ -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 diff --git a/includes/logging/PatrolLog.php b/includes/logging/PatrolLog.php index 510c71150d..f6ecc501a3 100644 --- a/includes/logging/PatrolLog.php +++ b/includes/logging/PatrolLog.php @@ -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' ); diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index 330889081d..fb4f247f0c 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -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 ); } /** diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php index 4a5dd5572c..d14e02f573 100644 --- a/includes/specials/SpecialUnblock.php +++ b/includes/specials/SpecialUnblock.php @@ -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 ); diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index 81ec4cc6a9..52a3d17c27 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -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 ] ); -- 2.20.1