Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
58.07% |
187 / 322 |
|
13.33% |
2 / 15 |
CRAP | |
0.00% |
0 / 1 |
ApiEchoNotifications | |
58.07% |
187 / 322 |
|
13.33% |
2 / 15 |
658.69 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
64.29% |
18 / 28 |
|
0.00% |
0 / 1 |
20.70 | |||
getLocalNotifications | |
70.21% |
33 / 47 |
|
0.00% |
0 / 1 |
17.47 | |||
getSectionPropList | |
63.64% |
7 / 11 |
|
0.00% |
0 / 1 |
2.19 | |||
getPropList | |
30.43% |
14 / 46 |
|
0.00% |
0 / 1 |
127.07 | |||
getPropCount | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
getPropSeenTime | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
makeForeignNotification | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
56 | |||
getForeignQueryParams | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
mergeResults | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
5.05 | |||
mergeList | |
68.75% |
11 / 16 |
|
0.00% |
0 / 1 |
9.95 | |||
mergeCount | |
71.43% |
10 / 14 |
|
0.00% |
0 / 1 |
8.14 | |||
getAllowedParams | |
92.31% |
72 / 78 |
|
0.00% |
0 / 1 |
3.00 | |||
getExamplesMessages | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Notifications\Api; |
4 | |
5 | use ApiBase; |
6 | use ApiQuery; |
7 | use ApiQueryBase; |
8 | use MediaWiki\Config\Config; |
9 | use MediaWiki\Extension\Notifications\AttributeManager; |
10 | use MediaWiki\Extension\Notifications\Bundler; |
11 | use MediaWiki\Extension\Notifications\Controller\NotificationController; |
12 | use MediaWiki\Extension\Notifications\DataOutputFormatter; |
13 | use MediaWiki\Extension\Notifications\ForeignNotifications; |
14 | use MediaWiki\Extension\Notifications\Mapper\NotificationMapper; |
15 | use MediaWiki\Extension\Notifications\Model\Notification; |
16 | use MediaWiki\Extension\Notifications\NotifUser; |
17 | use MediaWiki\Extension\Notifications\SeenTime; |
18 | use MediaWiki\Extension\Notifications\Services; |
19 | use MediaWiki\Title\Title; |
20 | use MediaWiki\User\User; |
21 | use MediaWiki\WikiMap\WikiMap; |
22 | use Wikimedia\ParamValidator\ParamValidator; |
23 | use Wikimedia\ParamValidator\TypeDef\IntegerDef; |
24 | |
25 | class ApiEchoNotifications extends ApiQueryBase { |
26 | use ApiCrossWiki; |
27 | |
28 | /** |
29 | * @var bool |
30 | */ |
31 | protected $crossWikiSummary = false; |
32 | |
33 | /** @var string[] */ |
34 | private $allowedNotifierTypes; |
35 | |
36 | public function __construct( ApiQuery $query, string $moduleName, Config $mainConfig ) { |
37 | parent::__construct( $query, $moduleName, 'not' ); |
38 | $this->allowedNotifierTypes = array_keys( $mainConfig->get( 'EchoNotifiers' ) ); |
39 | } |
40 | |
41 | public function execute() { |
42 | // To avoid API warning, register the parameter used to bust browser cache |
43 | $this->getMain()->getVal( '_' ); |
44 | |
45 | if ( !$this->getUser()->isRegistered() ) { |
46 | $this->dieWithError( 'apierror-mustbeloggedin-generic', 'login-required' ); |
47 | } |
48 | |
49 | $params = $this->extractRequestParams(); |
50 | |
51 | /* @deprecated */ |
52 | if ( $params['format'] === 'flyout' ) { |
53 | $this->addDeprecation( 'apiwarn-echo-deprecation-flyout', |
54 | 'action=query&meta=notifications¬format=flyout' ); |
55 | } elseif ( $params['format'] === 'html' ) { |
56 | $this->addDeprecation( 'apiwarn-echo-deprecation-html', |
57 | 'action=query&meta=notifications¬format=html' ); |
58 | } |
59 | |
60 | if ( $this->allowCrossWikiNotifications() ) { |
61 | $this->crossWikiSummary = $params['crosswikisummary']; |
62 | } |
63 | |
64 | $results = []; |
65 | if ( in_array( WikiMap::getCurrentWikiId(), $this->getRequestedWikis() ) ) { |
66 | $results[WikiMap::getCurrentWikiId()] = $this->getLocalNotifications( $params ); |
67 | } |
68 | |
69 | if ( $this->getRequestedForeignWikis() ) { |
70 | $foreignResults = $this->getFromForeign(); |
71 | foreach ( $foreignResults as $wiki => $result ) { |
72 | if ( isset( $result['query']['notifications'] ) ) { |
73 | $results[$wiki] = $result['query']['notifications']; |
74 | } |
75 | } |
76 | } |
77 | |
78 | // after getting local & foreign results, merge them all together |
79 | $result = $this->mergeResults( $results, $params ); |
80 | if ( $params['groupbysection'] ) { |
81 | foreach ( $params['sections'] as $section ) { |
82 | if ( in_array( 'list', $params['prop'] ) ) { |
83 | $this->getResult()->setIndexedTagName( $result[$section]['list'], 'notification' ); |
84 | } |
85 | } |
86 | } else { |
87 | if ( in_array( 'list', $params['prop'] ) ) { |
88 | $this->getResult()->setIndexedTagName( $result['list'], 'notification' ); |
89 | } |
90 | } |
91 | $this->getResult()->addValue( 'query', $this->getModuleName(), $result ); |
92 | } |
93 | |
94 | /** |
95 | * @param array $params |
96 | * @return array |
97 | */ |
98 | protected function getLocalNotifications( array $params ) { |
99 | $user = $this->getUser(); |
100 | $prop = $params['prop']; |
101 | $titles = null; |
102 | if ( $params['titles'] ) { |
103 | $titles = array_values( array_filter( array_map( [ Title::class, 'newFromText' ], $params['titles'] ) ) ); |
104 | if ( in_array( '[]', $params['titles'] ) ) { |
105 | $titles[] = null; |
106 | } |
107 | } |
108 | |
109 | $result = []; |
110 | if ( in_array( 'list', $prop ) ) { |
111 | // Group notification results by section |
112 | if ( $params['groupbysection'] ) { |
113 | foreach ( $params['sections'] as $section ) { |
114 | $result[$section] = $this->getSectionPropList( |
115 | $user, $section, $params['filter'], $params['limit'], |
116 | $params[$section . 'continue'], $params['format'], |
117 | $titles, $params[$section . 'unreadfirst'], $params['bundle'], |
118 | $params['notifiertypes'] |
119 | ); |
120 | |
121 | if ( $this->crossWikiSummary ) { |
122 | // insert fake notification for foreign notifications |
123 | $foreignNotification = $this->makeForeignNotification( $user, $params['format'], $section ); |
124 | if ( $foreignNotification ) { |
125 | array_unshift( $result[$section]['list'], $foreignNotification ); |
126 | } |
127 | } |
128 | } |
129 | } else { |
130 | $attributeManager = Services::getInstance()->getAttributeManager(); |
131 | $result = $this->getPropList( |
132 | $user, |
133 | $attributeManager->getUserEnabledEventsBySections( $user, $params['notifiertypes'], |
134 | $params['sections'] ), |
135 | $params['filter'], $params['limit'], $params['continue'], $params['format'], |
136 | $titles, $params['unreadfirst'], $params['bundle'] |
137 | ); |
138 | |
139 | // if exactly 1 section is specified, we consider only that section, otherwise |
140 | // we pass ALL to consider all foreign notifications |
141 | $section = count( $params['sections'] ) === 1 |
142 | ? reset( $params['sections'] ) |
143 | : AttributeManager::ALL; |
144 | if ( $this->crossWikiSummary ) { |
145 | $foreignNotification = $this->makeForeignNotification( $user, $params['format'], $section ); |
146 | if ( $foreignNotification ) { |
147 | array_unshift( $result['list'], $foreignNotification ); |
148 | } |
149 | } |
150 | } |
151 | } |
152 | |
153 | if ( in_array( 'count', $prop ) ) { |
154 | $result = array_merge_recursive( |
155 | $result, |
156 | $this->getPropCount( $user, $params['sections'], $params['groupbysection'] ) |
157 | ); |
158 | } |
159 | |
160 | if ( in_array( 'seenTime', $prop ) ) { |
161 | $result = array_merge_recursive( |
162 | $result, |
163 | $this->getPropSeenTime( $user, $params['sections'], $params['groupbysection'] ) |
164 | ); |
165 | } |
166 | |
167 | return $result; |
168 | } |
169 | |
170 | /** |
171 | * Internal method for getting the property 'list' data for individual section |
172 | * @param User $user |
173 | * @param string $section 'alert' or 'message' |
174 | * @param string[] $filter 'all', 'read' or 'unread' |
175 | * @param int $limit |
176 | * @param string $continue |
177 | * @param string $format |
178 | * @param Title[]|null $titles |
179 | * @param bool $unreadFirst |
180 | * @param bool $bundle |
181 | * @param string[] $notifierTypes |
182 | * @return array |
183 | */ |
184 | protected function getSectionPropList( |
185 | User $user, |
186 | $section, |
187 | $filter, |
188 | $limit, |
189 | $continue, |
190 | $format, |
191 | array $titles = null, |
192 | $unreadFirst = false, |
193 | $bundle = false, |
194 | array $notifierTypes = [ 'web' ] |
195 | ) { |
196 | $attributeManager = Services::getInstance()->getAttributeManager(); |
197 | $sectionEvents = $attributeManager->getUserEnabledEventsBySections( $user, $notifierTypes, [ $section ] ); |
198 | |
199 | if ( !$sectionEvents ) { |
200 | $result = [ |
201 | 'list' => [], |
202 | 'continue' => null |
203 | ]; |
204 | } else { |
205 | $result = $this->getPropList( |
206 | $user, $sectionEvents, $filter, $limit, $continue, $format, $titles, $unreadFirst, $bundle |
207 | ); |
208 | } |
209 | |
210 | return $result; |
211 | } |
212 | |
213 | /** |
214 | * Internal helper method for getting property 'list' data, this is based |
215 | * on the event types specified in the arguments and it could be event types |
216 | * of a set of sections or a single section |
217 | * @param User $user |
218 | * @param string[] $eventTypes |
219 | * @param string[] $filter 'all', 'read' or 'unread' |
220 | * @param int $limit |
221 | * @param string $continue |
222 | * @param string $format |
223 | * @param Title[]|null $titles |
224 | * @param bool $unreadFirst |
225 | * @param bool $bundle |
226 | * @return array |
227 | */ |
228 | protected function getPropList( |
229 | User $user, |
230 | array $eventTypes, |
231 | $filter, |
232 | $limit, |
233 | $continue, |
234 | $format, |
235 | array $titles = null, |
236 | $unreadFirst = false, |
237 | $bundle = false |
238 | ) { |
239 | $result = [ |
240 | 'list' => [], |
241 | 'continue' => null |
242 | ]; |
243 | |
244 | $notifMapper = new NotificationMapper(); |
245 | |
246 | // check if we want both read & unread... |
247 | if ( in_array( 'read', $filter ) && in_array( '!read', $filter ) ) { |
248 | // Prefer unread notifications. We don't care about next offset in this case |
249 | if ( $unreadFirst ) { |
250 | // query for unread notifications past 'continue' (offset) |
251 | $notifs = $notifMapper->fetchUnreadByUser( $user, $limit + 1, $continue, $eventTypes, $titles ); |
252 | |
253 | /* |
254 | * 'continue' has a timestamp & id (to start with, in case |
255 | * there would be multiple events with that same timestamp) |
256 | * Unread notifications should always load first, but may be |
257 | * older than read ones, but we can work with current |
258 | * 'continue' format: |
259 | * * if there's no continue, first load unread notifications |
260 | * * if there's a continue, fetch unread notifications first |
261 | * * if there are no unread ones, continue must've been |
262 | * about read notifications: fetch 'em |
263 | * * if there are unread ones but first one doesn't match |
264 | * continue id, it must've been about read notifications: |
265 | * discard unread & fetch read |
266 | */ |
267 | if ( $notifs && $continue ) { |
268 | /** @var Notification $first */ |
269 | $first = reset( $notifs ); |
270 | $continueId = intval( trim( strrchr( $continue, '|' ), '|' ) ); |
271 | if ( $first->getEvent()->getId() !== $continueId ) { |
272 | // notification doesn't match continue id, it must've been |
273 | // about read notifications: discard all unread ones |
274 | $notifs = []; |
275 | } |
276 | } |
277 | |
278 | // If there are less unread notifications than we requested, |
279 | // then fill the result with some read notifications |
280 | $count = count( $notifs ); |
281 | // we need 1 more than $limit, so we can respond 'continue' |
282 | if ( $count <= $limit ) { |
283 | // Query planner should be smart enough that passing a short list of ids to exclude |
284 | // will only visit at most that number of extra rows. |
285 | $mixedNotifs = $notifMapper->fetchByUser( |
286 | $user, |
287 | $limit - $count + 1, |
288 | // if there were unread notifications, 'continue' was for |
289 | // unread notifications and we should start fetching read |
290 | // notifications from start |
291 | $count > 0 ? null : $continue, |
292 | $eventTypes, |
293 | array_keys( $notifs ), |
294 | $titles |
295 | ); |
296 | foreach ( $mixedNotifs as $notif ) { |
297 | $notifs[$notif->getEvent()->getId()] = $notif; |
298 | } |
299 | } |
300 | } else { |
301 | $notifs = $notifMapper->fetchByUser( $user, $limit + 1, $continue, $eventTypes, [], $titles ); |
302 | } |
303 | } elseif ( in_array( 'read', $filter ) ) { |
304 | $notifs = $notifMapper->fetchReadByUser( $user, $limit + 1, $continue, $eventTypes, $titles ); |
305 | } else { |
306 | // = if ( in_array( '!read', $filter ) ) { |
307 | $notifs = $notifMapper->fetchUnreadByUser( $user, $limit + 1, $continue, $eventTypes, $titles ); |
308 | } |
309 | |
310 | // get $overfetchedItem before bundling and rendering so that it is not affected by filtering |
311 | /** @var Notification $overfetchedItem */ |
312 | $overfetchedItem = count( $notifs ) > $limit ? array_pop( $notifs ) : null; |
313 | |
314 | $bundler = null; |
315 | if ( $bundle ) { |
316 | $bundler = new Bundler(); |
317 | $notifs = $bundler->bundle( $notifs ); |
318 | } |
319 | |
320 | while ( $notifs !== [] ) { |
321 | /** @var Notification $notif */ |
322 | $notif = array_shift( $notifs ); |
323 | $output = DataOutputFormatter::formatOutput( $notif, $format, $user, $this->getLanguage() ); |
324 | if ( $output !== false ) { |
325 | $result['list'][] = $output; |
326 | } elseif ( $bundler && $notif->getBundledNotifications() ) { |
327 | // when the bundle_base gets filtered out, bundled notifications |
328 | // have to be re-bundled and formatted |
329 | $notifs = array_merge( $bundler->bundle( $notif->getBundledNotifications() ), $notifs ); |
330 | } |
331 | } |
332 | |
333 | // Generate offset if necessary |
334 | if ( $overfetchedItem ) { |
335 | // @todo: what to do with this when fetching from multiple wikis? |
336 | $timestamp = wfTimestamp( TS_UNIX, $overfetchedItem->getTimestamp() ); |
337 | $id = $overfetchedItem->getEvent()->getId(); |
338 | $result['continue'] = $timestamp . '|' . $id; |
339 | } |
340 | |
341 | return $result; |
342 | } |
343 | |
344 | /** |
345 | * Internal helper method for getting property 'count' data |
346 | * @param User $user |
347 | * @param string[] $sections |
348 | * @param bool $groupBySection |
349 | * @return array |
350 | */ |
351 | protected function getPropCount( User $user, array $sections, $groupBySection ) { |
352 | $result = []; |
353 | $notifUser = NotifUser::newFromUser( $user ); |
354 | $global = $this->crossWikiSummary ? 'preference' : false; |
355 | |
356 | $totalRawCount = 0; |
357 | foreach ( $sections as $section ) { |
358 | $rawCount = $notifUser->getNotificationCount( $section, $global ); |
359 | if ( $groupBySection ) { |
360 | $result[$section]['rawcount'] = $rawCount; |
361 | $result[$section]['count'] = NotificationController::formatNotificationCount( $rawCount ); |
362 | } |
363 | $totalRawCount += $rawCount; |
364 | } |
365 | $result['rawcount'] = $totalRawCount; |
366 | $result['count'] = NotificationController::formatNotificationCount( $totalRawCount ); |
367 | |
368 | return $result; |
369 | } |
370 | |
371 | /** |
372 | * Internal helper method for getting property 'seenTime' data |
373 | * @param User $user |
374 | * @param string[] $sections |
375 | * @param bool $groupBySection |
376 | * @return array |
377 | */ |
378 | protected function getPropSeenTime( User $user, array $sections, $groupBySection ) { |
379 | $result = []; |
380 | $seenTimeHelper = SeenTime::newFromUser( $user ); |
381 | |
382 | if ( $groupBySection ) { |
383 | foreach ( $sections as $section ) { |
384 | $result[$section]['seenTime'] = $seenTimeHelper->getTime( $section, TS_ISO_8601 ); |
385 | } |
386 | } else { |
387 | $result['seenTime'] = []; |
388 | foreach ( $sections as $section ) { |
389 | $result['seenTime'][$section] = $seenTimeHelper->getTime( $section, TS_ISO_8601 ); |
390 | } |
391 | } |
392 | |
393 | return $result; |
394 | } |
395 | |
396 | /** |
397 | * Build and format a "fake" notification to represent foreign notifications. |
398 | * @param User $user |
399 | * @param string $format |
400 | * @param string $section |
401 | * @return array|false A formatted notification, or false if there are no foreign notifications |
402 | */ |
403 | protected function makeForeignNotification( |
404 | User $user, |
405 | $format, |
406 | $section = AttributeManager::ALL |
407 | ) { |
408 | $wikis = $this->getForeignNotifications()->getWikis( $section ); |
409 | $count = $this->getForeignNotifications()->getCount( $section ); |
410 | $maxTimestamp = $this->getForeignNotifications()->getTimestamp( $section ); |
411 | $timestampsByWiki = []; |
412 | foreach ( $wikis as $wiki ) { |
413 | $timestampsByWiki[$wiki] = $this->getForeignNotifications()->getWikiTimestamp( $wiki, $section ); |
414 | } |
415 | |
416 | if ( $count === 0 || $wikis === [] ) { |
417 | return false; |
418 | } |
419 | |
420 | // Sort wikis by timestamp, in descending order (newest first) |
421 | usort( $wikis, static function ( $a, $b ) use ( $section, $timestampsByWiki ) { |
422 | return (int)$timestampsByWiki[$b]->getTimestamp( TS_UNIX ) |
423 | - (int)$timestampsByWiki[$a]->getTimestamp( TS_UNIX ); |
424 | } ); |
425 | |
426 | $row = (object)[ |
427 | 'event_id' => -1, |
428 | 'event_type' => 'foreign', |
429 | 'event_variant' => null, |
430 | 'event_agent_id' => $user->getId(), |
431 | 'event_agent_ip' => null, |
432 | 'event_page_id' => null, |
433 | 'event_extra' => serialize( [ |
434 | 'section' => $section ?: 'all', |
435 | 'wikis' => $wikis, |
436 | 'count' => $count |
437 | ] ), |
438 | 'event_deleted' => 0, |
439 | |
440 | 'notification_user' => $user->getId(), |
441 | 'notification_timestamp' => $maxTimestamp, |
442 | 'notification_read_timestamp' => null, |
443 | 'notification_bundle_hash' => md5( 'bogus' ), |
444 | ]; |
445 | |
446 | // Format output like any other notification |
447 | $notif = Notification::newFromRow( $row ); |
448 | $output = DataOutputFormatter::formatOutput( $notif, $format, $user, $this->getLanguage() ); |
449 | |
450 | // Add cross-wiki-specific data |
451 | $output['section'] = $section ?: 'all'; |
452 | $output['count'] = $count; |
453 | $output['sources'] = ForeignNotifications::getApiEndpoints( $wikis ); |
454 | // Add timestamp information |
455 | foreach ( $output['sources'] as $wiki => &$data ) { |
456 | $data['ts'] = $timestampsByWiki[$wiki]->getTimestamp( TS_ISO_8601 ); |
457 | } |
458 | return $output; |
459 | } |
460 | |
461 | protected function getForeignQueryParams() { |
462 | $params = $this->getRequest()->getValues(); |
463 | |
464 | // don't request cross-wiki notification summaries |
465 | unset( $params['notcrosswikisummary'] ); |
466 | |
467 | return $params; |
468 | } |
469 | |
470 | /** |
471 | * @param array[] $results |
472 | * @param array $params |
473 | * @return array |
474 | */ |
475 | protected function mergeResults( array $results, array $params ) { |
476 | $primary = array_shift( $results ); |
477 | if ( !$primary ) { |
478 | $primary = []; |
479 | } |
480 | |
481 | if ( in_array( 'list', $params['prop'] ) ) { |
482 | $primary = $this->mergeList( $primary, $results, $params['groupbysection'] ); |
483 | } |
484 | |
485 | if ( in_array( 'count', $params['prop'] ) && !$this->crossWikiSummary ) { |
486 | // if crosswiki data was requested, the count in $primary |
487 | // is accurate already |
488 | // otherwise, we'll want to combine counts for all wikis |
489 | $primary = $this->mergeCount( $primary, $results, $params['groupbysection'] ); |
490 | } |
491 | |
492 | return $primary; |
493 | } |
494 | |
495 | /** |
496 | * @param array $primary |
497 | * @param array[] $results |
498 | * @param bool $groupBySection |
499 | * @return array |
500 | */ |
501 | protected function mergeList( array $primary, array $results, $groupBySection ) { |
502 | // sort all notifications by timestamp: most recent first |
503 | $sort = static function ( $a, $b ) { |
504 | return $a['timestamp']['utcunix'] - $b['timestamp']['utcunix']; |
505 | }; |
506 | |
507 | if ( $groupBySection ) { |
508 | foreach ( AttributeManager::$sections as $section ) { |
509 | if ( !isset( $primary[$section]['list'] ) ) { |
510 | $primary[$section]['list'] = []; |
511 | } |
512 | foreach ( $results as $result ) { |
513 | $primary[$section]['list'] = array_merge( $primary[$section]['list'], $result[$section]['list'] ); |
514 | } |
515 | usort( $primary[$section]['list'], $sort ); |
516 | } |
517 | } else { |
518 | if ( !isset( $primary['list'] ) || !is_array( $primary['list'] ) ) { |
519 | $primary['list'] = []; |
520 | } |
521 | foreach ( $results as $result ) { |
522 | $primary['list'] = array_merge( $primary['list'], $result['list'] ); |
523 | } |
524 | usort( $primary['list'], $sort ); |
525 | } |
526 | |
527 | return $primary; |
528 | } |
529 | |
530 | /** |
531 | * @param array $primary |
532 | * @param array[] $results |
533 | * @param bool $groupBySection |
534 | * @return array |
535 | */ |
536 | protected function mergeCount( array $primary, array $results, $groupBySection ) { |
537 | if ( $groupBySection ) { |
538 | foreach ( AttributeManager::$sections as $section ) { |
539 | if ( !isset( $primary[$section]['rawcount'] ) ) { |
540 | $primary[$section]['rawcount'] = 0; |
541 | } |
542 | foreach ( $results as $result ) { |
543 | $primary[$section]['rawcount'] += $result[$section]['rawcount']; |
544 | } |
545 | $primary[$section]['count'] = NotificationController::formatNotificationCount( |
546 | $primary[$section]['rawcount'] ); |
547 | } |
548 | } |
549 | |
550 | if ( !isset( $primary['rawcount'] ) ) { |
551 | $primary['rawcount'] = 0; |
552 | } |
553 | foreach ( $results as $result ) { |
554 | // regardless of groupbysection, totals are always included |
555 | $primary['rawcount'] += $result['rawcount']; |
556 | } |
557 | $primary['count'] = NotificationController::formatNotificationCount( $primary['rawcount'] ); |
558 | |
559 | return $primary; |
560 | } |
561 | |
562 | public function getAllowedParams() { |
563 | $sections = AttributeManager::$sections; |
564 | |
565 | $params = $this->getCrossWikiParams() + [ |
566 | 'filter' => [ |
567 | ParamValidator::PARAM_ISMULTI => true, |
568 | ParamValidator::PARAM_DEFAULT => 'read|!read', |
569 | ParamValidator::PARAM_TYPE => [ |
570 | 'read', |
571 | '!read', |
572 | ], |
573 | ], |
574 | 'prop' => [ |
575 | ParamValidator::PARAM_ISMULTI => true, |
576 | ParamValidator::PARAM_TYPE => [ |
577 | 'list', |
578 | 'count', |
579 | 'seenTime', |
580 | ], |
581 | ParamValidator::PARAM_DEFAULT => 'list', |
582 | ], |
583 | 'sections' => [ |
584 | ParamValidator::PARAM_DEFAULT => implode( '|', $sections ), |
585 | ParamValidator::PARAM_TYPE => $sections, |
586 | ParamValidator::PARAM_ISMULTI => true, |
587 | ], |
588 | 'groupbysection' => [ |
589 | ParamValidator::PARAM_TYPE => 'boolean', |
590 | ParamValidator::PARAM_DEFAULT => false, |
591 | ], |
592 | 'format' => [ |
593 | ParamValidator::PARAM_TYPE => [ |
594 | 'model', |
595 | 'special', |
596 | // @deprecated |
597 | 'flyout', |
598 | // @deprecated |
599 | 'html', |
600 | ], |
601 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
602 | ], |
603 | 'limit' => [ |
604 | ParamValidator::PARAM_TYPE => 'limit', |
605 | ParamValidator::PARAM_DEFAULT => 20, |
606 | IntegerDef::PARAM_MIN => 1, |
607 | IntegerDef::PARAM_MAX => ApiBase::LIMIT_SML1, |
608 | IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_SML2, |
609 | ], |
610 | 'continue' => [ |
611 | ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', |
612 | ], |
613 | 'unreadfirst' => [ |
614 | ParamValidator::PARAM_TYPE => 'boolean', |
615 | ParamValidator::PARAM_DEFAULT => false, |
616 | ], |
617 | 'titles' => [ |
618 | ParamValidator::PARAM_ISMULTI => true, |
619 | ], |
620 | 'bundle' => [ |
621 | ParamValidator::PARAM_TYPE => 'boolean', |
622 | ParamValidator::PARAM_DEFAULT => false, |
623 | ], |
624 | 'notifiertypes' => [ |
625 | ParamValidator::PARAM_TYPE => $this->allowedNotifierTypes, |
626 | ParamValidator::PARAM_ISMULTI => true, |
627 | ParamValidator::PARAM_DEFAULT => 'web', |
628 | ], |
629 | ]; |
630 | foreach ( $sections as $section ) { |
631 | $params[$section . 'continue'] = null; |
632 | $params[$section . 'unreadfirst'] = [ |
633 | ParamValidator::PARAM_TYPE => 'boolean', |
634 | ParamValidator::PARAM_DEFAULT => false, |
635 | ]; |
636 | } |
637 | |
638 | if ( $this->allowCrossWikiNotifications() ) { |
639 | $params += [ |
640 | // create "x notifications from y wikis" notification bundle & |
641 | // include unread counts from other wikis in prop=count results |
642 | 'crosswikisummary' => [ |
643 | ParamValidator::PARAM_TYPE => 'boolean', |
644 | ParamValidator::PARAM_DEFAULT => false, |
645 | ], |
646 | ]; |
647 | } |
648 | |
649 | return $params; |
650 | } |
651 | |
652 | /** |
653 | * @see ApiBase::getExamplesMessages() |
654 | * @return array |
655 | */ |
656 | protected function getExamplesMessages() { |
657 | return [ |
658 | 'action=query&meta=notifications' |
659 | => 'apihelp-query+notifications-example-1', |
660 | 'action=query&meta=notifications¬prop=count¬sections=alert|message¬groupbysection=1' |
661 | => 'apihelp-query+notifications-example-2', |
662 | 'action=query&meta=notifications¬notifiertypes=email' |
663 | => 'apihelp-query+notifications-example-3', |
664 | ]; |
665 | } |
666 | |
667 | public function getHelpUrls() { |
668 | return 'https://www.mediawiki.org/wiki/Special:MyLanguage/Echo_(Notifications)/API'; |
669 | } |
670 | } |