Special:RC filter: userExpLevel
authorStephane Bisson <sbisson@wikimedia.org>
Tue, 15 Nov 2016 21:35:54 +0000 (16:35 -0500)
committerStephane Bisson <sbisson@wikimedia.org>
Wed, 21 Dec 2016 16:24:15 +0000 (11:24 -0500)
Allows filtering changes based on user
experience level. Supports the following
levels: 'newcomer', 'learner', 'experienced'

Will be used by the ERI project.

Bug: T149637
Change-Id: Ib2ac92925836ce2f3706d898968538aa18d14d5d

includes/DefaultSettings.php
includes/specials/SpecialRecentchanges.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php

index f419b77..3274480 100644 (file)
@@ -8460,6 +8460,23 @@ $wgCSPFalsePositiveUrls = [
        'https://d5p.de17a.com' => true,
 ];
 
+/**
+ * The following variables define 3 user experience levels:
+ *
+ *  - newcomer: has not yet reached the 'learner' level
+ *
+ *  - learner: has at least $wgLearnerEdits and has been
+ *             a member for $wgLearnerMemberSince days
+ *             but has not yet reached the 'experienced' level.
+ *
+ *  - experienced: has at least $wgExperiencedUserEdits edits and
+ *                 has been a member for $wgExperiencedUserMemberSince days.
+ */
+$wgLearnerEdits = 10;
+$wgLearnerMemberSince = 4; # days
+$wgExperiencedUserEdits = 500;
+$wgExperiencedUserMemberSince = 30; # days
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 8530eb1..1ce61e3 100644 (file)
@@ -91,6 +91,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                $opts->add( 'categories_any', false );
                $opts->add( 'tagfilter', '' );
 
+               $opts->add( 'userExpLevel', 'all' );
+
                return $opts;
        }
 
@@ -240,6 +242,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        $opts['tagfilter']
                );
 
+               $this->filterOnUserExperienceLevel( $tables, $conds, $join_conds, $opts );
+
                if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
                        $opts )
                ) {
@@ -803,4 +807,65 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                return 60 * 5;
        }
 
+       function filterOnUserExperienceLevel( &$tables, &$conds, &$join_conds, $opts ) {
+               global $wgLearnerEdits,
+                          $wgExperiencedUserEdits,
+                          $wgLearnerMemberSince,
+                          $wgExperiencedUserMemberSince;
+
+               $selectedExpLevels = explode( ',', strtolower( $opts['userExpLevel'] ) );
+               // remove values that are not recognized
+               $selectedExpLevels = array_intersect(
+                       $selectedExpLevels,
+                       [ 'newcomer', 'learner', 'experienced' ]
+               );
+               sort( $selectedExpLevels );
+
+               if ( $selectedExpLevels ) {
+                       $tables[] = 'user';
+                       $join_conds['user'] = [ 'LEFT JOIN', 'rc_user = user_id' ];
+
+                       $now = time();
+                       $secondsPerDay = 86400;
+                       $learnerCutoff = $now - $wgLearnerMemberSince * $secondsPerDay;
+                       $experiencedUserCutoff = $now - $wgExperiencedUserMemberSince * $secondsPerDay;
+
+                       $aboveNewcomer = $this->getDB()->makeList(
+                               [
+                                       'user_editcount >= ' . intval( $wgLearnerEdits ),
+                                       'user_registration <= ' . $this->getDB()->timestamp( $learnerCutoff ),
+                               ],
+                               IDatabase::LIST_AND
+                       );
+
+                       $aboveLearner = $this->getDB()->makeList(
+                               [
+                                       'user_editcount >= ' . intval( $wgExperiencedUserEdits ),
+                                       'user_registration <= ' . $this->getDB()->timestamp( $experiencedUserCutoff ),
+                               ],
+                               IDatabase::LIST_AND
+                       );
+
+                       if ( $selectedExpLevels === [ 'newcomer' ] ) {
+                               $conds[] =  "NOT ( $aboveNewcomer )";
+                       } elseif ( $selectedExpLevels === [ 'learner' ] ) {
+                               $conds[] = $this->getDB()->makeList(
+                                       [ $aboveNewcomer, "NOT ( $aboveLearner )" ],
+                                       IDatabase::LIST_AND
+                               );
+                       } elseif ( $selectedExpLevels === [ 'experienced' ] ) {
+                               $conds[] = $aboveLearner;
+                       } elseif ( $selectedExpLevels === [ 'learner', 'newcomer' ] ) {
+                               $conds[] = "NOT ( $aboveLearner )";
+                       } elseif ( $selectedExpLevels === [ 'experienced', 'newcomer' ] ) {
+                               $conds[] = $this->getDB()->makeList(
+                                       [ "NOT ( $aboveNewcomer )", $aboveLearner ],
+                                       IDatabase::LIST_OR
+                               );
+                       } elseif ( $selectedExpLevels === [ 'experienced', 'learner' ] ) {
+                               $conds[] = $aboveNewcomer;
+                       }
+               }
+       }
+
 }
