Bsitu has uploaded a new change for review.
https://gerrit.wikimedia.org/r/147369
Change subject: [WIP]Replace EchoBackend with mappers and gateway
......................................................................
[WIP]Replace EchoBackend with mappers and gateway
* Get rid of EchoBackend by separating responsibilities into smaller objects
* Move the fetchNotification logic to a more appropriate place
* Better for future maintenance
@Todo - integrate the remaining write actions
Change-Id: I42f4d7566543332588431c21c220c0d64d026b70
---
M Echo.php
M api/ApiEchoNotifications.php
A includes/ClientOutputFormatter.php
M includes/DbEchoBackend.php
M includes/EchoBackend.php
M includes/EchoDbFactory.php
M includes/NotifUser.php
A includes/gateway/UserNotificationGateway.php
A includes/mapper/EventMapper.php
A includes/mapper/NotificationMapper.php
M model/Event.php
M model/Notification.php
M special/SpecialNotifications.php
13 files changed, 656 insertions(+), 182 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo
refs/changes/69/147369/1
diff --git a/Echo.php b/Echo.php
index 2568095..b0ca0f3 100644
--- a/Echo.php
+++ b/Echo.php
@@ -47,6 +47,7 @@
$wgExtensionMessagesFiles['Echo'] = $dir . 'Echo.i18n.php';
$wgExtensionMessagesFiles['EchoAliases'] = $dir . 'Echo.alias.php';
+// Basic Echo classes
$wgAutoloadClasses['EchoHooks'] = $dir . 'Hooks.php';
$wgAutoloadClasses['EchoEvent'] = $dir . 'model/Event.php';
$wgAutoloadClasses['EchoNotification'] = $dir . 'model/Notification.php';
@@ -56,7 +57,15 @@
$wgAutoloadClasses['MWDbEchoEmailBundler'] = $dir .
'includes/DbEmailBundler.php';
$wgAutoloadClasses['MWEchoEventLogging'] = $dir . 'includes/EventLogging.php';
-// Formatters
+// Database mappers && gateways
+$wgAutoloadClasses['MWEchoEventMapper'] = $dir .
'includes/mapper/EventMapper.php';
+$wgAutoloadClasses['MWEchoNotificationMapper'] = $dir .
'includes/mapper/NotificationMapper.php';
+$wgAutoloadClasses['MWEchoUserNotificationGateway'] = $dir .
'includes/gateway/UserNotificationGateway.php';
+
+// Output formatters
+$wgAutoloadClasses['MWEchoClientOutputFormatter'] = $dir .
'includes/ClientOutputFormatter.php';
+
+// Event formatters
$wgAutoloadClasses['EchoNotificationFormatter'] = $dir .
'formatters/NotificationFormatter.php';
$wgAutoloadClasses['EchoBasicFormatter'] = $dir .
'formatters/BasicFormatter.php';
$wgAutoloadClasses['EchoEditFormatter'] = $dir .
'formatters/EditFormatter.php';
diff --git a/api/ApiEchoNotifications.php b/api/ApiEchoNotifications.php
index 91f0171..b8da62c 100644
--- a/api/ApiEchoNotifications.php
+++ b/api/ApiEchoNotifications.php
@@ -23,7 +23,12 @@
$result = array();
if ( in_array( 'list', $prop ) ) {
- $result['list'] = self::getNotifications( $user,
$params['format'], $params['limit'] + 1, $params['continue'] );
+ $result['list'] = array();
+ $notifMapper = new MWEchoNotificationMapper(
MWEchoDbFactory::create() );
+ $notifs = $notifMapper->fetchByUser( $user,
$params['limit'] + 1, $params['continue'], 'web' );
+ foreach ( $notifs as $notif ) {
+ $result['list'][$notif->getEvent()->getID()] =
MWEchoClientOutputFormatter::formatOutput( $notif, $params['format'], $user );
+ }
// check if there is more elements than we request
if ( count( $result['list'] ) > $params['limit'] ) {
@@ -54,116 +59,6 @@
$this->getResult()->setIndexedTagName( $result, 'notification'
);
$this->getResult()->addValue( 'query', $this->getModuleName(),
$result );
- }
-
- /**
- * Get a list of notifications based on the passed parameters
- *
- * @param $user User the user to get notifications for
- * @param $format string|bool false to not format any notifications,
string to a specific output format
- * @param $limit int The maximum number of notifications to return
- * @param $continue string Used for offset
- *
- * @return array
- */
- public static function getNotifications( $user, $format = false, $limit
= 20, $continue = null ) {
- global $wgEchoBackend;
-
- $output = array();
-
- // TODO: Make 'web' based on a new API param?
- $res = $wgEchoBackend->loadNotifications( $user, $limit,
$continue, 'web' );
-
- foreach ( $res as $row ) {
- $event = EchoEvent::newFromRow( $row );
- if ( $row->notification_bundle_base &&
$row->notification_bundle_display_hash ) {
- $event->setBundleHash(
$row->notification_bundle_display_hash );
- }
-
- $timestampMw = self::getUserLocalTime( $user,
$row->notification_timestamp );
-
- // Start creating date section header
- $now = wfTimestamp();
- $dateFormat = substr( $timestampMw, 0, 8 );
- if ( substr( self::getUserLocalTime( $user, $now ), 0,
8 ) === $dateFormat ) {
- // 'Today'
- $date = wfMessage( 'echo-date-today'
)->escaped();
- } elseif ( substr( self::getUserLocalTime( $user, $now
- 86400 ), 0, 8 ) === $dateFormat ) {
- // 'Yesterday'
- $date = wfMessage( 'echo-date-yesterday'
)->escaped();
- } else {
- // 'May 10' or '10 May' (depending on user's
date format preference)
- $lang =
RequestContext::getMain()->getLanguage();
- $dateFormat = $lang->getDateFormatString(
'pretty', $user->getDatePreference() ?: 'default' );
- $date = $lang->sprintfDate( $dateFormat,
$timestampMw );
- }
- // End creating date section header
-
- $thisEvent = array(
- 'id' => $event->getId(),
- 'type' => $event->getType(),
- 'category' => $event->getCategory(),
- 'timestamp' => array(
- // UTC timestamp in UNIX format used
for loading more notification
- 'utcunix' => wfTimestamp( TS_UNIX,
$row->notification_timestamp ),
- 'unix' => self::getUserLocalTime(
$user, $row->notification_timestamp, TS_UNIX ),
- 'mw' => $timestampMw,
- 'date' => $date
- ),
- );
-
- if ( $event->getVariant() ) {
- $thisEvent['variant'] = $event->getVariant();
- }
-
- if ( $event->getTitle() ) {
- $thisEvent['title'] = array(
- 'full' =>
$event->getTitle()->getPrefixedText(),
- 'namespace' =>
$event->getTitle()->getNSText(),
- 'namespace-key' =>
$event->getTitle()->getNamespace(),
- 'text' => $event->getTitle()->getText(),
- );
- }
-
- if ( $event->getAgent() ) {
- if ( $event->userCan( Revision::DELETED_USER,
$user ) ) {
- $thisEvent['agent'] = array(
- 'id' =>
$event->getAgent()->getId(),
- 'name' =>
$event->getAgent()->getName(),
- );
- } else {
- $thisEvent['agent'] = array(
'userhidden' => '' );
- }
- }
-
- if ( $row->notification_read_timestamp ) {
- $thisEvent['read'] =
$row->notification_read_timestamp;
- }
-
- if ( $format ) {
- $thisEvent['*'] =
EchoNotificationController::formatNotification(
- $event, $user, $format );
- }
-
- $output[$event->getID()] = $thisEvent;
- }
-
- return $output;
- }
-
- /**
- * Internal helper function for converting UTC timezone to a user's
timezone
- *
- * @param $user User
- * @param $ts string
- * @param $format int output format
- *
- * @return string
- */
- private static function getUserLocalTime( $user, $ts, $format = TS_MW )
{
- $timestamp = new MWTimestamp( $ts );
- $timestamp->offsetForUser( $user );
- return $timestamp->getTimestamp( $format );
}
public function getAllowedParams() {
diff --git a/includes/ClientOutputFormatter.php
b/includes/ClientOutputFormatter.php
new file mode 100644
index 0000000..cc8ec15
--- /dev/null
+++ b/includes/ClientOutputFormatter.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * Output formatter that format the output in raw format for client
+ */
+class MWEchoClientOutputFormatter {
+
+ /**
+ * Format the notification in as raw output to the clients
+ *
+ * @param string|bool specifify output format, false to not format any
notifications
+ * @param User|null the target user viewing the notification
+ * @return array
+ */
+ public function formatOutput( EchoNotification $notification, $format =
false, User $user = null ) {
+ $event = $notification->getEvent();
+ // Default to notification user if user is not specified
+ if ( !$user ) {
+ $user = $notification->getUser();
+ }
+
+ if ( $notification->getBundleBase() &&
$notification->getBundleDisplayHash() ) {
+ $event->setBundleHash(
$notification->getBundleDisplayHash() );
+ }
+
+ $timestampMw = self::getUserLocalTime( $user,
$notification->getTimestamp() );
+
+ // Start creating date section header
+ $now = wfTimestamp();
+ $dateFormat = substr( $timestampMw, 0, 8 );
+ if ( substr( self::getUserLocalTime( $user, $now ), 0, 8 ) ===
$dateFormat ) {
+ // 'Today'
+ $date = wfMessage( 'echo-date-today' )->escaped();
+ } elseif ( substr( self::getUserLocalTime( $user, $now - 86400
), 0, 8 ) === $dateFormat ) {
+ // 'Yesterday'
+ $date = wfMessage( 'echo-date-yesterday' )->escaped();
+ } else {
+ // 'May 10' or '10 May' (depending on user's date
format preference)
+ $lang = RequestContext::getMain()->getLanguage();
+ $dateFormat = $lang->getDateFormatString( 'pretty',
$user->getDatePreference() ?: 'default' );
+ $date = $lang->sprintfDate( $dateFormat, $timestampMw );
+ }
+ // End creating date section header
+
+ $output = array(
+ 'id' => $event->getId(),
+ 'type' => $event->getType(),
+ 'category' => $event->getCategory(),
+ 'timestamp' => array(
+ // UTC timestamp in UNIX format used for
loading more notification
+ 'utcunix' => wfTimestamp( TS_UNIX,
$notification->getTimestamp() ),
+ 'unix' => self::getUserLocalTime( $user,
$notification->getTimestamp(), TS_UNIX ),
+ 'mw' => $timestampMw,
+ 'date' => $date
+ ),
+ );
+
+ if ( $event->getVariant() ) {
+ $output['variant'] = $event->getVariant();
+ }
+
+ if ( $event->getTitle() ) {
+ $output['title'] = array(
+ 'full' => $event->getTitle()->getPrefixedText(),
+ 'namespace' => $event->getTitle()->getNSText(),
+ 'namespace-key' =>
$event->getTitle()->getNamespace(),
+ 'text' => $event->getTitle()->getText(),
+ );
+ }
+
+ if ( $event->getAgent() ) {
+ if ( $event->userCan( Revision::DELETED_USER, $user ) )
{
+ $output['agent'] = array(
+ 'id' => $event->getAgent()->getId(),
+ 'name' => $event->getAgent()->getName(),
+ );
+ } else {
+ $output['agent'] = array( 'userhidden' => '' );
+ }
+ }
+
+ if ( $notification->getReadTimestamp() ) {
+ $output['read'] = $notification->getReadTimestamp();
+ }
+
+ if ( $format ) {
+ $output['*'] =
EchoNotificationController::formatNotification( $event, $user, $format );
+ }
+
+ return $output;
+ }
+
+ /**
+ * Helper function for converting UTC timezone to a user's timezone
+ *
+ * @param User
+ * @param string
+ * @param int output format
+ *
+ * @return string
+ */
+ public static function getUserLocalTime( $user, $ts, $format = TS_MW ) {
+ $timestamp = new MWTimestamp( $ts );
+ $timestamp->offsetForUser( $user );
+ return $timestamp->getTimestamp( $format );
+ }
+
+}
diff --git a/includes/DbEchoBackend.php b/includes/DbEchoBackend.php
index cf457e6..313b2c1 100644
--- a/includes/DbEchoBackend.php
+++ b/includes/DbEchoBackend.php
@@ -1,6 +1,8 @@
<?php
/**
+ * @todo - Move the functionality to smaller objects and remove it eventually
+ *
* Database backend for echo notification
*/
class MWDbEchoBackend extends MWEchoBackend {
@@ -37,55 +39,6 @@
MWEchoNotifUser::newFromUser( $user
)->resetNotificationCount( DB_MASTER );
}
);
- }
-
- /**
- * @param $user User the user to get notifications for
- * @param $limit int The maximum number of notifications to return
- * @param $continue string Used for offset
- * @param $outputFormat string The output format of the notifications
(web,
- * email, etc.)
- * @return array
- */
- public function loadNotifications( $user, $limit, $continue,
$outputFormat = 'web' ) {
- $dbr = MWEchoDbFactory::getDB( DB_SLAVE );
-
- $eventTypesToLoad =
EchoNotificationController::getUserEnabledEvents( $user, $outputFormat );
- if ( !$eventTypesToLoad ) {
- return array();
- }
-
- // Look for notifications with base = 1
- $conds = array(
- 'notification_user' => $user->getID(),
- 'event_type' => $eventTypesToLoad,
- 'notification_bundle_base' => 1
- );
-
- $offset = $this->extractQueryOffset( $continue );
-
- // Start points are specified
- if ( $offset['timestamp'] && $offset['offset'] ) {
- $ts = $dbr->addQuotes( $dbr->timestamp(
$offset['timestamp'] ) );
- // The offset and timestamp are those of the first
notification we want to return
- $conds[] = "notification_timestamp < $ts OR (
notification_timestamp = $ts AND notification_event <= " . $offset['offset'] .
" )";
- }
-
- $res = $dbr->select(
- array( 'echo_notification', 'echo_event' ),
- '*',
- $conds,
- __METHOD__,
- array(
- 'ORDER BY' => 'notification_timestamp DESC,
notification_event DESC',
- 'LIMIT' => $limit,
- ),
- array(
- 'echo_event' => array( 'LEFT JOIN',
'notification_event=event_id' ),
- )
- );
-
- return iterator_to_array( $res, false );
}
/**
diff --git a/includes/EchoBackend.php b/includes/EchoBackend.php
index cbfbbed..60dbee8 100644
--- a/includes/EchoBackend.php
+++ b/includes/EchoBackend.php
@@ -57,16 +57,6 @@
abstract public function createNotification( $row );
/**
- * Load notifications based on the parameters
- * @param $user User the user to get notifications for
- * @param $limit int The maximum number of notifications to return
- * @param $continue string Used for offset
- * @param $outputFormat string The output format of the notifications
(web, email, etc.)
- * @return array
- */
- abstract public function loadNotifications( $user, $limit, $continue,
$outputFormat = 'web' );
-
- /**
* Get the bundle data for user/hash
* @param $user User
* @param $bundleHash string The hash used to identify a set of
bundle-able events
diff --git a/includes/EchoDbFactory.php b/includes/EchoDbFactory.php
index 24bac62..601bdce 100644
--- a/includes/EchoDbFactory.php
+++ b/includes/EchoDbFactory.php
@@ -28,4 +28,39 @@
}
+ /**
+ * Create a db factory instance
+ * @return MWEchoDbFactory
+ */
+ public static function create() {
+ return new self();
+ }
+
+ /**
+ * Get the database load blancer
+ * @param $wiki string|bool The wiki ID, or false for the current wiki
+ * @return LoadBalancer
+ */
+ protected function getLB( $wiki = false ) {
+ global $wgEchoCluster;
+
+ // Use the external db defined for Echo
+ if ( $wgEchoCluster ) {
+ $lb = wfGetLBFactory()->getExternalLB( $wgEchoCluster,
$wiki );
+ } else {
+ $lb = wfGetLB( $wiki );
+ }
+
+ return $lb;
+ }
+
+ /**
+ * @param $db int Index of the connection to get
+ * @param $groups mixed Query groups.
+ * @param $wiki string|bool The wiki ID, or false for the current wiki
+ * @return DatabaseBase
+ */
+ public function getEchoDb( $db, $groups = array(), $wiki = false ) {
+ return $this->getLB( $wiki )->getConnection( $db, $groups,
$wiki );
+ }
}
diff --git a/includes/NotifUser.php b/includes/NotifUser.php
index 38bc388..2959fdb 100644
--- a/includes/NotifUser.php
+++ b/includes/NotifUser.php
@@ -27,11 +27,10 @@
* Constructor for initialization
* @param $user User
*/
- private function __construct( User $user ) {
- global $wgMemc, $wgEchoBackend;
+ private function __construct( User $user, BagOStuff $cache,
MWEchoUserNotificationGateway $userNotifGateway ) {
$this->mUser = $user;
- $this->storage = $wgEchoBackend;
- $this->cache = $wgMemc;
+ $this->userNotifGateway = $userNotifGateway;
+ $this->cache = $cache;
}
/**
@@ -44,7 +43,11 @@
if ( $user->isAnon() ) {
throw new MWException( 'User must be logged in to view
notification!' );
}
- return new MWEchoNotifUser( $user );
+ global $wgMemc;
+ return new MWEchoNotifUser(
+ $user, $wgMemc,
+ new MWEchoUserNotificationGateway( $user,
MWEchoDbFactory::create() )
+ );
}
/**
@@ -66,8 +69,7 @@
// Mark the talk page notification as read
$this->markRead(
- $this->storage->getUnreadNotifications(
- $this->mUser,
+ $this->userNotifGateway->getUnreadNotifications(
'edit-user-talk'
)
);
@@ -133,7 +135,7 @@
return (int)$this->cache->get( $memcKey );
}
- $count = $this->storage->getNotificationCount( $this->mUser,
$dbSource );
+ $count = $this->userNotifGateway->getNotificationCount(
$dbSource );
$this->cache->set( $memcKey, $count, 86400 );
@@ -150,7 +152,7 @@
return;
}
- $this->storage->markRead( $this->mUser, $eventIds );
+ $this->userNotifGateway->markRead( $eventIds );
$this->resetNotificationCount( DB_MASTER );
}
@@ -165,7 +167,7 @@
// Only update all the unread notifications if it isn't a huge
number.
// TODO: Implement batched jobs it's over the maximum.
- $this->storage->markAllRead( $this->mUser );
+ $this->userNotifGateway->markAllRead();
$this->resetNotificationCount( DB_MASTER );
$this->flagCacheWithNoTalkNotification();
return true;
diff --git a/includes/gateway/UserNotificationGateway.php
b/includes/gateway/UserNotificationGateway.php
new file mode 100644
index 0000000..9be52c7
--- /dev/null
+++ b/includes/gateway/UserNotificationGateway.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * Database gateway which handles direct database interaction with the
+ * echo_notification & echo_event for a user, that wouldn't require
+ * loading data into a models
+ */
+class MWEchoUserNotificationGateway {
+
+ /**
+ * @var MWEchoDbFactory
+ */
+ protected $dbFactory;
+
+ /**
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * The tables to for this gateway
+ */
+ protected static $eventTable = 'echo_event';
+ protected static $notificationTable = 'echo_notification';
+
+ /**
+ * @param User
+ * @param MWEchoDbFactory
+ */
+ public function __construct( User $user, MWEchoDbFactory $dbFactory ) {
+ $this->user = $user;
+ $this->dbFactory = $dbFactory;
+ }
+
+ /**
+ * Mark notifications as read
+ * @param $eventIDs array
+ */
+ public function markRead( array $eventIDs ) {
+ if ( !$eventIDs ) {
+ return;
+ }
+
+ $dbw = $this->dbFactory->getEchoDb( DB_MASTER );
+
+ $dbw->update(
+ self::$notificationTable,
+ array( 'notification_read_timestamp' =>
$dbw->timestamp( wfTimestampNow() ) ),
+ array(
+ 'notification_user' => $this->user->getId(),
+ 'notification_event' => $eventIDs,
+ 'notification_read_timestamp' => null,
+ ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * Mark all notification as read
+ */
+ public function markAllRead() {
+ $dbw = $this->dbFactory->getEchoDb( DB_MASTER );
+
+ $dbw->update(
+ self::$notificationTable,
+ array( 'notification_read_timestamp' =>
$dbw->timestamp( wfTimestampNow() ) ),
+ array(
+ 'notification_user' => $this->user->getId(),
+ 'notification_read_timestamp' => NULL,
+ 'notification_bundle_base' => 1,
+ ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param $dbSource string use master or slave storage to pull count
+ * @return int
+ */
+ public function getNotificationCount( $dbSource ) {
+ // double check
+ if ( !in_array( $dbSource, array( DB_SLAVE, DB_MASTER ) ) ) {
+ $dbSource = DB_SLAVE;
+ }
+
+ $eventTypesToLoad =
EchoNotificationController::getUserEnabledEvents( $this->user, 'web' );
+
+ if ( !$eventTypesToLoad ) {
+ return 0;
+ }
+
+ global $wgEchoMaxNotificationCount;
+
+ $db = $this->dbFactory->getEchoDb( $dbSource );
+ $res = $db->select(
+ array(
+ self::$notificationTable,
+ self::$eventTable
+ ),
+ array( 'notification_event' ),
+ array(
+ 'notification_user' => $this->user->getId(),
+ 'notification_bundle_base' => 1,
+ 'notification_read_timestamp' => null,
+ 'event_type' => $eventTypesToLoad,
+ ),
+ __METHOD__,
+ array( 'LIMIT' => $wgEchoMaxNotificationCount + 1 ),
+ array(
+ 'echo_event' => array( 'LEFT JOIN',
'notification_event=event_id' ),
+ )
+ );
+ return $db->numRows( $res );
+ }
+
+ /**
+ * IMPORTANT: should only call this function if the number of unread
notification
+ * is reasonable, for example, unread notification count is less than
the max
+ * display defined in $wgEchoMaxNotificationCount
+ * @param $type string
+ * @return int[]
+ */
+ public function getUnreadNotifications( $type ) {
+ $dbr = $this->dbFactory->getEchoDb( DB_SLAVE );
+ $res = $dbr->select(
+ array(
+ self::$notificationTable,
+ self::$eventTable
+ ),
+ array( 'notification_event' ),
+ array(
+ 'notification_user' => $this->user->getId(),
+ 'notification_bundle_base' => 1,
+ 'notification_read_timestamp' => null,
+ 'event_type' => $type,
+ 'notification_event = event_id'
+ ),
+ __METHOD__
+ );
+
+ $eventIds = array();
+ if ( $res ) {
+ foreach ( $res as $row ) {
+ $eventIds[$row->notification_event] =
$row->notification_event;
+ }
+ }
+
+ return $eventIds;
+ }
+
+
+}
diff --git a/includes/mapper/EventMapper.php b/includes/mapper/EventMapper.php
new file mode 100644
index 0000000..88d6f6f
--- /dev/null
+++ b/includes/mapper/EventMapper.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Database mapper for EchoEvent model, which is an immutable class, there
should
+ * not be any update to it
+ */
+class MWEchoEventMapper {
+
+ /**
+ * Echo database factory
+ * @param MWEchoDbFactory
+ */
+ protected $dbFactory;
+
+ /**
+ * @param MWEchoDbFactory
+ */
+ public function __construct( MWEchoDbFactory $dbFactory ) {
+ $this->dbFactory = $dbFactory;
+ }
+
+ /**
+ * Insert an event record
+ * @param EchoEvent
+ * @return int|bool
+ */
+ public function insert( EchoEvent $event ) {
+ $dbw = $this->dbFactory->getEchoDb( DB_MASTER );
+
+ $id = $dbw->nextSequenceValue( 'echo_event_id' );
+
+ $row = $event->toDbArray();
+ if ( $id ) {
+ $row['event_id'] = $id;
+ }
+
+ $res = $dbw->insert( 'echo_event', $row, __METHOD__ );
+
+ if ( $res ) {
+ if ( !$id ) {
+ $id = $dbw->insertId();
+ }
+ return $id;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Create an EchoEvent by id
+ * @param int
+ * @param boolean
+ * @return EchoEvent
+ * @throws MWException
+ */
+ public function fetchById( $id, $fromMaster = false ) {
+ $db = $fromMaster ? $this->dbFactory->getEchoDb( DB_MASTER ) :
$this->dbFactory->getEchoDb( DB_SLAVE );
+
+ $row = $db->selectRow( 'echo_event', '*', array( 'event_id' =>
$id ), __METHOD__ );
+
+ if ( !$row && !$fromMaster ) {
+ return $this->fetchById( $id, true );
+ } elseif ( !$row ) {
+ throw new MWException( "No EchoEvent found with ID:
$id" );
+ }
+
+ return EchoEvent::newFromRow( $row );
+ }
+
+}
diff --git a/includes/mapper/NotificationMapper.php
b/includes/mapper/NotificationMapper.php
new file mode 100644
index 0000000..4b270b7
--- /dev/null
+++ b/includes/mapper/NotificationMapper.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * Database mapper for EchoNotification model
+ */
+class MWEchoNotificationMapper {
+
+ /**
+ * Echo database factory
+ * @param MWEchoDbFactory
+ */
+ protected $dbFactory;
+
+ /**
+ * @param MWEchoDbFactory
+ */
+ public function __construct( MWEchoDbFactory $dbFactory ) {
+ $this->dbFactory = $dbFactory;
+ }
+
+ /**
+ * Insert a notification record
+ * @param EchoNotification
+ * @return null
+ */
+ public function insert( EchoNotification $notification ) {
+ $dbw = $this->dbFactory->getEchoDb( DB_MASTER );
+
+ $fname = __METHOD__;
+ $row = $notification->toDbArray();
+ $dbw->onTransactionIdle(
+ function() use ( $dbw, $row, $fname ) {
+ $dbw->startAtomic( $fname );
+ // reset the bundle base if this notification
has a display hash
+ // the result of this operation is that all
previous notifications
+ // with the same display hash are set to
non-base because new record
+ // is becoming the bundle base
+ if ( $row['notification_bundle_display_hash'] )
{
+ $dbw->update(
+ 'echo_notification',
+ array(
'notification_bundle_base' => 0 ),
+ array(
+ 'notification_user' =>
$row['notification_user'],
+
'notification_bundle_display_hash' => $row['notification_bundle_display_hash'],
+
'notification_bundle_base' => 1
+ ),
+ $fname
+ );
+ }
+
+ $row['notification_timestamp'] =
$dbw->timestamp( $row['notification_timestamp'] );
+ $res = $dbw->insert( 'echo_notification', $row,
$fname );
+ $dbw->endAtomic( $fname );
+
+ // @todo - this is simple and easy but the
proper way is to build a notification
+ // observer to notify all listeners on creating
a new notification
+ if ( $res ) {
+ $user = User::newFromId(
$row['notification_user'] );
+ MWEchoNotifUser::newFromUser( $user
)->resetNotificationCount( DB_MASTER );
+ }
+ }
+ );
+ }
+
+ /**
+ * Extract the offset used for notification list
+ *
+ * @param $continue String Used for offset
+ *
+ * @throws MWException
+ * @return int[]
+ */
+ protected function extractQueryOffset( $continue ) {
+ $offset = array (
+ 'timestamp' => 0,
+ 'offset' => 0,
+ );
+ if ( $continue ) {
+ $values = explode( '|', $continue, 3 );
+ if ( count( $values ) !== 2 ) {
+ throw new MWException( 'Invalid continue param:
' . $continue );
+ }
+ $offset['timestamp'] = (int)$values[0];
+ $offset['offset'] = (int)$values[1];
+ }
+
+ return $offset;
+ }
+
+ /**
+ * @param $user User the user to get notifications for
+ * @param $limit int The maximum number of notifications to return
+ * @param $continue string Used for offset
+ * @param $outputFormat string The output format of the notifications
(web,
+ * email, etc.)
+ * @return EchoNotification[]
+ */
+ public function fetchByUser( User $user, $limit, $continue,
$outputFormat = 'web' ) {
+ $dbr = $this->dbFactory->getEchoDb( DB_SLAVE );
+
+ $eventTypesToLoad =
EchoNotificationController::getUserEnabledEvents( $user, $outputFormat );
+ if ( !$eventTypesToLoad ) {
+ return array();
+ }
+
+ // Look for notifications with base = 1
+ $conds = array(
+ 'notification_user' => $user->getID(),
+ 'event_type' => $eventTypesToLoad,
+ 'notification_bundle_base' => 1
+ );
+
+ $offset = $this->extractQueryOffset( $continue );
+
+ // Start points are specified
+ if ( $offset['timestamp'] && $offset['offset'] ) {
+ $ts = $dbr->addQuotes( $dbr->timestamp(
$offset['timestamp'] ) );
+ // The offset and timestamp are those of the first
notification we want to return
+ $conds[] = "notification_timestamp < $ts OR (
notification_timestamp = $ts AND notification_event <= " . $offset['offset'] .
" )";
+ }
+
+ $res = $dbr->select(
+ array( 'echo_notification', 'echo_event' ),
+ '*',
+ $conds,
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'notification_timestamp DESC,
notification_event DESC',
+ 'LIMIT' => $limit,
+ ),
+ array(
+ 'echo_event' => array( 'LEFT JOIN',
'notification_event=event_id' ),
+ )
+ );
+
+ $data = array();
+
+ if ( $res ) {
+ foreach ( $res as $row ) {
+ $data[] = EchoNotification::newFromRow( $row );
+ }
+ }
+ return $data;
+ }
+
+}
diff --git a/model/Event.php b/model/Event.php
index 34b9e46..492a689 100644
--- a/model/Event.php
+++ b/model/Event.php
@@ -147,6 +147,37 @@
}
/**
+ * Convert the object's database property to array
+ * @return array
+ */
+ public function toDbArray() {
+ $data = array (
+ 'event_type' => $this->type,
+ 'event_variant' => $this->variant,
+ 'event_extra' => $this->serializeExtra()
+ );
+ if ( $this->id ) {
+ $data['event_id'] = $this->id;
+ }
+ if ( $this->agent ) {
+ if ( $this->agent->isAnon() ) {
+ $data['event_agent_ip'] =
$this->agent->getName();
+ } else {
+ $data['event_agent_id'] = $this->agent->getId();
+ }
+ }
+ if ( $this->title ) {
+ $pageId = $this->title->getArticleId();
+ // Don't need any special handling for title with no id
+ // as they are already stored in extra data array
+ if ( $pageId ) {
+ $data['event_page_id'] = $pageId;
+ }
+ }
+ return $data;
+ }
+
+ /**
* Check whether the echo event is an enabled event
* @return bool
*/
diff --git a/model/Notification.php b/model/Notification.php
index d0a6abc..ac47411 100644
--- a/model/Notification.php
+++ b/model/Notification.php
@@ -23,6 +23,22 @@
protected $readTimestamp;
/**
+ * Default is 0, which means this is a base
+ * @var int
+ */
+ protected $bundleBase = 1;
+
+ /**
+ * @var string
+ */
+ protected $bundleHash;
+
+ /**
+ * @var string
+ */
+ protected $bundleDisplayHash;
+
+ /**
* Do not use this constructor.
*/
protected function __construct() {}
@@ -109,6 +125,44 @@
}
/**
+ * Load a notification record from std class
+ * @param stdClass
+ * @return EchoNotification
+ */
+ public function newFromRow( $row ) {
+ $notification = new EchoNotification();
+
+ if ( property_exists( $row, 'event_type' ) ) {
+ $notification->event = EchoEvent::newFromRow( $row );
+ } else {
+ $notification->event = EchoEvent::newFromID( $row );
+ }
+ $notification->user = User::newFromId( $row->notification_user
);
+ $notification->timestamp = $row->notification_timestamp;
+ $notification->readTimestamp =
$row->notification_read_timestamp;
+ $notification->bundleBase = $row->notification_bundle_base;
+ $notification->bundleHash = $row->notification_bundle_hash;
+ $notification->bundleDisplayHash =
$row->notification_bundle_display_hash;
+ return $notification;
+ }
+
+ /**
+ * Convert object property to database row array
+ * @return array
+ */
+ public function toDbArray() {
+ $data = array(
+ 'notification_event' => $this->event->getId(),
+ 'notification_user' => $this->user->getId(),
+ 'notification_timestamp' => $this->timestamp,
+ 'notification_read_timestamp' => $this->readTimestamp,
+ 'notification_bundle_base' => $this->bundleBase,
+ 'notification_bundle_hash' => $this->bundleHash,
+ 'notification_bundle_display_hash' =>
$this->bundleDisplayHash
+ );
+ }
+
+ /**
* Getter method
* @return EchoEvent The event for this notification
*/
@@ -139,4 +193,28 @@
public function getReadTimestamp() {
return $this->readTimestamp;
}
+
+ /**
+ * Getter method
+ * @return int Notification bundle base
+ */
+ public function getBundleBase() {
+ return $this->bundleBase;
+ }
+
+ /**
+ * Getter method
+ * @return string|null Notification bundle hash
+ */
+ public function getBundleHash() {
+ return $this->bundleHash;
+ }
+
+ /**
+ * Getter method
+ * @return string|null Notification bundle display hash
+ */
+ public function getBundleDisplayHash() {
+ return $this->bundleDisplayHash;
+ }
}
diff --git a/special/SpecialNotifications.php b/special/SpecialNotifications.php
index 067d090..89ac754 100644
--- a/special/SpecialNotifications.php
+++ b/special/SpecialNotifications.php
@@ -38,7 +38,12 @@
$continue = $this->getRequest()->getVal( 'continue', null );
// Pull the notifications
- $notif = ApiEchoNotifications::getNotifications( $user, 'html',
self::$displayNum + 1, $continue );
+ $notif = array();
+ $notificationMapper = new MWEchoNotificationMapper(
MWEchoDbFactory::create() );
+ $notifications = $notificationMapper->fetchByUser( $user,
self::$displayNum + 1, $continue, 'web' );
+ foreach ( $notifications as $notification ) {
+ $notif[] = MWEchoClientOutputFormatter::formatOutput(
$notification, 'html', $user );
+ }
// If there are no notifications, display a message saying so
if ( !$notif ) {
--
To view, visit https://gerrit.wikimedia.org/r/147369
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I42f4d7566543332588431c21c220c0d64d026b70
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Bsitu <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits