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