Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
60.00% |
18 / 30 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
UserDatabaseHelper | |
60.00% |
18 / 30 |
|
33.33% |
1 / 3 |
8.30 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
findFirstUserIdForRegistrationTimestamp | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
hasMainspaceEdits | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments; |
4 | |
5 | use MediaWiki\User\UserFactory; |
6 | use MediaWiki\User\UserIdentity; |
7 | use Wikimedia\Rdbms\IConnectionProvider; |
8 | use Wikimedia\Rdbms\SelectQueryBuilder; |
9 | |
10 | /** |
11 | * Helper class for some user-related queries. |
12 | */ |
13 | class UserDatabaseHelper { |
14 | |
15 | private UserFactory $userFactory; |
16 | private IConnectionProvider $connectionProvider; |
17 | |
18 | /** |
19 | * @param UserFactory $userFactory |
20 | * @param IConnectionProvider $connectionProvider For the database with the user table. |
21 | */ |
22 | public function __construct( |
23 | UserFactory $userFactory, |
24 | IConnectionProvider $connectionProvider |
25 | ) { |
26 | $this->userFactory = $userFactory; |
27 | $this->connectionProvider = $connectionProvider; |
28 | } |
29 | |
30 | /** |
31 | * Find the first user_id with a registration date >= $registrationDate. On large wikis this |
32 | * can be a slow operation and should be only used in deferreds and similar |
33 | * non-performance-sensitive places. |
34 | * |
35 | * user_registration is not indexed so filtering or paging based on it is very slow on large |
36 | * wikis. It is monotonic to a very good approximation though, so once we can find the first |
37 | * user_id matching the given registration timestamp, we can filter/page by primary key. |
38 | * @param int|string $registrationTimestamp Registration time in any format known by |
39 | * ConvertibleTimestamp. |
40 | * @return int|null User ID, or null if no user has registered on or after that timestamp. |
41 | */ |
42 | public function findFirstUserIdForRegistrationTimestamp( $registrationTimestamp ): ?int { |
43 | $dbr = $this->connectionProvider->getReplicaDatabase(); |
44 | $registrationTimestamp = $dbr->timestamp( $registrationTimestamp ); |
45 | $userId = $dbr->newSelectQueryBuilder() |
46 | ->field( 'user_id' ) |
47 | ->from( 'user' ) |
48 | ->where( $dbr->expr( 'user_registration', '>=', $registrationTimestamp ) ) |
49 | ->orderBy( 'user_id', SelectQueryBuilder::SORT_ASC ) |
50 | ->caller( __METHOD__ ) |
51 | ->fetchField(); |
52 | return $userId === false ? null : (int)$userId; |
53 | } |
54 | |
55 | /** |
56 | * Performant approximate check for whether the user has any edits in the main namespace. |
57 | * Will return null if the user's first $limit edits are all not in the main namespace. |
58 | * @param UserIdentity $userIdentity |
59 | * @param int $limit |
60 | * @return bool|null |
61 | */ |
62 | public function hasMainspaceEdits( UserIdentity $userIdentity, int $limit = 1000 ): ?bool { |
63 | $user = $this->userFactory->newFromUserIdentity( $userIdentity ); |
64 | $res = $this->connectionProvider->getReplicaDatabase()->newSelectQueryBuilder() |
65 | ->select( 'page_namespace' ) |
66 | ->from( 'revision' ) |
67 | ->join( 'page', null, 'page_id = rev_page' ) |
68 | ->where( [ |
69 | 'rev_actor' => $user->getActorId() |
70 | ] ) |
71 | // Look at the user's oldest edits - arbitrary choice, other than we want the method to be |
72 | // deterministic. Can be done efficiently via the rev_actor_timestamp index. |
73 | ->orderBy( 'rev_timestamp', SelectQueryBuilder::SORT_ASC ) |
74 | ->limit( $limit ) |
75 | ->caller( __METHOD__ ) |
76 | ->fetchFieldValues(); |
77 | $result = array_map( 'intval', $res ); |
78 | if ( in_array( NS_MAIN, $result ) ) { |
79 | return true; |
80 | } |
81 | return count( $result ) === $limit ? null : false; |
82 | } |
83 | |
84 | } |