index c11e6a3..ab92aee 100644 (file)
@@ -393,4 +393,136 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                        $user
                );
        }
+
+       public function testFilterUserExpLevel() {
+               $this->setMwGlobals( [
+                       'wgLearnerEdits' => 10,
+                       'wgLearnerMemberSince' => 4,
+                       'wgExperiencedUserEdits' => 500,
+                       'wgExperiencedUserMemberSince' => 30,
+               ] );
+
+               $this->createUsers( [
+                       'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
+                       'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
+                       'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
+                       'Learner1' => [ 'edits' => 15, 'days' => 10 ],
+                       'Learner2' => [ 'edits' => 450, 'days' => 20 ],
+                       'Learner3' => [ 'edits' => 460, 'days' => 33 ],
+                       'Learner4' => [ 'edits' => 525, 'days' => 28 ],
+                       'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
+               ] );
+
+               // newcomers only
+               $this->assertArrayEquals(
+                       [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'newcomer' ] )
+               );
+
+               // newcomers and learner
+               $this->assertArrayEquals(
+                       [
+                               'Newcomer1', 'Newcomer2', 'Newcomer3',
+                               'Learner1', 'Learner2', 'Learner3', 'Learner4',
+                       ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'newcomer,learner' ] )
+               );
+
+               // newcomers and more learner
+               $this->assertArrayEquals(
+                       [
+                               'Newcomer1', 'Newcomer2', 'Newcomer3',
+                               'Experienced1',
+                       ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'newcomer,experienced' ] )
+               );
+
+               // learner only
+               $this->assertArrayEquals(
+                       [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'learner' ] )
+               );
+
+               // more experienced only
+               $this->assertArrayEquals(
+                       [ 'Experienced1' ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'experienced' ] )
+               );
+
+               // learner and more experienced
+               $this->assertArrayEquals(
+                       [
+                               'Learner1', 'Learner2', 'Learner3', 'Learner4',
+                               'Experienced1',
+                       ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'learner,experienced' ] )
+               );
+
+               // newcomers, learner, and more experienced
+               $this->assertArrayEquals(
+                       [
+                               'Newcomer1', 'Newcomer2', 'Newcomer3',
+                               'Learner1', 'Learner2', 'Learner3', 'Learner4',
+                               'Experienced1',
+                       ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'newcomer,learner,experienced' ] )
+               );
+
+               // 'all'
+               $this->assertArrayEquals(
+                       [
+                               'Newcomer1', 'Newcomer2', 'Newcomer3',
+                               'Learner1', 'Learner2', 'Learner3', 'Learner4',
+                               'Experienced1',
+                       ],
+                       $this->fetchUsers( [ 'userExpLevel' => 'all' ] )
+               );
+       }
+
+       private function createUsers( $specs ) {
+               $dbw = wfGetDB( DB_MASTER );
+               foreach ( $specs as $name => $spec ) {
+                       User::createNew(
+                               $name,
+                               [
+                                       'editcount' => $spec['edits'],
+                                       'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'] ) ),
+                                       'email' => 'ut',
+                               ]
+                       );
+               }
+       }
+
+       private function fetchUsers( $filters ) {
+               $specialRC = new SpecialRecentChanges();
+
+               $tables = [];
+               $conds = [];
+               $join_conds = [];
+
+               $specialRC->filterOnUserExperienceLevel(
+                       $tables,
+                       $conds,
+                       $join_conds,
+                       $filters
+               );
+
+               $result = wfGetDB( DB_MASTER )->select(
+                       'user',
+                       'user_name',
+                       array_filter( $conds ) + [ 'user_email' => 'ut' ]
+               );
+
+               $usernames = [];
+               foreach ( $result as $row ) {
+                       $usernames[] = $row->user_name;
+               }
+
+               return $usernames;
+       }
+
+       private function daysAgo( $days ) {
+               $secondsPerDay = 86400;
+               return time() - $days * $secondsPerDay;
+       }
 }