mediawiki-extensions-Echo/includes/ForeignNotifications.php
Roan Kattouw bfbfa6be62 Sort wikis by timestamp of most recent notification
Both in the order of the cross-wiki bundles themselves, and
in the message in the notification body.

ForeignNotifications tracks timestamps per wiki per section,
and exposes these through getWikiTimestamp(). ApiEchoNotifications
adds these timestamps to the sources manifest, and also sorts
the list of wikis by timestamp (it'd be nicer to do this in
ForeignPresentationModel instead, but then we'd have to create a new
ForeignNotifications instance which causes a duplicated DB query).

NotificationsModel receives the timestamp for its wiki as its
fallback timestamp, and makes getTimestamp() return this value
during the pre-population phase. This causes its parent to
automatically sort it correctly.

Because the timestamp of a wiki depends on the section (alerts vs messages),
we can't put it in the global sources manifest at the top level
of the API response. Instead, get rid of this global sources
manifest and put all the sources data in the foreign notifications
directly. This allows us to specify different timestamps, and also
allows us to get rid of code in EchoApi that was already remapping
the API response to this format.

Bug: T130298
Change-Id: Ie083fbb1ccaf74fbe804633d87ef03c9e71b120f
2016-03-17 14:21:16 -07:00

225 lines
5.8 KiB
PHP

<?php
class EchoForeignNotifications {
/**
* @var bool|EchoUnreadWikis
*/
protected $unreadWikis = false;
/**
* @var array [(str) section => (int) count, ...]
*/
protected $counts = array( EchoAttributeManager::ALERT => 0, EchoAttributeManager::MESSAGE => 0 );
/**
* @var array [(str) section => (string[]) wikis, ...]
*/
protected $wikis = array( EchoAttributeManager::ALERT => array(), EchoAttributeManager::MESSAGE => array() );
/**
* @var array [(str) section => (MWTimestamp) timestamp, ...]
*/
protected $timestamps = array( EchoAttributeManager::ALERT => false, EchoAttributeManager::MESSAGE => false );
/**
* @var array [(str) wiki => [ (str) section => (MWTimestamp) timestamp, ...], ...]
*/
protected $wikiTimestamps = array();
/**
* @var bool
*/
protected $populated = false;
/**
* @param User $user
*/
public function __construct( User $user ) {
if ( $user->getOption( 'echo-cross-wiki-notifications' ) ) {
$this->unreadWikis = EchoUnreadWikis::newFromUser( $user );
}
}
/**
* @param string|null $section Name of section or null for all
* @return int
*/
public function getCount( $section = null ) {
$this->populate();
if ( $section === null ) {
return array_sum( $this->counts );
}
return isset( $this->counts[$section] ) ? $this->counts[$section] : 0;
}
/**
* @param string|null $section Name of section or null for all
* @return MWTimestamp|false
*/
public function getTimestamp( $section = null ) {
$this->populate();
if ( $section === null ) {
$max = false;
/** @var MWTimestamp $timestamp */
foreach ( $this->timestamps as $timestamp ) {
// $timestamp < $max = invert 0
// $timestamp > $max = invert 1
if ( $max === false || $timestamp->diff( $max )->invert === 1 ) {
$max = $timestamp;
}
}
return $timestamp;
}
return isset( $this->timestamps[$section] ) ? $this->timestamps[$section] : false;
}
/**
* @param string|null $section Name of section or null for all
* @return string[]
*/
public function getWikis( $section = null ) {
$this->populate();
if ( $section === null ) {
$all = array();
foreach ( $this->wikis as $wikis ) {
$all = array_merge( $all, $wikis );
}
return array_unique( $all );
}
return isset( $this->wikis[$section] ) ? $this->wikis[$section] : array();
}
public function getWikiTimestamp( $wiki, $section = null ) {
$this->populate();
if ( !isset( $this->wikiTimestamps[$wiki] ) ) {
return false;
}
if ( $section === null ) {
$max = false;
foreach ( $this->wikiTimestamps[$wiki] as $section => $ts ) {
// $ts < $max = invert 0
// $ts > $max = invert 1
if ( $max === false || $ts->diff( $max )->invert === 1 ) {
$max = $ts;
}
}
return $max;
}
return isset( $this->wikiTimestamps[$wiki][$section] ) ? $this->wikiTimestamps[$wiki][$section] : false;
}
protected function populate() {
if ( $this->populated ) {
return;
}
if ( $this->unreadWikis === false ) {
return;
}
$unreadCounts = $this->unreadWikis->getUnreadCounts();
if ( !$unreadCounts ) {
return;
}
foreach ( $unreadCounts as $wiki => $sections ) {
// exclude current wiki
if ( $wiki === wfWikiID() ) {
continue;
}
foreach ( $sections as $section => $data ) {
if ( $data['count'] > 0 ) {
$this->counts[$section] += $data['count'];
$this->wikis[$section][] = $wiki;
$timestamp = new MWTimestamp( $data['ts'] );
$this->wikiTimestamps[$wiki][$section] = $timestamp;
// We need $this->timestamp[$section] to be the max timestamp
// across all wikis.
// $timestamp < $this->timestamps[$section] = invert 0
// $timestamp > $this->timestamps[$section] = invert 1
if (
$this->timestamps[$section] === false ||
$timestamp->diff( $this->timestamps[$section] )->invert === 1
) {
$this->timestamps[$section] = new MWTimestamp( $data['ts'] );
}
}
}
}
$this->populated = true;
}
/**
* @param string[] $wikis
* @return array[] [(string) wiki => (array) data]
*/
public function getApiEndpoints( array $wikis ) {
global $wgConf;
$wgConf->loadFullData();
$data = array();
foreach ( $wikis as $wiki ) {
$siteFromDB = $wgConf->siteFromDB( $wiki );
list( $major, $minor ) = $siteFromDB;
$server = $wgConf->get( 'wgServer', $wiki, $major, array( 'lang' => $minor, 'site' => $major ) );
$scriptPath = $wgConf->get( 'wgScriptPath', $wiki, $major, array( 'lang' => $minor, 'site' => $major ) );
$data[$wiki] = array(
'title' => $this->getWikiTitle( $wiki, $siteFromDB ),
'url' => $server . $scriptPath . '/api.php',
);
}
return $data;
}
/**
* @param string $wikiId
* @param array $siteFromDB $wgConf->siteFromDB( $wikiId ) result
* @return mixed|string
*/
protected function getWikiTitle( $wikiId, array $siteFromDB = null ) {
global $wgConf, $wgLang;
$msg = wfMessage( 'project-localized-name-'.$wikiId );
// check if WikimediaMessages localized project names are available
if ( $msg->exists() ) {
return $msg->text();
} else {
// don't fetch $site, $langCode if known already
if ( $siteFromDB === null ) {
$siteFromDB = $wgConf->siteFromDB( $wikiId );
}
list( $site, $langCode ) = $siteFromDB;
// try to fetch site name for this specific wiki, or fallback to the
// general project's sitename if there is no override
$wikiName = $wgConf->get( 'wgSitename', $wikiId ) ?: $wgConf->get( 'wgSitename', $site );
$langName = Language::fetchLanguageName( $langCode, $wgLang->getCode() );
if ( !$langName ) {
// if we can't find a language name (in language-agnostic
// project like mediawikiwiki), including the language name
// doesn't make much sense
return $wikiName;
}
// ... or use generic fallback
return wfMessage( 'echo-foreign-wiki-lang', $wikiName, $langName )->text();
}
}
}