From c53f647658ade8652900994a4a78468d62ec616a Mon Sep 17 00:00:00 2001 From: cenarium Date: Sun, 17 May 2015 17:36:29 +0200 Subject: [PATCH] Allow patrol of uploads This allows to patrol file uploads, both new files and new file versions, from the description page, provided $wgUseFilePatrol is set to true. Special:NewFiles can be filtered to hide patrolled files. Bug: T11501 Change-Id: If71af58719a4461f12d125455b7bef07164525ca --- includes/DefaultSettings.php | 10 +++- includes/changes/ChangesList.php | 5 ++ includes/changes/RecentChange.php | 10 ++-- includes/filerepo/file/LocalFile.php | 13 +++++- includes/page/Article.php | 64 ++++++++++++++++++++++++-- includes/specials/SpecialNewimages.php | 39 +++++++++++++++- includes/user/User.php | 12 +++++ languages/i18n/en.json | 2 + languages/i18n/qqq.json | 2 + 9 files changed, 145 insertions(+), 12 deletions(-) diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 54216fdf52..7415528140 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -6164,7 +6164,8 @@ $wgRCEngines = array( $wgRCWatchCategoryMembership = false; /** - * Use RC Patrolling to check for vandalism + * Use RC Patrolling to check for vandalism (from recent changes and watchlists) + * New pages and new files are included. */ $wgUseRCPatrol = true; @@ -6173,6 +6174,13 @@ $wgUseRCPatrol = true; */ $wgUseNPPatrol = true; +/** + * Use file patrolling to check new files on Special:Newfiles + * + * @since 1.27 + */ +$wgUseFilePatrol = true; + /** * Log autopatrol actions to the log table */ diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php index 2494ef1098..6081a17167 100644 --- a/includes/changes/ChangesList.php +++ b/includes/changes/ChangesList.php @@ -638,9 +638,11 @@ class ChangesList extends ContextSource { if ( $rc instanceof RecentChange ) { $isPatrolled = $rc->mAttribs['rc_patrolled']; $rcType = $rc->mAttribs['rc_type']; + $rcLogType = $rc->mAttribs['rc_log_type']; } else { $isPatrolled = $rc->rc_patrolled; $rcType = $rc->rc_type; + $rcLogType = $rc->rc_log_type; } if ( !$isPatrolled ) { @@ -650,6 +652,9 @@ class ChangesList extends ContextSource { if ( $user->useNPPatrol() && $rcType == RC_NEW ) { return true; } + if ( $user->useFilePatrol() && $rcLogType == 'upload' ) { + return true; + } } return false; diff --git a/includes/changes/RecentChange.php b/includes/changes/RecentChange.php index b0b88d3e64..df3b5bc3d9 100644 --- a/includes/changes/RecentChange.php +++ b/includes/changes/RecentChange.php @@ -456,11 +456,13 @@ class RecentChange { * @return array Array of permissions errors, see Title::getUserPermissionsErrors() */ public function doMarkPatrolled( User $user, $auto = false ) { - global $wgUseRCPatrol, $wgUseNPPatrol; + global $wgUseRCPatrol, $wgUseNPPatrol, $wgUseFilePatrol; $errors = array(); - // If recentchanges patrol is disabled, only new pages - // can be patrolled - if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) ) { + // If recentchanges patrol is disabled, only new pages or new file versions + // can be patrolled, provided the appropriate config variable is set + if ( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute( 'rc_type' ) != RC_NEW ) && + ( !$wgUseFilePatrol || !( $this->getAttribute( 'rc_type' ) == RC_LOG && + $this->getAttribute( 'rc_log_type' ) == 'upload' ) ) ) { $errors[] = array( 'rcpatroldisabled' ); } // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol" diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index 0da1ae887c..5a6a8b26c0 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -1330,6 +1330,7 @@ class LocalFile extends File { } $descTitle = $this->getTitle(); + $descId = $descTitle->getArticleID(); $wikiPage = new WikiFilePage( $descTitle ); $wikiPage->setFile( $this ); @@ -1362,7 +1363,7 @@ class LocalFile extends File { $nullRevision = Revision::newNullRevision( $dbw, - $descTitle->getArticleID(), + $descId, $editSummary, false, $user @@ -1374,6 +1375,8 @@ class LocalFile extends File { array( $wikiPage, $nullRevision, $nullRevision->getParentId(), $user ) ); $wikiPage->updateRevisionOn( $dbw, $nullRevision ); + // Associate null revision id + $logEntry->setAssociatedRevId( $nullRevision->getId() ); } $newPageContent = null; @@ -1391,7 +1394,7 @@ class LocalFile extends File { # b) They won't cause rollback of the log publish/update above $that = $this; $dbw->onTransactionIdle( function () use ( - $that, $reupload, $wikiPage, $newPageContent, $comment, $user, $logEntry, $logId + $that, $reupload, $wikiPage, $newPageContent, $comment, $user, $logEntry, $logId, $descId ) { # Update memcache after the commit $that->invalidateCache(); @@ -1408,6 +1411,10 @@ class LocalFile extends File { $user ); + if ( isset( $status->value['revision'] ) ) { + // Associate new page revision id + $logEntry->setAssociatedRevId( $status->value['revision']->getId() ); + } // This relies on the resetArticleID() call in WikiPage::insertOn(), // which is triggered on $descTitle by doEditContent() above. if ( isset( $status->value['revision'] ) ) { @@ -1424,6 +1431,8 @@ class LocalFile extends File { # Existing file page: invalidate description page cache $wikiPage->getTitle()->invalidateCache(); $wikiPage->getTitle()->purgeSquid(); + # Allow the new file version to be patrolled from the page footer + Article::purgePatrolFooterCache( $descId ); } # Now that the page exists, make an RC entry. diff --git a/includes/page/Article.php b/includes/page/Article.php index af1f00b505..22e24aef46 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -1061,14 +1061,14 @@ class Article implements Page { * @return bool */ public function showPatrolFooter() { - global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI; + global $wgUseNPPatrol, $wgUseRCPatrol, $wgUseFilePatrol, $wgEnableAPI, $wgEnableWriteAPI; $outputPage = $this->getContext()->getOutput(); $user = $this->getContext()->getUser(); $rc = false; if ( !$this->getTitle()->quickUserCan( 'patrol', $user ) - || !( $wgUseRCPatrol || $wgUseNPPatrol ) + || !( $wgUseRCPatrol || $wgUseNPPatrol || $wgUseFilePatrol ) ) { // Patrolling is disabled or the user isn't allowed to return false; @@ -1103,6 +1103,9 @@ class Article implements Page { __METHOD__ ); + $cantPatrolNewPage = false; + $cantPatrolFile = false; + if ( $oldestRevisionTimestamp && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 ) ) { @@ -1116,7 +1119,51 @@ class Article implements Page { ), __METHOD__ ); + if ( $rc ) { + // Use generic patrol message for new pages + $markPatrolledMsg = wfMessage( 'markaspatrolledtext' ); + } + } else { + $cantPatrolNewPage = true; + } + + // Allow patrolling of latest file upload + if ( !$rc && $wgUseFilePatrol && $this->getTitle()->getNamespace() === NS_FILE ) { + // Retrieve timestamp of most recent upload + $newestUploadTimestamp = $dbr->selectField( + 'image', + 'MAX( img_timestamp )', + array( 'img_name' => $this->getTitle()->getDBkey() ), + __METHOD__ + ); + if ( $newestUploadTimestamp + && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 ) + ) { + // 6h tolerance because the RC might not be cleaned out regularly + $rc = RecentChange::newFromConds( + array( + 'rc_type' => RC_LOG, + 'rc_log_type' => 'upload', + 'rc_timestamp' => $newestUploadTimestamp, + 'rc_namespace' => NS_FILE, + 'rc_cur_id' => $this->getTitle()->getArticleID(), + 'rc_patrolled' => 0 + ), + __METHOD__, + array( 'USE INDEX' => 'rc_timestamp' ) + ); + if ( $rc ) { + // Use patrol message specific to files + $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' ); + } + } else { + $cantPatrolFile = true; + } } else { + $cantPatrolFile = true; + } + + if ( $cantPatrolFile && $cantPatrolNewPage ) { // Cache the information we gathered above in case we can't patrol // Don't cache in case we can patrol as this could change $cache->set( $key, '1' ); @@ -1156,7 +1203,7 @@ class Article implements Page { $link = Linker::linkKnown( $this->getTitle(), - wfMessage( 'markaspatrolledtext' )->escaped(), + $markPatrolledMsg->escaped(), array(), array( 'action' => 'markpatrolled', @@ -1174,6 +1221,17 @@ class Article implements Page { return true; } + /** + * Purge the cache used to check if it is worth showing the patrol footer + * For example, it is done during re-uploads when file patrol is used. + * @param int $articleID ID of the article to purge + * @since 1.27 + */ + public static function purgePatrolFooterCache( $articleID ) { + $cache = ObjectCache::getMainWANInstance(); + $cache->touchCheckKey( wfMemcKey( 'unpatrollable-page', $articleID ) ); + } + /** * Show the error text for a missing article. For articles in the MediaWiki * namespace, show the default message text. To be called from Article::view(). diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index 6b7c0387b6..53fb45e707 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -81,9 +81,20 @@ class NewFilesPager extends ReverseChronologicalPager { */ protected $gallery; + /** + * @var bool + */ + protected $showBots; + + /** + * @var bool + */ + protected $hidePatrolled; + function __construct( IContextSource $context, $par = null ) { $this->like = $context->getRequest()->getText( 'like' ); - $this->showbots = $context->getRequest()->getBool( 'showbots', 0 ); + $this->showBots = $context->getRequest()->getBool( 'showbots', 0 ); + $this->hidePatrolled = $context->getRequest()->getBool( 'hidepatrolled', 0 ); if ( is_numeric( $par ) ) { $this->setLimit( $par ); } @@ -95,7 +106,7 @@ class NewFilesPager extends ReverseChronologicalPager { $conds = $jconds = array(); $tables = array( 'image' ); - if ( !$this->showbots ) { + if ( !$this->showBots ) { $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' ); if ( count( $groupsWithBotPermission ) ) { @@ -111,6 +122,21 @@ class NewFilesPager extends ReverseChronologicalPager { } } + if ( $this->hidePatrolled ) { + $tables[] = 'recentchanges'; + $conds['rc_type'] = RC_LOG; + $conds['rc_log_type'] = 'upload'; + $conds['rc_patrolled'] = 0; + $jconds['recentchanges'] = array( + 'INNER JOIN', + array( + 'rc_title = img_name', + 'rc_user = img_user', + 'rc_timestamp = img_timestamp' + ) + ); + } + if ( !$this->getConfig()->get( 'MiserMode' ) && $this->like !== null ) { $dbr = wfGetDB( DB_SLAVE ); $likeObj = Title::newFromText( $this->like ); @@ -185,6 +211,11 @@ class NewFilesPager extends ReverseChronologicalPager { 'label-message' => 'newimages-showbots', 'name' => 'showbots', ), + 'hidepatrolled' => array( + 'type' => 'check', + 'label-message' => 'newimages-hidepatrolled', + 'name' => 'hidepatrolled', + ), 'limit' => array( 'type' => 'hidden', 'default' => $this->mLimit, @@ -201,6 +232,10 @@ class NewFilesPager extends ReverseChronologicalPager { unset( $fields['like'] ); } + if ( !$this->getUser()->useFilePatrol() ) { + unset( $fields['hidepatrolled'] ); + } + $context = new DerivativeContext( $this->getContext() ); $context->setTitle( $this->getTitle() ); // Remove subpage $form = new HTMLForm( $fields, $context ); diff --git a/includes/user/User.php b/includes/user/User.php index fed9664abd..10365b06c8 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -3310,6 +3310,18 @@ class User implements IDBAccessObject { ); } + /** + * Check whether to enable new files patrol features for this user + * @return bool True or false + */ + public function useFilePatrol() { + global $wgUseRCPatrol, $wgUseFilePatrol; + return ( + ( $wgUseRCPatrol || $wgUseFilePatrol ) + && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) ) + ); + } + /** * Get the WebRequest object to use with this object * diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 000b4aec4c..ce3eb6c995 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2711,6 +2711,7 @@ "markaspatrolleddiff": "Mark as patrolled", "markaspatrolledlink": "[$1]", "markaspatrolledtext": "Mark this page as patrolled", + "markaspatrolledtext-file": "Mark this file version as patrolled", "markedaspatrolled": "Marked as patrolled", "markedaspatrolledtext": "The selected revision of [[:$1]] has been marked as patrolled.", "rcpatroldisabled": "Recent changes patrol disabled", @@ -2764,6 +2765,7 @@ "newimages-legend": "Filter", "newimages-label": "Filename (or a part of it):", "newimages-showbots": "Show uploads by bots", + "newimages-hidepatrolled": "Hide patrolled uploads", "noimages": "Nothing to see.", "ilsubmit": "Search", "bydate": "by date", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index d21718e385..0ef31e34b3 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2886,6 +2886,7 @@ "markaspatrolleddiff": "{{doc-actionlink}}\nSee also:\n* {{msg-mw|Markaspatrolledtext}}\n{{Identical|Mark as patrolled}}", "markaspatrolledlink": "{{notranslate}}\nParameters:\n* $1 - link which has text {{msg-mw|Markaspatrolledtext}}", "markaspatrolledtext": "{{doc-actionlink}}\nSee also:\n* {{msg-mw|Markaspatrolleddiff}}", + "markaspatrolledtext-file": "Same as markaspatrolledtext, but for files (new versions included) instead of pages.", "markedaspatrolled": "Used as title of the message {{msg-mw|Markedaspatrolledtext}}, when marking a change as patrolled.\n{{Related|Markedaspatrolled}}", "markedaspatrolledtext": "Used when marking a change as patrolled.\n\nThe title for this message is {{msg-mw|Markedaspatrolled}}.\n\nParameters:\n* $1 - page title\n{{Related|Markedaspatrolled}}", "rcpatroldisabled": "Used as title of the error message {{msg-mw|Rcpatroldisabledtext}}, when marking a change as patrolled.\n{{Related|Markedaspatrolled}}", @@ -2939,6 +2940,7 @@ "newimages-legend": "Caption of the fieldset for the filter on [[Special:NewImages]]\n\n{{Identical|Filter}}", "newimages-label": "Caption of the filter editbox on [[Special:NewImages]]", "newimages-showbots": "Used as label for a checkbox. When checked, [[Special:NewImages]] will also display uploads by users in the bots group.", + "newimages-hidepatrolled": "Used as label for a checkbox. When checked, [[Special:NewImages]] will not display patrolled uploads.", "noimages": "This is shown on the special page [[Special:NewImages]], when there aren't any recently uploaded files.", "ilsubmit": "Used as label for input box in the MIMESearch form on [[Special:MIMESearch]].\n\nSee also:\n* {{msg-mw|Mimesearch|page title}}\n* {{msg-mw|Mimetype|label for input box}}\n{{Identical|Search}}", "bydate": "{{Identical|Date}}", -- 2.20.1