Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
80.52% |
62 / 77 |
|
14.29% |
1 / 7 |
CRAP | |
0.00% |
0 / 1 |
UserLocator | |
81.58% |
62 / 76 |
|
14.29% |
1 / 7 |
37.01 | |
0.00% |
0 / 1 |
locateUsersWatchingTitle | |
90.00% |
18 / 20 |
|
0.00% |
0 / 1 |
2.00 | |||
locateTalkPageOwner | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
5.07 | |||
locateUserPageOwner | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
locateEventAgent | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
locateArticleCreator | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
6.40 | |||
getArticleAuthorByArticleId | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
3.00 | |||
locateFromEventExtra | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
7.01 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Notifications; |
4 | |
5 | use BatchRowIterator; |
6 | use Iterator; |
7 | use MediaWiki\Extension\Notifications\Iterator\CallbackIterator; |
8 | use MediaWiki\Extension\Notifications\Model\Event; |
9 | use MediaWiki\MediaWikiServices; |
10 | use MediaWiki\User\User; |
11 | use RecursiveIteratorIterator; |
12 | |
13 | class UserLocator { |
14 | /** |
15 | * Return all users watching the event title. |
16 | * |
17 | * The echo job queue must be enabled to prevent timeouts submitting to |
18 | * heavily watched pages when this is used. |
19 | * |
20 | * @param Event $event |
21 | * @param int $batchSize |
22 | * @return User[]|Iterator<User> |
23 | */ |
24 | public static function locateUsersWatchingTitle( Event $event, $batchSize = 500 ) { |
25 | $title = $event->getTitle(); |
26 | if ( !$title ) { |
27 | return []; |
28 | } |
29 | $provider = MediaWikiServices::getInstance()->getConnectionProvider(); |
30 | $batchRowIt = new BatchRowIterator( |
31 | $provider->getReplicaDatabase( false, 'watchlist' ), |
32 | /* $table = */ 'watchlist', |
33 | /* $primaryKeys = */ [ 'wl_user' ], |
34 | $batchSize |
35 | ); |
36 | $batchRowIt->addConditions( [ |
37 | 'wl_namespace' => $title->getNamespace(), |
38 | 'wl_title' => $title->getDBkey(), |
39 | ] ); |
40 | $batchRowIt->setCaller( __METHOD__ ); |
41 | |
42 | // flatten the result into a stream of rows |
43 | $recursiveIt = new RecursiveIteratorIterator( $batchRowIt ); |
44 | |
45 | // add callback to convert user id to user objects |
46 | $echoCallbackIt = new CallbackIterator( $recursiveIt, static function ( $row ) { |
47 | return User::newFromId( $row->wl_user ); |
48 | } ); |
49 | |
50 | return $echoCallbackIt; |
51 | } |
52 | |
53 | /** |
54 | * If the event occurred on the talk page of a registered |
55 | * user return that user. |
56 | * |
57 | * @param Event $event |
58 | * @return User[] |
59 | */ |
60 | public static function locateTalkPageOwner( Event $event ) { |
61 | $title = $event->getTitle(); |
62 | if ( !$title || $title->getNamespace() !== NS_USER_TALK ) { |
63 | return []; |
64 | } |
65 | |
66 | $user = User::newFromName( $title->getDBkey() ); |
67 | if ( $user && $user->isRegistered() ) { |
68 | return [ $user->getId() => $user ]; |
69 | } |
70 | |
71 | return []; |
72 | } |
73 | |
74 | /** |
75 | * If the event occurred on the user page of a registered |
76 | * user return that user. |
77 | * |
78 | * @param Event $event |
79 | * @return User[] |
80 | */ |
81 | public static function locateUserPageOwner( Event $event ) { |
82 | $title = $event->getTitle(); |
83 | if ( !$title || !$title->inNamespace( NS_USER ) ) { |
84 | return []; |
85 | } |
86 | |
87 | $user = User::newFromName( $title->getDBkey() ); |
88 | if ( $user && $user->isRegistered() ) { |
89 | return [ $user->getId() => $user ]; |
90 | } |
91 | |
92 | return []; |
93 | } |
94 | |
95 | /** |
96 | * Return the event agent |
97 | * |
98 | * @param Event $event |
99 | * @return User[] |
100 | */ |
101 | public static function locateEventAgent( Event $event ) { |
102 | $agent = $event->getAgent(); |
103 | if ( $agent && $agent->isRegistered() ) { |
104 | return [ $agent->getId() => $agent ]; |
105 | } |
106 | |
107 | return []; |
108 | } |
109 | |
110 | /** |
111 | * Return the user that created the first revision of the |
112 | * associated title. |
113 | * |
114 | * @param Event $event |
115 | * @return User[] |
116 | */ |
117 | public static function locateArticleCreator( Event $event ) { |
118 | $title = $event->getTitle(); |
119 | |
120 | if ( !$title || $title->getArticleID() <= 0 ) { |
121 | return []; |
122 | } |
123 | |
124 | $user = self::getArticleAuthorByArticleId( $title->getArticleID() ); |
125 | if ( $user ) { |
126 | // T318523: Don't send page-linked notifications for pages created by bot users. |
127 | if ( $event->getType() === 'page-linked' && $user->isBot() ) { |
128 | return []; |
129 | } |
130 | return [ $user->getId() => $user ]; |
131 | } |
132 | |
133 | return []; |
134 | } |
135 | |
136 | /** |
137 | * @param int $articleId |
138 | * @return User|null |
139 | */ |
140 | public static function getArticleAuthorByArticleId( int $articleId ): ?User { |
141 | $services = MediaWikiServices::getInstance(); |
142 | $dbr = $services->getConnectionProvider()->getReplicaDatabase(); |
143 | $revQuery = $services->getRevisionStore()->getQueryInfo(); |
144 | $res = $dbr->newSelectQueryBuilder() |
145 | ->select( [ 'rev_user' => $revQuery['fields']['rev_user'] ] ) |
146 | ->tables( $revQuery['tables'] ) |
147 | ->where( [ 'rev_page' => $articleId ] ) |
148 | ->orderBy( [ 'rev_timestamp', 'rev_id' ] ) |
149 | ->joinConds( $revQuery['joins'] ) |
150 | ->caller( __METHOD__ ) |
151 | ->fetchRow(); |
152 | if ( !$res || !$res->rev_user ) { |
153 | return null; |
154 | } |
155 | |
156 | return User::newFromId( $res->rev_user ); |
157 | } |
158 | |
159 | /** |
160 | * Fetch user ids from the event extra data. Requires additional |
161 | * parameter. Example $wgEchoNotifications parameter: |
162 | * |
163 | * 'user-locators' => [ [ 'event-extra', 'mentions' ] ], |
164 | * |
165 | * The above will look in the 'mentions' parameter for a user id or |
166 | * array of user ids. It will return all these users as notification |
167 | * targets. |
168 | * |
169 | * @param Event $event |
170 | * @param string[] $keys one or more keys to check for user ids |
171 | * @return User[] |
172 | */ |
173 | public static function locateFromEventExtra( Event $event, array $keys ) { |
174 | $users = []; |
175 | foreach ( $keys as $key ) { |
176 | $userIds = $event->getExtraParam( $key ); |
177 | if ( !$userIds ) { |
178 | continue; |
179 | } |
180 | if ( !is_array( $userIds ) ) { |
181 | $userIds = [ $userIds ]; |
182 | } |
183 | foreach ( $userIds as $userId ) { |
184 | // we shouldn't receive User instances, but allow |
185 | // it for backward compatability |
186 | if ( $userId instanceof User ) { |
187 | if ( !$userId->isRegistered() ) { |
188 | continue; |
189 | } |
190 | $user = $userId; |
191 | } else { |
192 | $user = User::newFromId( $userId ); |
193 | } |
194 | $users[$user->getId()] = $user; |
195 | } |
196 | } |
197 | |
198 | return $users; |
199 | } |
200 | } |
201 | |
202 | class_alias( UserLocator::class, 'EchoUserLocator' ); |