Merge "RCFilters: Convert patrolled filter to three states"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 12 Apr 2018 16:07:07 +0000 (16:07 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 12 Apr 2018 16:07:07 +0000 (16:07 +0000)
1  2 
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialWatchlist.php
languages/i18n/en.json
languages/i18n/qqq.json

@@@ -22,7 -22,7 +22,7 @@@
   */
  use MediaWiki\Logger\LoggerFactory;
  use Wikimedia\Rdbms\DBQueryTimeoutError;
 -use Wikimedia\Rdbms\ResultWrapper;
 +use Wikimedia\Rdbms\IResultWrapper;
  use Wikimedia\Rdbms\FakeResultWrapper;
  use Wikimedia\Rdbms\IDatabase;
  
@@@ -87,9 -87,12 +87,12 @@@ abstract class ChangesListSpecialPage e
  
        // Same format as filterGroupDefinitions, but for a single group (reviewStatus)
        // that is registered conditionally.
+       private $legacyReviewStatusFilterGroupDefinition;
+       // Single filter group registered conditionally
        private $reviewStatusFilterGroupDefinition;
  
-       // Single filter registered conditionally
+       // Single filter group registered conditionally
        private $hideCategorizationFilterDefinition;
  
        /**
                                ]
                        ],
  
-                       // reviewStatus (conditional)
+                       // significance (conditional)
  
                        [
                                'name' => 'significance',
  
                ];
  
-               $this->reviewStatusFilterGroupDefinition = [
+               $this->legacyReviewStatusFilterGroupDefinition = [
                        [
-                               'name' => 'reviewStatus',
+                               'name' => 'legacyReviewStatus',
                                'title' => 'rcfilters-filtergroup-reviewstatus',
                                'class' => ChangesListBooleanFilterGroup::class,
-                               'priority' => -5,
                                'filters' => [
                                        [
                                                'name' => 'hidepatrolled',
-                                               'label' => 'rcfilters-filter-patrolled-label',
-                                               'description' => 'rcfilters-filter-patrolled-description',
                                                // rcshowhidepatr-show, rcshowhidepatr-hide
                                                // wlshowhidepatr
                                                'showHideSuffix' => 'showhidepatr',
                                                ) {
                                                        $conds[] = 'rc_patrolled = 0';
                                                },
-                                               'cssClassSuffix' => 'patrolled',
-                                               'isRowApplicableCallable' => function ( $ctx, $rc ) {
-                                                       return $rc->getAttribute( 'rc_patrolled' );
-                                               },
+                                               'isReplacedInStructuredUi' => true,
                                        ],
                                        [
                                                'name' => 'hideunpatrolled',
-                                               'label' => 'rcfilters-filter-unpatrolled-label',
-                                               'description' => 'rcfilters-filter-unpatrolled-description',
                                                'default' => false,
                                                'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
                                                        &$query_options, &$join_conds
                                                ) {
                                                        $conds[] = 'rc_patrolled != 0';
                                                },
-                                               'cssClassSuffix' => 'unpatrolled',
+                                               'isReplacedInStructuredUi' => true,
+                                       ],
+                               ],
+                       ]
+               ];
+               $this->reviewStatusFilterGroupDefinition = [
+                       [
+                               'name' => 'reviewStatus',
+                               'title' => 'rcfilters-filtergroup-reviewstatus',
+                               'class' => ChangesListStringOptionsFilterGroup::class,
+                               'isFullCoverage' => true,
+                               'priority' => -5,
+                               'filters' => [
+                                       [
+                                               'name' => 'unpatrolled',
+                                               'label' => 'rcfilters-filter-reviewstatus-unpatrolled-label',
+                                               'description' => 'rcfilters-filter-reviewstatus-unpatrolled-description',
+                                               'cssClassSuffix' => 'reviewstatus-unpatrolled',
+                                               'isRowApplicableCallable' => function ( $ctx, $rc ) {
+                                                       return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_UNPATROLLED;
+                                               },
+                                       ],
+                                       [
+                                               'name' => 'manual',
+                                               'label' => 'rcfilters-filter-reviewstatus-manual-label',
+                                               'description' => 'rcfilters-filter-reviewstatus-manual-description',
+                                               'cssClassSuffix' => 'reviewstatus-manual',
                                                'isRowApplicableCallable' => function ( $ctx, $rc ) {
-                                                       return !$rc->getAttribute( 'rc_patrolled' );
+                                                       return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_PATROLLED;
+                                               },
+                                       ],
+                                       [
+                                               'name' => 'auto',
+                                               'label' => 'rcfilters-filter-reviewstatus-auto-label',
+                                               'description' => 'rcfilters-filter-reviewstatus-auto-description',
+                                               'cssClassSuffix' => 'reviewstatus-auto',
+                                               'isRowApplicableCallable' => function ( $ctx, $rc ) {
+                                                       return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_AUTOPATROLLED;
                                                },
                                        ],
                                ],
+                               'default' => ChangesListStringOptionsFilterGroup::NONE,
+                               'queryCallable' => function ( $specialPageClassName, $ctx, $dbr,
+                                       &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selected
+                               ) {
+                                       if ( $selected === [] ) {
+                                               return;
+                                       }
+                                       $rcPatrolledValues = [
+                                               'unpatrolled' => RecentChange::PRC_UNPATROLLED,
+                                               'manual' => RecentChange::PRC_PATROLLED,
+                                               'auto' => RecentChange::PRC_AUTOPATROLLED,
+                                       ];
+                                       // e.g. rc_patrolled IN (0, 2)
+                                       $conds['rc_patrolled'] = array_map( function ( $s ) use ( $rcPatrolledValues ) {
+                                               return $rcPatrolledValues[ $s ];
+                                       }, $selected );
+                               }
                        ]
                ];
  
        /**
         * Get the database result for this special page instance. Used by ApiFeedRecentChanges.
         *
 -       * @return bool|ResultWrapper Result or false
 +       * @return bool|IResultWrapper Result or false
         */
        public function getRows() {
                $opts = $this->getOptions();
                // information to all users just because the user that saves the edit can
                // patrol or is logged in)
                if ( !$this->including() && $this->getUser()->useRCPatrol() ) {
+                       $this->registerFiltersFromDefinitions( $this->legacyReviewStatusFilterGroupDefinition );
                        $this->registerFiltersFromDefinitions( $this->reviewStatusFilterGroupDefinition );
                }
  
        }
  
        /**
-        * Replace old options 'hideanons' or 'hideliu' with structured UI equivalent
+        * Replace old options with their structured UI equivalents
         *
         * @param FormOptions $opts
         * @return bool True if the change was made
                        return false;
                }
  
+               $changed = false;
                // At this point 'hideanons' and 'hideliu' cannot be both true,
                // because fixBackwardsCompatibilityOptions resets (at least) 'hideanons' in such case
                if ( $opts[ 'hideanons' ] ) {
                        $opts->reset( 'hideanons' );
                        $opts[ 'userExpLevel' ] = 'registered';
-                       return true;
+                       $changed = true;
                }
  
                if ( $opts[ 'hideliu' ] ) {
                        $opts->reset( 'hideliu' );
                        $opts[ 'userExpLevel' ] = 'unregistered';
-                       return true;
+                       $changed = true;
                }
  
-               return false;
+               if ( $this->getFilterGroup( 'legacyReviewStatus' ) ) {
+                       if ( $opts[ 'hidepatrolled' ] ) {
+                               $opts->reset( 'hidepatrolled' );
+                               $opts[ 'reviewStatus' ] = 'unpatrolled';
+                               $changed = true;
+                       }
+                       if ( $opts[ 'hideunpatrolled' ] ) {
+                               $opts->reset( 'hideunpatrolled' );
+                               $opts[ 'reviewStatus' ] = implode(
+                                       ChangesListStringOptionsFilterGroup::SEPARATOR,
+                                       [ 'manual', 'auto' ]
+                               );
+                               $changed = true;
+                       }
+               }
+               return $changed;
        }
  
        /**
         * @param array $query_options Array of query options; see IDatabase::select $options
         * @param array $join_conds Array of join conditions; see IDatabase::select $join_conds
         * @param FormOptions $opts
 -       * @return bool|ResultWrapper Result or false
 +       * @return bool|IResultWrapper Result or false
         */
        protected function doMainQuery( $tables, $fields, $conds,
                $query_options, $join_conds, FormOptions $opts
        /**
         * Send output to the OutputPage object, only called if not used feeds
         *
 -       * @param ResultWrapper $rows Database rows
 +       * @param IResultWrapper $rows Database rows
         * @param FormOptions $opts
         */
        public function webOutput( $rows, $opts ) {
        /**
         * Build and output the actual changes list.
         *
 -       * @param ResultWrapper $rows Database rows
 +       * @param IResultWrapper $rows Database rows
         * @param FormOptions $opts
         */
        abstract public function outputChangesList( $rows, $opts );
@@@ -22,7 -22,7 +22,7 @@@
   */
  
  use MediaWiki\MediaWikiServices;
 -use Wikimedia\Rdbms\ResultWrapper;
 +use Wikimedia\Rdbms\IResultWrapper;
  use Wikimedia\Rdbms\FakeResultWrapper;
  
  /**
@@@ -208,8 -208,12 +208,12 @@@ class SpecialRecentChanges extends Chan
                $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
                if ( $reviewStatus !== null ) {
                        // Conditional on feature being available and rights
-                       $hidePatrolled = $reviewStatus->getFilter( 'hidepatrolled' );
-                       $hidePatrolled->setDefault( $user->getBoolOption( 'hidepatrolled' ) );
+                       if ( $user->getBoolOption( 'hidepatrolled' ) ) {
+                               $reviewStatus->setDefault( 'unpatrolled' );
+                               $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+                               $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+                               $legacyHidePatrolled->setDefault( true );
+                       }
                }
  
                $changeType = $this->getFilterGroup( 'changeType' );
        /**
         * Build and output the actual changes list.
         *
 -       * @param ResultWrapper $rows Database rows
 +       * @param IResultWrapper $rows Database rows
         * @param FormOptions $opts
         */
        public function outputChangesList( $rows, $opts ) {
         *
         * @deprecated since 1.31
         *
 -       * @param ResultWrapper &$rows Database rows
 +       * @param IResultWrapper &$rows Database rows
         * @param FormOptions $opts
         */
        function filterByCategories( &$rows, FormOptions $opts ) {
@@@ -22,7 -22,7 +22,7 @@@
   */
  
  use MediaWiki\MediaWikiServices;
 -use Wikimedia\Rdbms\ResultWrapper;
 +use Wikimedia\Rdbms\IResultWrapper;
  use Wikimedia\Rdbms\IDatabase;
  
  /**
@@@ -264,8 -264,12 +264,12 @@@ class SpecialWatchlist extends ChangesL
                $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
                if ( $reviewStatus !== null ) {
                        // Conditional on feature being available and rights
-                       $hidePatrolled = $reviewStatus->getFilter( 'hidepatrolled' );
-                       $hidePatrolled->setDefault( $user->getBoolOption( 'watchlisthidepatrolled' ) );
+                       if ( $user->getBoolOption( 'watchlisthidepatrolled' ) ) {
+                               $reviewStatus->setDefault( 'unpatrolled' );
+                               $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+                               $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+                               $legacyHidePatrolled->setDefault( true );
+                       }
                }
  
                $authorship = $this->getFilterGroup( 'authorship' );
        /**
         * Build and output the actual changes list.
         *
 -       * @param ResultWrapper $rows Database rows
 +       * @param IResultWrapper $rows Database rows
         * @param FormOptions $opts
         */
        public function outputChangesList( $rows, $opts ) {
diff --combined languages/i18n/en.json
@@@ -37,7 -37,7 +37,7 @@@
        "tog-watchlisthideminor": "Hide minor edits from the watchlist",
        "tog-watchlisthideliu": "Hide edits by logged in users from the watchlist",
        "tog-watchlistreloadautomatically": "Reload the watchlist automatically whenever a filter is changed (JavaScript required)",
 -      "tog-watchlistunwatchlinks": "Add direct unwatch/watch links to watchlist entries (JavaScript required for toggle functionality)",
 +      "tog-watchlistunwatchlinks": "Add direct unwatch/watch markers ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) to watched pages with changes (JavaScript required for toggle functionality)",
        "tog-watchlisthideanons": "Hide edits by anonymous users from the watchlist",
        "tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist",
        "tog-watchlisthidecategorization": "Hide categorization of pages",
        "password-login-forbidden": "The use of this username and password has been forbidden.",
        "mailmypassword": "Reset password",
        "passwordremindertitle": "New temporary password for {{SITENAME}}",
 -      "passwordremindertext": "Someone (probably you, from IP address $1) requested a new\npassword for {{SITENAME}} ($4). A temporary password for user\n\"$2\" has been created and was set to \"$3\". If this was your\nintent, you will need to log in and choose a new password now.\nYour temporary password will expire in {{PLURAL:$5|one day|$5 days}}.\n\nIf someone else made this request, or if you have remembered your password,\nand you no longer wish to change it, you may ignore this message and\ncontinue using your old password.",
 +      "passwordremindertext": "Someone (from IP address $1) requested a new\npassword for {{SITENAME}} ($4). A temporary password for user\n\"$2\" has been created and was set to \"$3\". If this was your\nintent, you will need to log in and choose a new password now.\nYour temporary password will expire in {{PLURAL:$5|one day|$5 days}}.\n\nIf someone else made this request, or if you have remembered your password,\nand you no longer wish to change it, you may ignore this message and\ncontinue using your old password.",
        "noemail": "There is no email address recorded for user \"$1\".",
        "noemailcreate": "You need to provide a valid email address.",
        "passwordsent": "A new password has been sent to the email address registered for \"$1\".\nPlease log in again after you receive it.",
        "longpageerror": "<strong>Error: The text you have submitted is {{PLURAL:$1|one kilobyte|$1 kilobytes}} long, which is longer than the maximum of {{PLURAL:$2|one kilobyte|$2 kilobytes}}.</strong>\nIt cannot be saved.",
        "readonlywarning": "<strong>Warning: The database has been locked for maintenance, so you will not be able to save your edits right now.</strong>\nYou may wish to copy and paste your text into a text file and save it for later.\n\nThe system administrator who locked it offered this explanation: $1",
        "protectedpagewarning": "<strong>Warning: This page has been protected so that only users with administrator privileges can edit it.</strong>\nThe latest log entry is provided below for reference:",
 -      "semiprotectedpagewarning": "<strong>Note:</strong> This page has been protected so that only registered users can edit it.\nThe latest log entry is provided below for reference:",
 +      "semiprotectedpagewarning": "<strong>Note:</strong> This page has been protected so that only autoconfirmed users can edit it.\nThe latest log entry is provided below for reference:",
        "cascadeprotectedwarning": "<strong>Warning:</strong> This page has been protected so that only users with [[Special:ListGroupRights|specific rights]] can edit it because it is transcluded in the following cascade-protected {{PLURAL:$1|page|pages}}:",
        "titleprotectedwarning": "<strong>Warning: This page has been protected so that [[Special:ListGroupRights|specific rights]] are needed to create it.</strong>\nThe latest log entry is provided below for reference:",
        "templatesused": "{{PLURAL:$1|Template|Templates}} used on this page:",
        "rcfilters-filter-humans-label": "Human (not bot)",
        "rcfilters-filter-humans-description": "Edits made by human editors.",
        "rcfilters-filtergroup-reviewstatus": "Review status",
-       "rcfilters-filter-patrolled-label": "Patrolled",
-       "rcfilters-filter-patrolled-description": "Edits marked as patrolled.",
-       "rcfilters-filter-unpatrolled-label": "Unpatrolled",
-       "rcfilters-filter-unpatrolled-description": "Edits not marked as patrolled.",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Edits not manually or automatically marked as patrolled.",
+       "rcfilters-filter-reviewstatus-unpatrolled-label": "Unpatrolled",
+       "rcfilters-filter-reviewstatus-manual-description": "Edits manually marked as patrolled.",
+       "rcfilters-filter-reviewstatus-manual-label": "Manually patrolled",
+       "rcfilters-filter-reviewstatus-auto-description": "Edits by advanced users whose work is automatically marked as patrolled.",
+       "rcfilters-filter-reviewstatus-auto-label": "Autopatrolled",
        "rcfilters-filtergroup-significance": "Significance",
        "rcfilters-filter-minor-label": "Minor edits",
        "rcfilters-filter-minor-description": "Edits the author labeled as minor.",
        "fix-double-redirects": "Update any redirects that point to the original title",
        "move-leave-redirect": "Leave a redirect behind",
        "protectedpagemovewarning": "<strong>Warning:</strong> This page has been protected so that only users with administrator privileges can move it.\nThe latest log entry is provided below for reference:",
 -      "semiprotectedpagemovewarning": "<strong>Note:</strong> This page has been protected so that only registered users can move it.\nThe latest log entry is provided below for reference:",
 +      "semiprotectedpagemovewarning": "<strong>Note:</strong> This page has been protected so that only autoconfirmed users can move it.\nThe latest log entry is provided below for reference:",
        "move-over-sharedrepo": "[[:$1]] exists on a shared repository. Moving a file to this title will override the shared file.",
        "file-exists-sharedrepo": "The filename chosen is already in use on a shared repository.\nPlease choose another name.",
        "export": "Export pages",
diff --combined languages/i18n/qqq.json
        "unicode-support-fail": "Error message shown to users if their browser doesn't support Unicode",
        "yourdiff": "Used as h2 header for the diff of the current version of a page with the user's version in case there is an edit conflict or a spam filter hit.",
        "copyrightwarning": "Copyright warning displayed under the edit box in editor. Parameters:\n* $1 - link\n* $2 - license name",
 -      "copyrightwarning2": "Copyright warning displayed under the edit box in editor\n*$1 - license name",
 +      "copyrightwarning2": "Copyright warning displayed under the edit box in editor\n* $1 - link",
        "editpage-head-copy-warn": "{{ignored}}Custom copyright warning in the header of an edit page.",
        "editpage-tos-summary": "{{notranslate}}",
        "editpage-cannot-use-custom-model": "Error message shown if the database does not support changing the content model of a page.",
        "rcfilters-legend-heading": "Used as a heading for legend box on [[Special:RecentChanges]] and [[Special:Watchlist]] when RCFilters are enabled.",
        "rcfilters-other-review-tools": "Used as a heading for the community collection of other links on [[Special:RecentChanges]] when RCFilters are enabled.",
        "rcfilters-group-results-by-page": "A label for the checkbox describing whether the results in [[Special:RecentChanges]] are grouped by page when RCFilters are enabled.",
 -      "rcfilters-activefilters": "Title for the filters selection showing the active filters.",
 +      "rcfilters-activefilters": "{{doc-important|Translations of this message should not more than 3 cm long, otherwise it will make bad user experiences for potential mobile users.}}\nTitle for the filters selection showing the active filters.",
        "rcfilters-advancedfilters": "Title for the buttons allowing the user to switch to the various advanced filters views.",
        "rcfilters-limit-title": "Title for the options to change the number of results shown.",
        "rcfilters-limit-and-date-label": "Title for the button that opens the operation to control how many results to show and in which time period to search. \n\nParameters: $1 - Number of results shown\n\n$2 - Time period to search. One of {{msg-mw|rcfilters-days-title}} or {{msg-mw|rcfilters-hours-title}} is used as $2\n{{Identical|Change}}",
        "rcfilters-filter-humans-label": "Label for the filter for showing edits made by human editors.",
        "rcfilters-filter-humans-description": "Description for the filter for showing edits made by human editors.",
        "rcfilters-filtergroup-reviewstatus": "Title for the filter group about review status (in core this is whether it's been patrolled)",
-       "rcfilters-filter-patrolled-label": "Label for the filter for showing patrolled edits",
-       "rcfilters-filter-patrolled-description": "Label for the filter showing patrolled edits",
-       "rcfilters-filter-unpatrolled-label": "Label for the filter for showing unpatrolled edits",
-       "rcfilters-filter-unpatrolled-description": "Description for the filter for showing unpatrolled edits",
+       "rcfilters-filter-reviewstatus-manual-description": "Description for the filter showing manually patrolled edits",
+       "rcfilters-filter-reviewstatus-manual-label": "Label for the filter showing manually patrolled edits",
+       "rcfilters-filter-reviewstatus-auto-description": "Description for the filter showing automatically patrolled edits",
+       "rcfilters-filter-reviewstatus-auto-label": "Label for the filter showing automatically patrolled edits",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Description for the filter for showing unpatrolled edits",
+       "rcfilters-filter-reviewstatus-unpatrolled-label": "Label for the filter for showing unpatrolled edits",
        "rcfilters-filtergroup-significance": "Title for the filter group for edit significance.\n{{Identical|Significance}}",
        "rcfilters-filter-minor-label": "Label for the filter for showing edits marked as minor.",
        "rcfilters-filter-minor-description": "Description for the filter for showing edits marked as minor.",
        "apisandbox-dynamic-error-exists": "Displayed as an error message from JavaScript when trying to add a new arbitrary parameter with a name that already exists. Parameters:\n* $1 - Parameter name that failed.",
        "apisandbox-deprecated-parameters": "JavaScript button label and fieldset legend for separating deprecated parameters in the UI.",
        "apisandbox-fetch-token": "Label for the button that fetches a CSRF token.",
 -      "apisandbox-add-multi": "Label for the button to add another value to a field that accepts multiple values",
 +      "apisandbox-add-multi": "Label for the button to add another value to a field that accepts multiple values\n{{Identical|Add}}",
        "apisandbox-submit-invalid-fields-title": "Title for a JavaScript error message when fields are invalid.",
        "apisandbox-submit-invalid-fields-message": "Content for a JavaScript error message when fields are invalid.",
        "apisandbox-results": "JavaScript tab label for the tab displaying the API query results.\n{{Identical|Result}}",