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

Reply via email to