Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.27% covered (warning)
68.27%
71 / 104
36.36% covered (danger)
36.36%
4 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
FRUserCounters
68.27% covered (warning)
68.27%
71 / 104
36.36% covered (danger)
36.36%
4 / 11
77.40
0.00% covered (danger)
0.00%
0 / 1
 getUserParams
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getParams
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 setUnitializedFields
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 fetchParamsRow
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 saveUserParams
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 deleteUserParams
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 mergeUserParams
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 incCount
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 flattenParams
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 expandParams
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 updateUserParams
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
1<?php
2
3use MediaWiki\MediaWikiServices;
4use MediaWiki\Title\Title;
5use MediaWiki\User\UserIdentity;
6
7/**
8 * Class containing utility functions for per-user stats
9 */
10class FRUserCounters {
11    /**
12     * Get params for a user ID
13     * @param int $userId
14     * @param int $flags One of the IDBAccessObject::READ_… constants
15     * @return array
16     */
17    public static function getUserParams( $userId, $flags = 0 ) {
18        $p = [];
19        $row = self::fetchParamsRow( $userId, $flags );
20        if ( $row ) {
21            $p = self::expandParams( $row->frp_user_params );
22        }
23        self::setUnitializedFields( $p );
24        return $p;
25    }
26
27    /**
28     * Get params for a user
29     * @param UserIdentity $user
30     * @return array|null
31     * @suppress PhanUndeclaredProperty
32     */
33    public static function getParams( UserIdentity $user ) {
34        if ( !$user->isRegistered() ) {
35            return null;
36        }
37
38        if ( !isset( $user->fr_user_params ) ) { // process cache...
39            $user->fr_user_params = self::getUserParams( $user->getId() );
40        }
41        return $user->fr_user_params;
42    }
43
44    /**
45     * Initializes unset param fields to their starting values
46     * @param array &$p
47     */
48    private static function setUnitializedFields( array &$p ) {
49        if ( !isset( $p['uniqueContentPages'] ) ) {
50            $p['uniqueContentPages'] = [];
51        }
52        if ( !isset( $p['totalContentEdits'] ) ) {
53            $p['totalContentEdits'] = 0;
54        }
55        if ( !isset( $p['editComments'] ) ) {
56            $p['editComments'] = 0;
57        }
58        if ( !isset( $p['revertedEdits'] ) ) {
59            $p['revertedEdits'] = 0;
60        }
61    }
62
63    /**
64     * Get the params row for a user
65     * @param int $userId
66     * @param int $flags One of the IDBAccessObject::READ_… constants
67     * @return stdClass|false
68     */
69    private static function fetchParamsRow( $userId, $flags = 0 ) {
70        if ( $flags & IDBAccessObject::READ_LATEST ) {
71            $db = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
72        } else {
73            $db = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
74        }
75        return $db->newSelectQueryBuilder()
76            ->select( 'frp_user_params' )
77            ->from( 'flaggedrevs_promote' )
78            ->where( [ 'frp_user_id' => $userId ] )
79            ->recency( $flags )
80            ->caller( __METHOD__ )
81            ->fetchRow();
82    }
83
84    /**
85     * Save params for a user
86     * @param int $userId
87     * @param array $params
88     */
89    public static function saveUserParams( $userId, array $params ) {
90        $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
91
92        $dbw->newReplaceQueryBuilder()
93            ->replaceInto( 'flaggedrevs_promote' )
94            ->uniqueIndexFields( 'frp_user_id' )
95            ->row( [
96                'frp_user_id' => $userId,
97                'frp_user_params' => self::flattenParams( $params )
98            ] )
99            ->caller( __METHOD__ )
100            ->execute();
101    }
102
103    /**
104     * @param UserIdentity $user
105     */
106    public static function deleteUserParams( UserIdentity $user ) {
107        $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
108
109        $dbw->newDeleteQueryBuilder()
110            ->deleteFrom( 'flaggedrevs_promote' )
111            ->where( [ 'frp_user_id' => $user->getId() ] )
112            ->caller( __METHOD__ )
113            ->execute();
114    }
115
116    /**
117     * @param UserIdentity $oldUser
118     * @param UserIdentity $newUser
119     */
120    public static function mergeUserParams( UserIdentity $oldUser, UserIdentity $newUser ) {
121        $oldParams = self::getUserParams( $oldUser->getId(), IDBAccessObject::READ_LATEST );
122        $newParams = self::getUserParams( $newUser->getId(), IDBAccessObject::READ_LATEST );
123        $newParams['uniqueContentPages'] = array_unique( array_merge(
124            $newParams['uniqueContentPages'],
125            $oldParams['uniqueContentPages']
126        ) );
127        sort( $newParams['uniqueContentPages'] );
128        $newParams['totalContentEdits'] += $oldParams['totalContentEdits'];
129        $newParams['editComments'] += $oldParams['editComments'];
130        $newParams['revertedEdits'] += $oldParams['revertedEdits'];
131
132        self::saveUserParams( $newUser->getId(), $newParams );
133    }
134
135    /**
136     * Increments a count for a user
137     * @param int $userId
138     * @param string $param Count name
139     */
140    public static function incCount( $userId, $param ) {
141        $p = self::getUserParams( $userId, IDBAccessObject::READ_EXCLUSIVE );
142        if ( !isset( $p[$param] ) ) {
143            $p[$param] = 0;
144        }
145        $p[$param]++;
146        self::saveUserParams( $userId, $p );
147    }
148
149    /**
150     * Flatten params for a user for DB storage
151     * Note: param values must be integers
152     * @param array $params
153     * @return string
154     */
155    private static function flattenParams( array $params ) {
156        $flatRows = [];
157        foreach ( $params as $key => $value ) {
158            if ( str_contains( $key, '=' ) || str_contains( $key, "\n" ) ) {
159                throw new InvalidArgumentException( "flattenParams() - key cannot use '=' or newline" );
160            }
161            if ( $key === 'uniqueContentPages' ) { // list
162                $value = implode( ',', array_map( 'intval', $value ) );
163            } else {
164                $value = intval( $value );
165            }
166            $flatRows[] = trim( $key ) . '=' . $value;
167        }
168        return implode( "\n", $flatRows );
169    }
170
171    /**
172     * Expand params for a user from DB storage
173     * @param string $flatPars
174     * @return array
175     */
176    private static function expandParams( $flatPars ) {
177        $p = []; // init
178        $flatPars = explode( "\n", trim( $flatPars ) );
179        foreach ( $flatPars as $pair ) {
180            $m = explode( '=', trim( $pair ), 2 );
181            $key = $m[0];
182            $value = $m[1] ?? '';
183            if ( $key === 'uniqueContentPages' ) { // list
184                $value = ( $value === '' )
185                    ? [] // explode() would make [ 0 => '' ]
186                    : array_map( 'intval', explode( ',', $value ) );
187            } else {
188                $value = intval( $value );
189            }
190            $p[$key] = $value;
191        }
192        return $p;
193    }
194
195    /**
196     * Update users params array for a user on edit
197     * @param array &$p user params
198     * @param Title $title the article just edited
199     * @param string $summary edit summary
200     * @return bool anything changed
201     */
202    public static function updateUserParams( array &$p, Title $title, $summary ) {
203        global $wgFlaggedRevsAutoconfirm, $wgFlaggedRevsAutopromote;
204        # Update any special counters for non-null revisions
205        $changed = false;
206        if ( $title->isContentPage() ) {
207            $pages = $p['uniqueContentPages']; // page IDs
208            # Don't let this get bloated for no reason
209            $maxUniquePages = 50; // some flexibility
210            if ( is_array( $wgFlaggedRevsAutoconfirm ) &&
211                $wgFlaggedRevsAutoconfirm['uniqueContentPages'] > $maxUniquePages
212            ) {
213                $maxUniquePages = $wgFlaggedRevsAutoconfirm['uniqueContentPages'];
214            }
215            if ( is_array( $wgFlaggedRevsAutopromote ) &&
216                $wgFlaggedRevsAutopromote['uniqueContentPages'] > $maxUniquePages
217            ) {
218                $maxUniquePages = $wgFlaggedRevsAutopromote['uniqueContentPages'];
219            }
220            if ( count( $pages ) < $maxUniquePages // limit the size of this
221                && $title->getId()
222                && !in_array( $title->getId(), $pages )
223            ) {
224                $pages[] = $title->getId();
225                $p['uniqueContentPages'] = $pages;
226            }
227            $p['totalContentEdits'] += 1;
228            $changed = true;
229        }
230        // Record non-automatic summary tally
231        if ( !preg_match( '/^\/\*.*\*\/$/', $summary ) ) {
232            $p['editComments'] += 1;
233            $changed = true;
234        }
235        return $changed;
236    }
237}