Allow patrol of uploads
authorcenarium <cenarium.sysop@gmail.com>
Sun, 17 May 2015 15:36:29 +0000 (17:36 +0200)
committerBartosz Dziewoński <matma.rex@gmail.com>
Wed, 6 Jan 2016 22:57:22 +0000 (14:57 -0800)
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
includes/changes/ChangesList.php
includes/changes/RecentChange.php
includes/filerepo/file/LocalFile.php
includes/page/Article.php
includes/specials/SpecialNewimages.php
includes/user/User.php
languages/i18n/en.json
languages/i18n/qqq.json

index 54216fd..7415528 100644 (file)
@@ -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
  */
index 2494ef1..6081a17 100644 (file)
@@ -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;
index b0b88d3..df3b5bc 100644 (file)
@@ -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"
index 0da1ae8..5a6a8b2 100644 (file)
@@ -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.
index af1f00b..22e24ae 100644 (file)
@@ -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().
index 6b7c038..53fb45e 100644 (file)
@@ -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 );
index fed9664..10365b0 100644 (file)
@@ -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
         *
index 000b4ae..ce3eb6c 100644 (file)
        "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",
        "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",
index d21718e..0ef31e3 100644 (file)
        "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}}",
        "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}}",