2012-04-27 15:14:24 +00:00
|
|
|
<?php
|
|
|
|
|
2022-10-02 22:44:38 +00:00
|
|
|
namespace MediaWiki\Extension\Notifications\Api;
|
|
|
|
|
|
|
|
use ApiBase;
|
|
|
|
use ApiQuery;
|
|
|
|
use ApiQueryBase;
|
|
|
|
use Bundler;
|
|
|
|
use Config;
|
|
|
|
use EchoAttributeManager;
|
|
|
|
use EchoDataOutputFormatter;
|
|
|
|
use EchoForeignNotifications;
|
|
|
|
use EchoSeenTime;
|
|
|
|
use EchoServices;
|
2022-11-02 03:51:15 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Controller\NotificationController;
|
2022-11-02 20:47:04 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Mapper\NotificationMapper;
|
2022-11-02 21:34:17 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Model\Notification;
|
2022-10-02 22:44:38 +00:00
|
|
|
use MWEchoNotifUser;
|
|
|
|
use Title;
|
|
|
|
use User;
|
|
|
|
use WikiMap;
|
2021-08-13 15:53:55 +00:00
|
|
|
use Wikimedia\ParamValidator\ParamValidator;
|
2022-04-03 20:14:48 +00:00
|
|
|
use Wikimedia\ParamValidator\TypeDef\IntegerDef;
|
2021-08-13 15:53:55 +00:00
|
|
|
|
2018-08-30 01:03:16 +00:00
|
|
|
class ApiEchoNotifications extends ApiQueryBase {
|
|
|
|
use ApiCrossWiki;
|
|
|
|
|
2016-04-21 10:28:32 +00:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
protected $crossWikiSummary = false;
|
2013-12-02 23:11:59 +00:00
|
|
|
|
2021-08-13 15:53:55 +00:00
|
|
|
/** @var string[] */
|
|
|
|
private $allowedNotifierTypes;
|
|
|
|
|
|
|
|
public function __construct( ApiQuery $query, string $moduleName, Config $mainConfig ) {
|
2012-04-27 15:14:24 +00:00
|
|
|
parent::__construct( $query, $moduleName, 'not' );
|
2021-08-13 15:53:55 +00:00
|
|
|
$this->allowedNotifierTypes = array_keys( $mainConfig->get( 'EchoNotifiers' ) );
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function execute() {
|
2013-07-12 22:46:37 +00:00
|
|
|
// To avoid API warning, register the parameter used to bust browser cache
|
|
|
|
$this->getMain()->getVal( '_' );
|
|
|
|
|
2020-12-22 15:36:58 +00:00
|
|
|
if ( !$this->getUser()->isRegistered() ) {
|
2016-11-03 19:16:56 +00:00
|
|
|
$this->dieWithError( 'apierror-mustbeloggedin-generic', 'login-required' );
|
2012-12-07 01:08:33 +00:00
|
|
|
}
|
2012-06-01 10:57:09 +00:00
|
|
|
|
2012-12-07 01:08:33 +00:00
|
|
|
$params = $this->extractRequestParams();
|
2012-04-27 15:14:24 +00:00
|
|
|
|
2016-01-18 06:27:17 +00:00
|
|
|
/* @deprecated */
|
|
|
|
if ( $params['format'] === 'flyout' ) {
|
2018-08-25 10:51:14 +00:00
|
|
|
$this->addDeprecation( 'apiwarn-echo-deprecation-flyout',
|
|
|
|
'action=query&meta=notifications¬format=flyout' );
|
2016-01-18 06:27:17 +00:00
|
|
|
} elseif ( $params['format'] === 'html' ) {
|
2018-08-25 10:51:14 +00:00
|
|
|
$this->addDeprecation( 'apiwarn-echo-deprecation-html',
|
|
|
|
'action=query&meta=notifications¬format=html' );
|
2016-01-18 06:27:17 +00:00
|
|
|
}
|
|
|
|
|
2016-05-26 20:41:25 +00:00
|
|
|
if ( $this->allowCrossWikiNotifications() ) {
|
2016-05-13 20:48:03 +00:00
|
|
|
$this->crossWikiSummary = $params['crosswikisummary'];
|
|
|
|
}
|
2015-11-25 04:07:54 +00:00
|
|
|
|
2016-12-05 18:51:07 +00:00
|
|
|
$results = [];
|
2021-12-21 00:47:31 +00:00
|
|
|
if ( in_array( WikiMap::getCurrentWikiId(), $this->getRequestedWikis() ) ) {
|
|
|
|
$results[WikiMap::getCurrentWikiId()] = $this->getLocalNotifications( $params );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 21:10:04 +00:00
|
|
|
if ( $this->getRequestedForeignWikis() ) {
|
2016-05-26 20:41:25 +00:00
|
|
|
$foreignResults = $this->getFromForeign();
|
|
|
|
foreach ( $foreignResults as $wiki => $result ) {
|
|
|
|
if ( isset( $result['query']['notifications'] ) ) {
|
|
|
|
$results[$wiki] = $result['query']['notifications'];
|
|
|
|
}
|
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// after getting local & foreign results, merge them all together
|
|
|
|
$result = $this->mergeResults( $results, $params );
|
|
|
|
if ( $params['groupbysection'] ) {
|
|
|
|
foreach ( $params['sections'] as $section ) {
|
|
|
|
if ( in_array( 'list', $params['prop'] ) ) {
|
|
|
|
$this->getResult()->setIndexedTagName( $result[$section]['list'], 'notification' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( in_array( 'list', $params['prop'] ) ) {
|
|
|
|
$this->getResult()->setIndexedTagName( $result['list'], 'notification' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->getResult()->addValue( 'query', $this->getModuleName(), $result );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $params
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getLocalNotifications( array $params ) {
|
|
|
|
$user = $this->getUser();
|
|
|
|
$prop = $params['prop'];
|
2016-06-07 18:48:33 +00:00
|
|
|
$titles = null;
|
|
|
|
if ( $params['titles'] ) {
|
2019-02-19 10:42:30 +00:00
|
|
|
$titles = array_values( array_filter( array_map( [ Title::class, 'newFromText' ], $params['titles'] ) ) );
|
2016-06-23 22:32:18 +00:00
|
|
|
if ( in_array( '[]', $params['titles'] ) ) {
|
|
|
|
$titles[] = null;
|
|
|
|
}
|
2016-06-07 18:48:33 +00:00
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
|
2016-12-05 18:51:07 +00:00
|
|
|
$result = [];
|
2012-08-30 16:04:39 +00:00
|
|
|
if ( in_array( 'list', $prop ) ) {
|
2014-08-05 21:50:54 +00:00
|
|
|
// Group notification results by section
|
|
|
|
if ( $params['groupbysection'] ) {
|
|
|
|
foreach ( $params['sections'] as $section ) {
|
|
|
|
$result[$section] = $this->getSectionPropList(
|
2015-12-10 16:18:30 +00:00
|
|
|
$user, $section, $params['filter'], $params['limit'],
|
2016-06-07 18:48:33 +00:00
|
|
|
$params[$section . 'continue'], $params['format'],
|
2021-08-13 15:53:55 +00:00
|
|
|
$titles, $params[$section . 'unreadfirst'], $params['bundle'],
|
|
|
|
$params['notifiertypes']
|
2014-08-05 21:50:54 +00:00
|
|
|
);
|
2015-11-25 04:07:54 +00:00
|
|
|
|
2016-05-29 20:54:15 +00:00
|
|
|
if ( $this->crossWikiSummary ) {
|
2015-11-25 04:07:54 +00:00
|
|
|
// insert fake notification for foreign notifications
|
2016-05-29 20:54:15 +00:00
|
|
|
$foreignNotification = $this->makeForeignNotification( $user, $params['format'], $section );
|
|
|
|
if ( $foreignNotification ) {
|
|
|
|
array_unshift( $result[$section]['list'], $foreignNotification );
|
|
|
|
}
|
2015-11-25 04:07:54 +00:00
|
|
|
}
|
2014-08-05 21:50:54 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-02-25 01:55:40 +00:00
|
|
|
$attributeManager = EchoServices::getInstance()->getAttributeManager();
|
2014-08-05 21:50:54 +00:00
|
|
|
$result = $this->getPropList(
|
|
|
|
$user,
|
2021-08-20 16:54:38 +00:00
|
|
|
$attributeManager->getUserEnabledEventsBySections( $user, $params['notifiertypes'],
|
2021-08-13 15:53:55 +00:00
|
|
|
$params['sections'] ),
|
2016-06-07 18:48:33 +00:00
|
|
|
$params['filter'], $params['limit'], $params['continue'], $params['format'],
|
2016-03-04 19:23:02 +00:00
|
|
|
$titles, $params['unreadfirst'], $params['bundle']
|
2014-08-05 21:50:54 +00:00
|
|
|
);
|
2015-11-25 04:07:54 +00:00
|
|
|
|
2016-04-21 10:28:32 +00:00
|
|
|
// if exactly 1 section is specified, we consider only that section, otherwise
|
2016-04-27 04:18:28 +00:00
|
|
|
// we pass ALL to consider all foreign notifications
|
2020-06-27 10:05:03 +00:00
|
|
|
$section = count( $params['sections'] ) === 1
|
|
|
|
? reset( $params['sections'] )
|
|
|
|
: EchoAttributeManager::ALL;
|
2016-05-29 20:54:15 +00:00
|
|
|
if ( $this->crossWikiSummary ) {
|
|
|
|
$foreignNotification = $this->makeForeignNotification( $user, $params['format'], $section );
|
|
|
|
if ( $foreignNotification ) {
|
|
|
|
array_unshift( $result['list'], $foreignNotification );
|
|
|
|
}
|
2015-11-25 04:07:54 +00:00
|
|
|
}
|
2014-07-30 22:09:22 +00:00
|
|
|
}
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2012-08-30 16:04:39 +00:00
|
|
|
if ( in_array( 'count', $prop ) ) {
|
2014-08-14 18:46:26 +00:00
|
|
|
$result = array_merge_recursive(
|
|
|
|
$result,
|
2016-04-21 10:28:32 +00:00
|
|
|
$this->getPropCount( $user, $params['sections'], $params['groupbysection'] )
|
2014-08-14 18:46:26 +00:00
|
|
|
);
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2016-07-20 03:02:33 +00:00
|
|
|
if ( in_array( 'seenTime', $prop ) ) {
|
|
|
|
$result = array_merge_recursive(
|
|
|
|
$result,
|
|
|
|
$this->getPropSeenTime( $user, $params['sections'], $params['groupbysection'] )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-04-21 11:16:21 +00:00
|
|
|
return $result;
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2014-07-30 22:09:22 +00:00
|
|
|
/**
|
2014-08-05 21:50:54 +00:00
|
|
|
* Internal method for getting the property 'list' data for individual section
|
|
|
|
* @param User $user
|
2015-08-15 01:51:11 +00:00
|
|
|
* @param string $section 'alert' or 'message'
|
2019-02-15 20:23:02 +00:00
|
|
|
* @param string[] $filter 'all', 'read' or 'unread'
|
2014-08-05 21:50:54 +00:00
|
|
|
* @param int $limit
|
|
|
|
* @param string $continue
|
|
|
|
* @param string $format
|
2018-04-04 15:11:39 +00:00
|
|
|
* @param Title[]|null $titles
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param bool $unreadFirst
|
2016-03-04 19:23:02 +00:00
|
|
|
* @param bool $bundle
|
2021-08-13 15:53:55 +00:00
|
|
|
* @param string[] $notifierTypes
|
2014-08-05 21:50:54 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2018-08-25 10:51:14 +00:00
|
|
|
protected function getSectionPropList(
|
|
|
|
User $user,
|
|
|
|
$section,
|
|
|
|
$filter,
|
|
|
|
$limit,
|
|
|
|
$continue,
|
|
|
|
$format,
|
|
|
|
array $titles = null,
|
|
|
|
$unreadFirst = false,
|
2021-08-13 15:53:55 +00:00
|
|
|
$bundle = false,
|
|
|
|
array $notifierTypes = [ 'web' ]
|
2018-08-25 10:51:14 +00:00
|
|
|
) {
|
2021-02-25 01:55:40 +00:00
|
|
|
$attributeManager = EchoServices::getInstance()->getAttributeManager();
|
2021-08-20 16:54:38 +00:00
|
|
|
$sectionEvents = $attributeManager->getUserEnabledEventsBySections( $user, $notifierTypes, [ $section ] );
|
2015-08-15 01:51:11 +00:00
|
|
|
|
|
|
|
if ( !$sectionEvents ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$result = [
|
|
|
|
'list' => [],
|
2014-08-05 21:50:54 +00:00
|
|
|
'continue' => null
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2014-08-05 21:50:54 +00:00
|
|
|
} else {
|
|
|
|
$result = $this->getPropList(
|
2016-03-04 19:23:02 +00:00
|
|
|
$user, $sectionEvents, $filter, $limit, $continue, $format, $titles, $unreadFirst, $bundle
|
2014-08-05 21:50:54 +00:00
|
|
|
);
|
|
|
|
}
|
2015-10-01 13:48:52 +00:00
|
|
|
|
2014-07-30 22:09:22 +00:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-08-05 21:50:54 +00:00
|
|
|
* Internal helper method for getting property 'list' data, this is based
|
|
|
|
* on the event types specified in the arguments and it could be event types
|
|
|
|
* of a set of sections or a single section
|
|
|
|
* @param User $user
|
2014-08-14 00:01:35 +00:00
|
|
|
* @param string[] $eventTypes
|
2019-02-15 20:23:02 +00:00
|
|
|
* @param string[] $filter 'all', 'read' or 'unread'
|
2014-08-05 21:50:54 +00:00
|
|
|
* @param int $limit
|
|
|
|
* @param string $continue
|
|
|
|
* @param string $format
|
2018-04-04 15:11:39 +00:00
|
|
|
* @param Title[]|null $titles
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param bool $unreadFirst
|
2016-03-04 19:23:02 +00:00
|
|
|
* @param bool $bundle
|
2014-08-05 21:50:54 +00:00
|
|
|
* @return array
|
2014-07-30 22:09:22 +00:00
|
|
|
*/
|
2018-08-25 10:51:14 +00:00
|
|
|
protected function getPropList(
|
|
|
|
User $user,
|
|
|
|
array $eventTypes,
|
|
|
|
$filter,
|
|
|
|
$limit,
|
|
|
|
$continue,
|
|
|
|
$format,
|
|
|
|
array $titles = null,
|
|
|
|
$unreadFirst = false,
|
|
|
|
$bundle = false
|
|
|
|
) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$result = [
|
|
|
|
'list' => [],
|
2014-07-30 22:09:22 +00:00
|
|
|
'continue' => null
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2014-07-30 22:09:22 +00:00
|
|
|
|
2022-11-02 20:47:04 +00:00
|
|
|
$notifMapper = new NotificationMapper();
|
2014-08-14 00:01:35 +00:00
|
|
|
|
2015-12-10 16:18:30 +00:00
|
|
|
// check if we want both read & unread...
|
|
|
|
if ( in_array( 'read', $filter ) && in_array( '!read', $filter ) ) {
|
|
|
|
// Prefer unread notifications. We don't care about next offset in this case
|
|
|
|
if ( $unreadFirst ) {
|
|
|
|
// query for unread notifications past 'continue' (offset)
|
2016-06-07 18:48:33 +00:00
|
|
|
$notifs = $notifMapper->fetchUnreadByUser( $user, $limit + 1, $continue, $eventTypes, $titles );
|
2015-12-09 18:41:40 +00:00
|
|
|
|
2015-12-10 16:18:30 +00:00
|
|
|
/*
|
|
|
|
* 'continue' has a timestamp & id (to start with, in case
|
|
|
|
* there would be multiple events with that same timestamp)
|
|
|
|
* Unread notifications should always load first, but may be
|
|
|
|
* older than read ones, but we can work with current
|
|
|
|
* 'continue' format:
|
|
|
|
* * if there's no continue, first load unread notifications
|
|
|
|
* * if there's a continue, fetch unread notifications first
|
|
|
|
* * if there are no unread ones, continue must've been
|
|
|
|
* about read notifications: fetch 'em
|
|
|
|
* * if there are unread ones but first one doesn't match
|
|
|
|
* continue id, it must've been about read notifications:
|
|
|
|
* discard unread & fetch read
|
|
|
|
*/
|
|
|
|
if ( $notifs && $continue ) {
|
2022-11-02 21:34:17 +00:00
|
|
|
/** @var Notification $first */
|
2015-12-10 16:18:30 +00:00
|
|
|
$first = reset( $notifs );
|
|
|
|
$continueId = intval( trim( strrchr( $continue, '|' ), '|' ) );
|
2019-06-11 21:43:05 +00:00
|
|
|
if ( $first->getEvent()->getId() !== $continueId ) {
|
2015-12-10 16:18:30 +00:00
|
|
|
// notification doesn't match continue id, it must've been
|
|
|
|
// about read notifications: discard all unread ones
|
2016-12-05 18:51:07 +00:00
|
|
|
$notifs = [];
|
2015-12-10 16:18:30 +00:00
|
|
|
}
|
2015-12-09 18:41:40 +00:00
|
|
|
}
|
|
|
|
|
2015-12-10 16:18:30 +00:00
|
|
|
// If there are less unread notifications than we requested,
|
|
|
|
// then fill the result with some read notifications
|
|
|
|
$count = count( $notifs );
|
|
|
|
// we need 1 more than $limit, so we can respond 'continue'
|
|
|
|
if ( $count <= $limit ) {
|
|
|
|
// Query planner should be smart enough that passing a short list of ids to exclude
|
|
|
|
// will only visit at most that number of extra rows.
|
|
|
|
$mixedNotifs = $notifMapper->fetchByUser(
|
|
|
|
$user,
|
|
|
|
$limit - $count + 1,
|
|
|
|
// if there were unread notifications, 'continue' was for
|
|
|
|
// unread notifications and we should start fetching read
|
|
|
|
// notifications from start
|
|
|
|
$count > 0 ? null : $continue,
|
|
|
|
$eventTypes,
|
2016-06-07 18:48:33 +00:00
|
|
|
array_keys( $notifs ),
|
|
|
|
$titles
|
2015-12-10 16:18:30 +00:00
|
|
|
);
|
|
|
|
foreach ( $mixedNotifs as $notif ) {
|
|
|
|
$notifs[$notif->getEvent()->getId()] = $notif;
|
|
|
|
}
|
2014-08-14 00:01:35 +00:00
|
|
|
}
|
2015-12-10 16:18:30 +00:00
|
|
|
} else {
|
2016-12-05 18:51:07 +00:00
|
|
|
$notifs = $notifMapper->fetchByUser( $user, $limit + 1, $continue, $eventTypes, [], $titles );
|
2014-08-14 00:01:35 +00:00
|
|
|
}
|
2015-12-10 16:18:30 +00:00
|
|
|
} elseif ( in_array( 'read', $filter ) ) {
|
2016-06-07 18:48:33 +00:00
|
|
|
$notifs = $notifMapper->fetchReadByUser( $user, $limit + 1, $continue, $eventTypes, $titles );
|
2015-12-10 16:18:30 +00:00
|
|
|
} else { // = if ( in_array( '!read', $filter ) ) {
|
2016-06-07 18:48:33 +00:00
|
|
|
$notifs = $notifMapper->fetchUnreadByUser( $user, $limit + 1, $continue, $eventTypes, $titles );
|
2014-08-14 00:01:35 +00:00
|
|
|
}
|
2014-08-27 23:59:49 +00:00
|
|
|
|
2016-08-26 10:45:48 +00:00
|
|
|
// get $overfetchedItem before bundling and rendering so that it is not affected by filtering
|
2022-11-02 21:34:17 +00:00
|
|
|
/** @var Notification $overfetchedItem */
|
2016-08-26 10:45:48 +00:00
|
|
|
$overfetchedItem = count( $notifs ) > $limit ? array_pop( $notifs ) : null;
|
2016-07-25 19:54:16 +00:00
|
|
|
|
2020-01-29 04:01:51 +00:00
|
|
|
$bundler = null;
|
2016-03-04 19:23:02 +00:00
|
|
|
if ( $bundle ) {
|
|
|
|
$bundler = new Bundler();
|
|
|
|
$notifs = $bundler->bundle( $notifs );
|
|
|
|
}
|
|
|
|
|
2018-10-19 08:04:24 +00:00
|
|
|
while ( $notifs !== [] ) {
|
2022-11-02 21:34:17 +00:00
|
|
|
/** @var Notification $notif */
|
2016-07-26 18:59:13 +00:00
|
|
|
$notif = array_shift( $notifs );
|
2015-10-28 17:15:25 +00:00
|
|
|
$output = EchoDataOutputFormatter::formatOutput( $notif, $format, $user, $this->getLanguage() );
|
|
|
|
if ( $output !== false ) {
|
2016-04-21 09:18:59 +00:00
|
|
|
$result['list'][] = $output;
|
2020-01-29 04:01:51 +00:00
|
|
|
} elseif ( $bundler && $notif->getBundledNotifications() ) {
|
2016-07-26 18:59:13 +00:00
|
|
|
// when the bundle_base gets filtered out, bundled notifications
|
|
|
|
// have to be re-bundled and formatted
|
|
|
|
$notifs = array_merge( $bundler->bundle( $notif->getBundledNotifications() ), $notifs );
|
2015-10-28 17:15:25 +00:00
|
|
|
}
|
2014-07-30 22:09:22 +00:00
|
|
|
}
|
|
|
|
|
2014-08-26 20:07:11 +00:00
|
|
|
// Generate offset if necessary
|
2016-08-26 10:45:48 +00:00
|
|
|
if ( $overfetchedItem ) {
|
2016-04-21 11:16:21 +00:00
|
|
|
// @todo: what to do with this when fetching from multiple wikis?
|
2016-08-26 10:45:48 +00:00
|
|
|
$timestamp = wfTimestamp( TS_UNIX, $overfetchedItem->getTimestamp() );
|
|
|
|
$id = $overfetchedItem->getEvent()->getId();
|
2017-08-11 03:22:40 +00:00
|
|
|
$result['continue'] = $timestamp . '|' . $id;
|
2014-08-26 20:07:11 +00:00
|
|
|
}
|
|
|
|
|
2014-07-30 22:09:22 +00:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2014-08-05 21:50:54 +00:00
|
|
|
/**
|
|
|
|
* Internal helper method for getting property 'count' data
|
|
|
|
* @param User $user
|
|
|
|
* @param string[] $sections
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param bool $groupBySection
|
2015-08-19 20:13:07 +00:00
|
|
|
* @return array
|
2014-08-05 21:50:54 +00:00
|
|
|
*/
|
2016-04-21 10:28:32 +00:00
|
|
|
protected function getPropCount( User $user, array $sections, $groupBySection ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$result = [];
|
2014-08-05 21:50:54 +00:00
|
|
|
$notifUser = MWEchoNotifUser::newFromUser( $user );
|
NotifUser: Refactor getNotificationCount() and friends, add caching for global counts
Previously, getNotificationCount() only looked at local notifications,
and foreign notifications were added in separately by getMessageCount()
and getAlertCount(). This didn't make any sense and resulted in
counter-intuitive things like I4d49b543.
Instead, add a $global flag to getNotificationCount(). If $global=false,
the local count is returned as before, but if $global=true, the
global count (=local+foreign) is returned. If $global is omitted,
the user's cross-wiki notification preference determines which is returned.
Update getLastUnreadNotificationCount() in the same way, since it had
the same issues.
Also add caching for global counts and timestamps, using a global
memc key.
Bug: T133623
Change-Id: If78bfc710acd91a075771b565cc99f4c302a104d
2016-04-27 07:12:32 +00:00
|
|
|
$global = $this->crossWikiSummary ? 'preference' : false;
|
2016-07-05 20:36:36 +00:00
|
|
|
|
|
|
|
$totalRawCount = 0;
|
|
|
|
foreach ( $sections as $section ) {
|
2018-05-31 23:11:57 +00:00
|
|
|
$rawCount = $notifUser->getNotificationCount( $section, $global );
|
2016-07-05 20:36:36 +00:00
|
|
|
if ( $groupBySection ) {
|
2014-08-05 21:50:54 +00:00
|
|
|
$result[$section]['rawcount'] = $rawCount;
|
2022-11-02 03:51:15 +00:00
|
|
|
$result[$section]['count'] = NotificationController::formatNotificationCount( $rawCount );
|
2014-08-05 21:50:54 +00:00
|
|
|
}
|
2016-07-05 20:36:36 +00:00
|
|
|
$totalRawCount += $rawCount;
|
2014-08-05 21:50:54 +00:00
|
|
|
}
|
2016-07-05 20:36:36 +00:00
|
|
|
$result['rawcount'] = $totalRawCount;
|
2022-11-02 03:51:15 +00:00
|
|
|
$result['count'] = NotificationController::formatNotificationCount( $totalRawCount );
|
2015-10-01 13:48:52 +00:00
|
|
|
|
2014-08-05 21:50:54 +00:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2016-07-20 03:02:33 +00:00
|
|
|
/**
|
|
|
|
* Internal helper method for getting property 'seenTime' data
|
|
|
|
* @param User $user
|
|
|
|
* @param string[] $sections
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param bool $groupBySection
|
2016-07-20 03:02:33 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getPropSeenTime( User $user, array $sections, $groupBySection ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$result = [];
|
2016-07-20 03:02:33 +00:00
|
|
|
$seenTimeHelper = EchoSeenTime::newFromUser( $user );
|
|
|
|
|
|
|
|
if ( $groupBySection ) {
|
|
|
|
foreach ( $sections as $section ) {
|
2016-09-23 18:46:44 +00:00
|
|
|
$result[$section]['seenTime'] = $seenTimeHelper->getTime( $section, TS_ISO_8601 );
|
2016-07-20 03:02:33 +00:00
|
|
|
}
|
|
|
|
} else {
|
2016-12-05 18:51:07 +00:00
|
|
|
$result['seenTime'] = [];
|
2016-07-20 03:02:33 +00:00
|
|
|
foreach ( $sections as $section ) {
|
2016-09-23 18:46:44 +00:00
|
|
|
$result['seenTime'][$section] = $seenTimeHelper->getTime( $section, TS_ISO_8601 );
|
2016-07-20 03:02:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2016-05-29 20:54:15 +00:00
|
|
|
/**
|
|
|
|
* Build and format a "fake" notification to represent foreign notifications.
|
|
|
|
* @param User $user
|
|
|
|
* @param string $format
|
|
|
|
* @param string $section
|
|
|
|
* @return array|false A formatted notification, or false if there are no foreign notifications
|
|
|
|
*/
|
2018-08-25 10:51:14 +00:00
|
|
|
protected function makeForeignNotification(
|
|
|
|
User $user,
|
|
|
|
$format,
|
|
|
|
$section = EchoAttributeManager::ALL
|
|
|
|
) {
|
2019-04-18 05:07:14 +00:00
|
|
|
$wikis = $this->getForeignNotifications()->getWikis( $section );
|
|
|
|
$count = $this->getForeignNotifications()->getCount( $section );
|
|
|
|
$maxTimestamp = $this->getForeignNotifications()->getTimestamp( $section );
|
|
|
|
$timestampsByWiki = [];
|
|
|
|
foreach ( $wikis as $wiki ) {
|
|
|
|
$timestampsByWiki[$wiki] = $this->getForeignNotifications()->getWikiTimestamp( $wiki, $section );
|
2016-05-29 20:54:15 +00:00
|
|
|
}
|
|
|
|
|
2018-06-17 16:59:03 +00:00
|
|
|
if ( $count === 0 || $wikis === [] ) {
|
2016-05-29 20:54:15 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-11-25 04:07:54 +00:00
|
|
|
|
2016-02-27 05:43:46 +00:00
|
|
|
// Sort wikis by timestamp, in descending order (newest first)
|
2021-05-04 16:06:42 +00:00
|
|
|
usort( $wikis, static function ( $a, $b ) use ( $section, $timestampsByWiki ) {
|
2020-06-27 10:05:03 +00:00
|
|
|
return (int)$timestampsByWiki[$b]->getTimestamp( TS_UNIX )
|
|
|
|
- (int)$timestampsByWiki[$a]->getTimestamp( TS_UNIX );
|
2016-02-27 05:43:46 +00:00
|
|
|
} );
|
|
|
|
|
2020-08-10 10:55:36 +00:00
|
|
|
$row = (object)[
|
|
|
|
'event_id' => -1,
|
|
|
|
'event_type' => 'foreign',
|
|
|
|
'event_variant' => null,
|
|
|
|
'event_agent_id' => $user->getId(),
|
|
|
|
'event_agent_ip' => null,
|
|
|
|
'event_page_id' => null,
|
|
|
|
'event_extra' => serialize( [
|
|
|
|
'section' => $section ?: 'all',
|
|
|
|
'wikis' => $wikis,
|
|
|
|
'count' => $count
|
|
|
|
] ),
|
|
|
|
'event_deleted' => 0,
|
|
|
|
|
|
|
|
'notification_user' => $user->getId(),
|
|
|
|
'notification_timestamp' => $maxTimestamp,
|
|
|
|
'notification_read_timestamp' => null,
|
|
|
|
'notification_bundle_hash' => md5( 'bogus' ),
|
|
|
|
];
|
2015-11-25 04:07:54 +00:00
|
|
|
|
2016-02-27 05:43:46 +00:00
|
|
|
// Format output like any other notification
|
2022-11-02 21:34:17 +00:00
|
|
|
$notif = Notification::newFromRow( $row );
|
2015-11-25 04:07:54 +00:00
|
|
|
$output = EchoDataOutputFormatter::formatOutput( $notif, $format, $user, $this->getLanguage() );
|
|
|
|
|
2016-02-27 05:43:46 +00:00
|
|
|
// Add cross-wiki-specific data
|
2016-04-15 19:45:48 +00:00
|
|
|
$output['section'] = $section ?: 'all';
|
2015-11-25 04:07:54 +00:00
|
|
|
$output['count'] = $count;
|
2016-04-27 00:25:14 +00:00
|
|
|
$output['sources'] = EchoForeignNotifications::getApiEndpoints( $wikis );
|
2016-02-27 05:43:46 +00:00
|
|
|
// Add timestamp information
|
|
|
|
foreach ( $output['sources'] as $wiki => &$data ) {
|
2016-07-27 21:00:05 +00:00
|
|
|
$data['ts'] = $timestampsByWiki[$wiki]->getTimestamp( TS_ISO_8601 );
|
2016-02-27 05:43:46 +00:00
|
|
|
}
|
2015-11-25 04:07:54 +00:00
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2016-06-15 22:39:07 +00:00
|
|
|
protected function getForeignQueryParams() {
|
2018-08-30 01:03:16 +00:00
|
|
|
$params = $this->getRequest()->getValues();
|
2016-04-21 11:16:21 +00:00
|
|
|
|
2016-05-26 20:41:25 +00:00
|
|
|
// don't request cross-wiki notification summaries
|
2016-05-04 00:46:59 +00:00
|
|
|
unset( $params['notcrosswikisummary'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
|
2016-05-26 20:41:25 +00:00
|
|
|
return $params;
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-17 10:45:03 +00:00
|
|
|
* @param array[] $results
|
2016-04-21 11:16:21 +00:00
|
|
|
* @param array $params
|
2018-12-17 10:45:03 +00:00
|
|
|
* @return array
|
2016-04-21 11:16:21 +00:00
|
|
|
*/
|
|
|
|
protected function mergeResults( array $results, array $params ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary = array_shift( $results );
|
|
|
|
if ( !$primary ) {
|
|
|
|
$primary = [];
|
2016-05-09 18:08:00 +00:00
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
|
|
|
|
if ( in_array( 'list', $params['prop'] ) ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary = $this->mergeList( $primary, $results, $params['groupbysection'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( in_array( 'count', $params['prop'] ) && !$this->crossWikiSummary ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
// if crosswiki data was requested, the count in $primary
|
2016-04-21 11:16:21 +00:00
|
|
|
// is accurate already
|
|
|
|
// otherwise, we'll want to combine counts for all wikis
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary = $this->mergeCount( $primary, $results, $params['groupbysection'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 00:46:13 +00:00
|
|
|
return $primary;
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-04 00:46:13 +00:00
|
|
|
* @param array $primary
|
2018-12-17 10:45:03 +00:00
|
|
|
* @param array[] $results
|
2016-04-21 11:16:21 +00:00
|
|
|
* @param bool $groupBySection
|
|
|
|
* @return array
|
|
|
|
*/
|
2021-09-04 00:46:13 +00:00
|
|
|
protected function mergeList( array $primary, array $results, $groupBySection ) {
|
2016-04-21 11:16:21 +00:00
|
|
|
// sort all notifications by timestamp: most recent first
|
2021-05-04 16:06:42 +00:00
|
|
|
$sort = static function ( $a, $b ) {
|
2016-04-21 11:16:21 +00:00
|
|
|
return $a['timestamp']['utcunix'] - $b['timestamp']['utcunix'];
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( $groupBySection ) {
|
|
|
|
foreach ( EchoAttributeManager::$sections as $section ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
if ( !isset( $primary[$section]['list'] ) ) {
|
|
|
|
$primary[$section]['list'] = [];
|
2016-05-09 18:08:00 +00:00
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
foreach ( $results as $result ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary[$section]['list'] = array_merge( $primary[$section]['list'], $result[$section]['list'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
2021-09-04 00:46:13 +00:00
|
|
|
usort( $primary[$section]['list'], $sort );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-09-04 00:46:13 +00:00
|
|
|
if ( !isset( $primary['list'] ) || !is_array( $primary['list'] ) ) {
|
|
|
|
$primary['list'] = [];
|
2016-05-09 18:08:00 +00:00
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
foreach ( $results as $result ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary['list'] = array_merge( $primary['list'], $result['list'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
2021-09-04 00:46:13 +00:00
|
|
|
usort( $primary['list'], $sort );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 00:46:13 +00:00
|
|
|
return $primary;
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-04 00:46:13 +00:00
|
|
|
* @param array $primary
|
2018-12-17 10:45:03 +00:00
|
|
|
* @param array[] $results
|
2016-04-21 11:16:21 +00:00
|
|
|
* @param bool $groupBySection
|
|
|
|
* @return array
|
|
|
|
*/
|
2021-09-04 00:46:13 +00:00
|
|
|
protected function mergeCount( array $primary, array $results, $groupBySection ) {
|
2016-04-21 11:16:21 +00:00
|
|
|
if ( $groupBySection ) {
|
|
|
|
foreach ( EchoAttributeManager::$sections as $section ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
if ( !isset( $primary[$section]['rawcount'] ) ) {
|
|
|
|
$primary[$section]['rawcount'] = 0;
|
2016-05-09 18:08:00 +00:00
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
foreach ( $results as $result ) {
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary[$section]['rawcount'] += $result[$section]['rawcount'];
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
2022-11-02 03:51:15 +00:00
|
|
|
$primary[$section]['count'] = NotificationController::formatNotificationCount(
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary[$section]['rawcount'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-04 00:46:13 +00:00
|
|
|
if ( !isset( $primary['rawcount'] ) ) {
|
|
|
|
$primary['rawcount'] = 0;
|
2016-05-09 18:08:00 +00:00
|
|
|
}
|
2016-04-21 11:16:21 +00:00
|
|
|
foreach ( $results as $result ) {
|
|
|
|
// regardless of groupbysection, totals are always included
|
2021-09-04 00:46:13 +00:00
|
|
|
$primary['rawcount'] += $result['rawcount'];
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
2022-11-02 03:51:15 +00:00
|
|
|
$primary['count'] = NotificationController::formatNotificationCount( $primary['rawcount'] );
|
2016-04-21 11:16:21 +00:00
|
|
|
|
2021-09-04 00:46:13 +00:00
|
|
|
return $primary;
|
2016-04-21 11:16:21 +00:00
|
|
|
}
|
|
|
|
|
2012-04-27 15:14:24 +00:00
|
|
|
public function getAllowedParams() {
|
2014-08-05 21:50:54 +00:00
|
|
|
$sections = EchoAttributeManager::$sections;
|
2016-05-26 20:41:25 +00:00
|
|
|
|
2018-08-30 01:03:16 +00:00
|
|
|
$params = $this->getCrossWikiParams() + [
|
2016-12-05 18:51:07 +00:00
|
|
|
'filter' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
ParamValidator::PARAM_DEFAULT => 'read|!read',
|
|
|
|
ParamValidator::PARAM_TYPE => [
|
2015-12-10 16:18:30 +00:00
|
|
|
'read',
|
|
|
|
'!read',
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
],
|
|
|
|
'prop' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
ParamValidator::PARAM_TYPE => [
|
2012-08-31 21:50:46 +00:00
|
|
|
'list',
|
|
|
|
'count',
|
2016-07-20 03:02:33 +00:00
|
|
|
'seenTime',
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_DEFAULT => 'list',
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'sections' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_DEFAULT => implode( '|', $sections ),
|
|
|
|
ParamValidator::PARAM_TYPE => $sections,
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'groupbysection' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'boolean',
|
|
|
|
ParamValidator::PARAM_DEFAULT => false,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'format' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => [
|
2015-11-16 16:05:07 +00:00
|
|
|
'model',
|
2015-11-11 02:23:03 +00:00
|
|
|
'special',
|
2016-01-18 06:27:17 +00:00
|
|
|
'flyout', /* @deprecated */
|
|
|
|
'html', /* @deprecated */
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
|
|
|
|
],
|
|
|
|
'limit' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'limit',
|
|
|
|
ParamValidator::PARAM_DEFAULT => 20,
|
2022-04-03 20:14:48 +00:00
|
|
|
IntegerDef::PARAM_MIN => 1,
|
|
|
|
IntegerDef::PARAM_MAX => ApiBase::LIMIT_SML1,
|
|
|
|
IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_SML2,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'continue' => [
|
2015-10-26 15:41:57 +00:00
|
|
|
ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'unreadfirst' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'boolean',
|
|
|
|
ParamValidator::PARAM_DEFAULT => false,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'titles' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
'bundle' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'boolean',
|
|
|
|
ParamValidator::PARAM_DEFAULT => false,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2021-08-13 15:53:55 +00:00
|
|
|
'notifiertypes' => [
|
|
|
|
ParamValidator::PARAM_TYPE => $this->allowedNotifierTypes,
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
ParamValidator::PARAM_DEFAULT => 'web',
|
|
|
|
],
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2014-08-05 21:50:54 +00:00
|
|
|
foreach ( $sections as $section ) {
|
|
|
|
$params[$section . 'continue'] = null;
|
2016-12-05 18:51:07 +00:00
|
|
|
$params[$section . 'unreadfirst'] = [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'boolean',
|
|
|
|
ParamValidator::PARAM_DEFAULT => false,
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2014-08-05 21:50:54 +00:00
|
|
|
}
|
2015-10-01 13:48:52 +00:00
|
|
|
|
2016-05-26 20:41:25 +00:00
|
|
|
if ( $this->allowCrossWikiNotifications() ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$params += [
|
2016-05-13 20:48:03 +00:00
|
|
|
// create "x notifications from y wikis" notification bundle &
|
|
|
|
// include unread counts from other wikis in prop=count results
|
2016-12-05 18:51:07 +00:00
|
|
|
'crosswikisummary' => [
|
2022-04-03 23:26:22 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'boolean',
|
|
|
|
ParamValidator::PARAM_DEFAULT => false,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
];
|
2016-05-13 20:48:03 +00:00
|
|
|
}
|
|
|
|
|
2014-08-05 21:50:54 +00:00
|
|
|
return $params;
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
|
|
|
|
2014-10-28 19:42:41 +00:00
|
|
|
/**
|
|
|
|
* @see ApiBase::getExamplesMessages()
|
2017-08-09 15:20:55 +00:00
|
|
|
* @return array
|
2014-10-28 19:42:41 +00:00
|
|
|
*/
|
|
|
|
protected function getExamplesMessages() {
|
2016-12-05 18:51:07 +00:00
|
|
|
return [
|
2014-10-28 19:42:41 +00:00
|
|
|
'action=query&meta=notifications'
|
|
|
|
=> 'apihelp-query+notifications-example-1',
|
|
|
|
'action=query&meta=notifications¬prop=count¬sections=alert|message¬groupbysection=1'
|
|
|
|
=> 'apihelp-query+notifications-example-2',
|
2021-08-13 15:53:55 +00:00
|
|
|
'action=query&meta=notifications¬notifiertypes=email'
|
|
|
|
=> 'apihelp-query+notifications-example-3',
|
2016-12-05 18:51:07 +00:00
|
|
|
];
|
2014-10-28 19:42:41 +00:00
|
|
|
}
|
|
|
|
|
2012-04-27 15:14:24 +00:00
|
|
|
public function getHelpUrls() {
|
2019-08-27 06:13:35 +00:00
|
|
|
return 'https://www.mediawiki.org/wiki/Special:MyLanguage/Echo_(Notifications)/API';
|
2012-04-27 15:14:24 +00:00
|
|
|
}
|
2012-07-26 17:23:18 +00:00
|
|
|
}
|