Jeroen De Dauw has submitted this change and it was merged.

Change subject: Improvements to the event system
......................................................................


Improvements to the event system

* One class for each responsibility
* Added a ton of tests

Change-Id: I02202d977185eb11702ce8b5948288fd04a9c3f7
---
M EducationProgram.hooks.php
M EducationProgram.php
M includes/Events/EditEventCreator.php
A includes/Events/Event.php
A includes/Events/EventQuery.php
A includes/Events/EventStore.php
M includes/Timeline.php
M includes/TimelineGroup.php
D includes/rows/Event.php
M includes/specials/SpecialMyCourses.php
M includes/tables/Events.php
M tests/phpunit/Events/EditEventCreatorTest.php
A tests/phpunit/Events/EventQueryTest.php
A tests/phpunit/Events/EventStoreTest.php
A tests/phpunit/Events/EventTest.php
15 files changed, 1,080 insertions(+), 150 deletions(-)

Approvals:
  Jeroen De Dauw: Verified; Looks good to me, approved



diff --git a/EducationProgram.hooks.php b/EducationProgram.hooks.php
index 39c7d26..794544c 100644
--- a/EducationProgram.hooks.php
+++ b/EducationProgram.hooks.php
@@ -83,6 +83,9 @@
                        'Utils',
 
                        'Events/EditEventCreator',
+                       'Events/EventQuery',
+                       'Events/EventStore',
+                       'Events/Event',
 
                        'tables/Orgs',
                );
@@ -441,18 +444,21 @@
 
                $dbw = wfGetDB( DB_MASTER );
 
+               // TODO: properly inject dependencies
                $userCourseFinder = new UPCUserCourseFinder( $dbw );
                $eventCreator = new \EducationProgram\Events\EditEventCreator( 
$dbw, $userCourseFinder );
                $events = $eventCreator->getEventsForEdit( $article, $rev, 
$user );
 
                $startOwnStransaction = $dbw->trxLevel() === 0;
 
+               $eventStore = new \EducationProgram\Events\EventStore( 
'ep_events' );
+
                if ( $startOwnStransaction ) {
                        $dbw->begin();
                }
 
                foreach ( $events as $event ) {
-                       $event->save( __METHOD__ );
+                       $eventStore->insertEvent( $event );
                }
 
                if ( $startOwnStransaction ) {
diff --git a/EducationProgram.php b/EducationProgram.php
index c1b3cbc..14ce9a0 100644
--- a/EducationProgram.php
+++ b/EducationProgram.php
@@ -85,6 +85,9 @@
 $wgAutoloadClasses['EducationProgram\ApiRefreshEducation']                     
= $dir . '/includes/api/ApiRefreshEducation.php';
 
 $wgAutoloadClasses['EducationProgram\Events\EditEventCreator']                 
= $dir . '/includes/Events/EditEventCreator.php';
+$wgAutoloadClasses['EducationProgram\Events\Event']                            
= $dir . '/includes/Events/Event.php';
+$wgAutoloadClasses['EducationProgram\Events\EventQuery']                       
= $dir . '/includes/Events/EventQuery.php';
+$wgAutoloadClasses['EducationProgram\Events\EventStore']                       
= $dir . '/includes/Events/EventStore.php';
 
 // includes/pagers (implementing Pager)
 $wgAutoloadClasses['EducationProgram\ArticleTable']                            
= $dir . '/includes/pagers/ArticleTable.php';
@@ -107,7 +110,6 @@
 $wgAutoloadClasses['EducationProgram\Course']                                  
        = $dir . '/includes/rows/Course.php';
 $wgAutoloadClasses['EducationProgram\EPArticle']                               
        = $dir . '/includes/rows/EPArticle.php';
 $wgAutoloadClasses['EducationProgram\EPRevision']                              
        = $dir . '/includes/rows/EPRevision.php';
-$wgAutoloadClasses['EducationProgram\Event']                                   
        = $dir . '/includes/rows/Event.php';
 $wgAutoloadClasses['EducationProgram\Instructor']                              
        = $dir . '/includes/rows/Instructor.php';
 $wgAutoloadClasses['EducationProgram\OA']                                      
                = $dir . '/includes/rows/OA.php';
 $wgAutoloadClasses['EducationProgram\Org']                                     
                = $dir . '/includes/rows/Org.php';
diff --git a/includes/Events/EditEventCreator.php 
b/includes/Events/EditEventCreator.php
index 4710616..0284884 100644
--- a/includes/Events/EditEventCreator.php
+++ b/includes/Events/EditEventCreator.php
@@ -2,7 +2,6 @@
 
 namespace EducationProgram\Events;
 
-use EducationProgram\Event;
 use EducationProgram\Courses;
 use EducationProgram\Student;
 use EducationProgram\UserCourseFinder;
@@ -11,13 +10,15 @@
 use User;
 use Page;
 use DatabaseBase;
+use MWNamespace;
+use Diff;
+use _DiffOp;
 
 /**
  * Class that generates edit based events by handling new edits.
  *
  * TODO: properly inject dependencies
  * - Profiler
- * - event factory
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -119,24 +120,64 @@
         *
         * @since 0.3
         *
-        * @param Revision $rev
+        * @param Revision $revision
         * @param User $user
         * @param int[] $courseIds
         *
         * @return Event[]
         */
-       protected function createEditEvents( Revision $rev, User $user, array 
$courseIds ) {
-               if ( is_null( $rev->getTitle() ) ) {
+       protected function createEditEvents( Revision $revision, User $user, 
array $courseIds ) {
+               if ( is_null( $revision->getTitle() ) ) {
                        return array();
                }
 
-               $event = Event::newFromRevision( $rev, $user );
                $events = array();
 
+               $title = $revision->getTitle();
+
+               $info = array(
+                       'page' => $title->getFullText(),
+                       'comment' => $revision->getComment(),
+                       'minoredit' => $revision->isMinor(),
+                       'parent' => $revision->getParentId()
+               );
+
+               if ( MWNamespace::isTalk( $title->getNamespace() ) && !is_null( 
$revision->getParentId() ) ) {
+                       $diff = new Diff(
+                               explode( "\n", Revision::newFromId( 
$revision->getParentId() )->getText() ),
+                               explode( "\n", $revision->getText() )
+                       );
+
+                       // Only an order of magnitude more lines then the 
python equivalent, but oh well... >_>
+                       // lines = [ diffOp->closing for diffOp in diff->edits 
if diffOp->type == 'add' ]
+                       $lines = array_map(
+                               function( _DiffOp $diffOp ) {
+                                       return $diffOp->closing;
+                               },
+                               array_filter(
+                                       $diff->edits,
+                                       function( _DiffOp $diffOp ) {
+                                               return $diffOp->type == 'add';
+                                       }
+                               )
+                       );
+
+                       if ( $lines !== array() ) {
+                               $lines = call_user_func_array( 'array_merge', 
$lines );
+                       }
+
+                       $info['addedlines'] = $lines;
+               }
+
                foreach ( $courseIds as $courseId ) {
-                       $eventForCourse = clone $event;
-                       $eventForCourse->setField( 'course_id', $courseId );
-                       $events[] = $eventForCourse;
+                       $events[] = new Event(
+                               null,
+                               $courseId,
+                               $user->getId(),
+                               $revision->getTimestamp(),
+                               'edit-' . $title->getNamespace(),
+                               $info
+                       );
                }
 
                return $events;
diff --git a/includes/Events/Event.php b/includes/Events/Event.php
new file mode 100644
index 0000000..40df0d6
--- /dev/null
+++ b/includes/Events/Event.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace EducationProgram\Events;
+
+use User;
+
+/**
+ * Class representing a single Education Program event.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.3
+ *
+ * @ingroup EducationProgram
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < [email protected] >
+ */
+class Event {
+
+       private $eventId;
+       private $courseId;
+       private $userId;
+       private $time;
+       private $type;
+       private $info;
+
+       /**
+        * Constructor.
+        *
+        * @since 0.3
+        *
+        * @param int|null $eventId
+        * @param int $courseId
+        * @param int $userId
+        * @param string $time TS_MW
+        * @param string $type
+        * @param array $info
+        */
+       public function __construct( $eventId, $courseId, $userId, $time, 
$type, array $info ) {
+               $this->eventId = $eventId;
+               $this->courseId = $courseId;
+               $this->userId = $userId;
+               $this->time = $time;
+               $this->type = $type;
+               $this->info = $info;
+       }
+
+       /**
+        * Returns the id of the event.
+        *
+        * @since 0.3
+        *
+        * @return int|null
+        */
+       public function getId() {
+               return $this->eventId;
+       }
+
+       /**
+        * Returns the id of the course for which the event is relevant.
+        *
+        * @since 0.3
+        *
+        * @return int
+        */
+       public function getCourseId() {
+               return $this->courseId;
+       }
+
+       /**
+        * Returns the id of the User that made the event.
+        *
+        * @since 0.3
+        *
+        * @return int
+        */
+       public function getUserId() {
+               return $this->userId;
+       }
+
+       /**c
+        * Returns the time at which the event occurred.
+        * The time is a string formatted as TS_MW.
+        *
+        * @since 0.3
+        *
+        * @return string TS_MW
+        */
+       public function getTime() {
+               return $this->time;
+       }
+
+       /**
+        * Returns the string identifier for the events type.
+        *
+        * @since 0.3
+        *
+        * @return string
+        */
+       public function getType() {
+               return $this->type;
+       }
+
+       /**
+        * Returns the events type specific info.
+        *
+        * @since 0.3
+        *
+        * @return string
+        */
+       public function getInfo() {
+               return $this->info;
+       }
+
+       /**
+        * Returns the age of the event in seconds.
+        *
+        * @since 0.3
+        *
+        * @return integer
+        */
+       public function getAge() {
+               return time() - (int)wfTimestamp( TS_UNIX, $this->time );
+       }
+
+}
diff --git a/includes/Events/EventQuery.php b/includes/Events/EventQuery.php
new file mode 100644
index 0000000..dc72366
--- /dev/null
+++ b/includes/Events/EventQuery.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace EducationProgram\Events;
+
+use InvalidArgumentException;
+
+/**
+ * Specifies the selection criteria and options for a EventStore query.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.3
+ *
+ * @file
+ * @ingroup EducationProgram
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < [email protected] >
+ */
+class EventQuery {
+
+       const COMP_BIGGER = 0;
+       const COMP_SMALLER = 1;
+
+       const ORDER_NONE = 0;
+       const ORDER_TIME_ASC = 1;
+       const ORDER_TIME_DESC = 2;
+
+       /**
+        * @var int[]
+        */
+       private $courseIds = array();
+
+       /**
+        * @var string|null
+        */
+       private $timeLimit = null;
+
+       /**
+        * @var int|null
+        */
+       private $timeLimitComparator = null;
+
+       /**
+        * @var int|null
+        */
+       private $limit = null;
+
+       /**
+        * @var int|null
+        */
+       private $order = null;
+
+       /**
+        * Sets the ids of the courses to which the events should be relevant.
+        *
+        * @since 0.3
+        *
+        * @param int|int[] $courseIds
+        * @throws InvalidArgumentException
+        */
+       public function setCourses( $courseIds ) {
+               $courseIds = (array)$courseIds;
+
+               foreach ( $courseIds as $courseId ) {
+                       if ( !is_int( $courseId ) ) {
+                               throw new InvalidArgumentException( 'Course ids 
need to be integers' );
+                       }
+               }
+
+               $this->courseIds = $courseIds;
+       }
+
+       /**
+        * Sets a time limit all events should be older or newer then,
+        * depending on the provided comparator.
+        *
+        * @since 0.3
+        *
+        * @param string $time TS_MW
+        * @param int $comparator
+        *
+        * @throws InvalidArgumentException
+        */
+       public function setTimeLimit( $time, $comparator ) {
+               if ( !is_string( $time ) ) {
+                       throw new InvalidArgumentException( '$time needs to be 
a TS_MW string' );
+               }
+
+               if ( !is_int( $comparator ) ) {
+                       throw new InvalidArgumentException( '$comparator needs 
to be an integer' );
+               }
+
+               $this->timeLimit = $time;
+               $this->timeLimitComparator = $comparator;
+       }
+
+       /**
+        * Sets the query limit.
+        *
+        * @since 0.3
+        *
+        * @param int $limit
+        *
+        * @throws InvalidArgumentException
+        */
+       public function setRowLimit( $limit ) {
+               if ( !is_int( $limit ) ) {
+                       throw new InvalidArgumentException( '$limit needs to be 
an integer' );
+               }
+
+               if ( $limit <= 0 ) {
+                       throw new InvalidArgumentException( '$limit needs to be 
bigger than 0' );
+               }
+
+               $this->limit = $limit ;
+       }
+
+       /**
+        * Sets the query sort order.
+        *
+        * @since 0.3
+        *
+        * @param int $order
+        *
+        * @throws InvalidArgumentException
+        */
+       public function setSortOrder( $order ) {
+               if ( !is_int( $order ) ) {
+                       throw new InvalidArgumentException( '$order needs to be 
an integer' );
+               }
+
+               $this->order = $order;
+       }
+
+       /**
+        * Gets the ids of the courses to which the events should be relevant.
+        *
+        * @since 0.3
+        *
+        * @return int[]
+        */
+       public function getCourseIds() {
+               return $this->courseIds;
+       }
+
+       /**
+        * Returns the time limit.
+        * Returned as string in TS_MW format or null if there is no such limit.
+        *
+        * @since 0.3
+        *
+        * @return string|null
+        */
+       public function getTimeLimit() {
+               return $this->timeLimit;
+       }
+
+       /**
+        * Returns the time limit comparator.
+        *
+        * @since 0.3
+        *
+        * @return int|null
+        */
+       public function getTimeLimitComparator() {
+               return $this->timeLimitComparator;
+       }
+
+       /**
+        * Returns the query row limit.
+        *
+        * @since 0.3
+        *
+        * @return int|null
+        */
+       public function getRowLimit() {
+               return $this->limit;
+       }
+
+       /**
+        * Returns the query sort order.
+        *
+        * @since 0.3
+        *
+        * @return int|null
+        */
+       public function getSortOrder() {
+               return $this->order;
+       }
+
+}
diff --git a/includes/Events/EventStore.php b/includes/Events/EventStore.php
new file mode 100644
index 0000000..0dbee5f
--- /dev/null
+++ b/includes/Events/EventStore.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace EducationProgram\Events;
+
+use DatabaseBase;
+use InvalidArgumentException;
+
+/**
+ * Service via which EducationProgram events can be saved and queried.
+ *
+ * Side note:
+ * This MySQL implementation of the interface pulls in some global
+ * DatabaseBase object. Injecting a connection provider would be better,
+ * though sadly enough we do not have such an interface yet.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.3
+ *
+ * @file
+ * @ingroup EducationProgram
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < [email protected] >
+ */
+class EventStore {
+
+       /**
+        * @since 0.3
+        *
+        * @var string
+        */
+       private $tableName;
+
+       /**
+        * @since 0.3
+        *
+        * @var int
+        */
+       private $readConnectionId;
+
+       /**
+        * Constructor.
+        *
+        * @since 0.3
+        *
+        * @param string $tableName
+        * @param int $readConnectionId
+        */
+       public function __construct( $tableName, $readConnectionId = DB_SLAVE ) 
{
+               $this->tableName = $tableName;
+               $this->readConnectionId = $readConnectionId;
+       }
+
+       /**
+        * @since 0.3
+        *
+        * @return DatabaseBase
+        */
+       private function getReadConnection() {
+               return wfGetDB( $this->readConnectionId );
+       }
+
+       /**
+        * @since 0.3
+        *
+        * @return DatabaseBase
+        */
+       private function getWriteConnection() {
+               return wfGetDB( DB_MASTER );
+       }
+
+       /**
+        * Runs the provided event query and returns the matching events.
+        *
+        * @since 0.3
+        *
+        * @param EventQuery $query
+        *
+        * @return Event[]
+        */
+       public function query( EventQuery $query ) {
+               $db = $this->getReadConnection();
+
+               $conditions = array();
+
+               if ( $query->getCourseIds() !== null ) {
+                       $conditions['event_course_id'] = $query->getCourseIds();
+               }
+
+               if ( $query->getTimeLimit() !== null ) {
+                       $comparator = $query->getTimeLimitComparator() === 
EventQuery::COMP_BIGGER ? '>' : '<';
+                       $conditions[] = 'event_time  ' . $comparator . ' ' . 
$db->addQuotes( $query->getTimeLimit() );
+               }
+
+               $options = array();
+
+               if ( $query->getRowLimit() !== null ) {
+                       $options['LIMIT'] = $query->getRowLimit();
+
+                       $order = $query->getSortOrder() === 
EventQuery::ORDER_TIME_ASC ? ' ASC' : ' DESC';
+
+                       $options['ORDER BY'] = 'event_time' . $order;
+               }
+
+               $queryResult = $db->select( $this->tableName, '*', $conditions, 
__METHOD__, $options );
+
+               $events = array();
+
+               foreach ( $queryResult as $resultRow ) {
+                       $events[] = $this->eventFromDbResult( $resultRow );
+               }
+
+               return $events;
+       }
+
+       /**
+        * Constructs and returns an Event object given a result row from the 
events table.
+        *
+        * @since 0.3
+        *
+        * @param object $resultRow
+        *
+        * @return Event
+        */
+       private function eventFromDbResult( $resultRow ) {
+               return new Event(
+                       (int)$resultRow->event_id,
+                       (int)$resultRow->event_course_id,
+                       (int)$resultRow->event_user_id,
+                       $resultRow->event_time,
+                       $resultRow->event_type,
+                       unserialize( $resultRow->event_info )
+               );
+       }
+
+       /**
+        * Inserts a new event into the event store.
+        *
+        * @since 0.3
+        *
+        * @param Event $event
+        *
+        * @return boolean SuccessIndicator
+        * @throws InvalidArgumentException
+        */
+       public function insertEvent( Event $event ) {
+               if ( $event->getId() !== null ) {
+                       throw new InvalidArgumentException( 'Can not insert 
events that already have an ID' );
+               }
+
+               $db = $this->getWriteConnection();
+
+               $fields = array(
+                       'event_course_id' => $event->getCourseId(),
+                       'event_user_id' => $event->getUserId(),
+                       'event_time' => $event->getTime(),
+                       'event_type' => $event->getType(),
+                       'event_info' => serialize( $event->getInfo() )
+               );
+
+               return $db->insert(
+                       $this->tableName,
+                       $fields,
+                       __METHOD__
+               ) !== false;
+       }
+
+}
diff --git a/includes/Timeline.php b/includes/Timeline.php
index 30c8150..099d545 100644
--- a/includes/Timeline.php
+++ b/includes/Timeline.php
@@ -1,7 +1,9 @@
 <?php
 
 namespace EducationProgram;
+
 use IContextSource;
+use EducationProgram\Events\Event;
 
 /**
  * Education Program timeline.
@@ -65,7 +67,7 @@
                $groups = array();
 
                foreach ( $this->events as $event ) {
-                       $eventInfo = $event->getField( 'info' );
+                       $eventInfo = $event->getInfo();
 
                        if ( array_key_exists( 'page', $eventInfo ) ) {
                                $groupId = $eventInfo['page'] . '|';
@@ -74,20 +76,20 @@
                                if ( array_key_exists( $groupId, $groups ) ) {
                                        $groups[$groupId]['events'][] = $event;
 
-                                       if ( $event->getField( 'time' ) > 
$groups[$groupId]['time'] ) {
-                                               $groups[$groupId]['time'] = 
$event->getField( 'time' );
+                                       if ( $event->getTime() > 
$groups[$groupId]['time'] ) {
+                                               $groups[$groupId]['time'] = 
$event->getTime();
                                        }
                                }
                                else {
                                        $groups[$groupId] = array(
-                                               'time' => $event->getField( 
'time' ),
+                                               'time' => $event->getTime(),
                                                'events' => array( $event ),
                                        );
                                }
                        }
                        else {
                                $groups[] = array(
-                                       'time' => $event->getField( 'time' ),
+                                       'time' => $event->getTime(),
                                        'events' => array( $event ),
                                );
                        }
diff --git a/includes/TimelineGroup.php b/includes/TimelineGroup.php
index c314167..d9443c8 100644
--- a/includes/TimelineGroup.php
+++ b/includes/TimelineGroup.php
@@ -1,7 +1,15 @@
 <?php
 
 namespace EducationProgram;
-use IContextSource, MWException, Html, Linker, Message, User, Title;
+
+use IContextSource;
+use MWException;
+use Html;
+use Linker;
+use Message;
+use User;
+use Title;
+use EducationProgram\Events\Event;
 
 /**
  * Class for displaying a group of Education Program events in a timeline.
@@ -48,9 +56,9 @@
                 */
                foreach ( $events as $event ) {
                        if ( is_null( $type ) ) {
-                               $type = $event->getField( 'type' );
+                               $type = $event->getType();
                        }
-                       elseif ( $type !== $event->getField( 'type' ) ) {
+                       elseif ( $type !== $event->getType() ) {
                                throw new MWException( 'Got events of different 
types when trying to build a ' . __CLASS__ );
                        }
                }
@@ -242,9 +250,9 @@
        protected function getSegmentHTML( Event $event ) {
                return $this->msg(
                        'ep-timeline-unknown',
-                       $event->getUser()->getName(),
-                       $this->getLanguage()->time( $event->getField( 'time' ) 
),
-                       $this->getLanguage()->date( $event->getField( 'time' ) )
+                       User::newFromId( $event->getUserId() ),
+                       $this->getLanguage()->time( $event->getTime() ),
+                       $this->getLanguage()->date( $event->getTime() )
 
                )->escaped();
        }
@@ -275,8 +283,8 @@
        protected function getSegmentHTML( Event $event ) {
                $html = '';
 
-               $user = $event->getUser();
-               $info = $event->getField( 'info' );
+               $user = User::newFromId( $event->getUserId() );
+               $info = $event->getInfo();
 
                $html .= Linker::userLink( $user->getId(), $user->getName() );
 
@@ -323,7 +331,7 @@
                 * @var Event $event
                 */
                foreach ( $this->events as $event ) {
-                       $userIds[] = $event->getField( 'user_id' );
+                       $userIds[] = $event->getUserId();
                }
 
                $userIds = array_unique( $userIds );
@@ -345,8 +353,8 @@
                        )->escaped();
                }
 
-               $info = $this->events[0]->getField( 'info' );
-               $type = explode( '-', $this->events[0]->getField( 'type' ) );
+               $info = $this->events[0]->getInfo();
+               $type = explode( '-', $this->events[0]->getType() );
                $type = (int)array_pop( $type );
 
                $keys = array(
diff --git a/includes/rows/Event.php b/includes/rows/Event.php
deleted file mode 100644
index 817a168..0000000
--- a/includes/rows/Event.php
+++ /dev/null
@@ -1,109 +0,0 @@
-<?php
-
-namespace EducationProgram;
-use User, Revision, MWNamespace, Diff, _DiffOp;
-
-/**
- * Class representing a single Education Program event.
- *
- * @since 0.1
- *
- * @ingroup EducationProgram
- *
- * @licence GNU GPL v2+
- * @author Jeroen De Dauw < [email protected] >
- */
-class Event extends \ORMRow {
-
-       /**
-        * Field for caching the linked user.
-        *
-        * @since 0.1
-        * @var User|bool false
-        */
-       protected $user = false;
-
-       /**
-        * Create a new edit event from a revision.
-        *
-        * @since 0.1
-        *
-        * @param Revision $revision
-        * @param User $user
-        *
-        * @return Event
-        */
-       public static function newFromRevision( Revision $revision, User $user 
) {
-               $title = $revision->getTitle();
-
-               $info = array(
-                       'page' => $title->getFullText(),
-                       'comment' => $revision->getComment(),
-                       'minoredit' => $revision->isMinor(),
-                       'parent' => $revision->getParentId()
-               );
-
-               if ( MWNamespace::isTalk( $title->getNamespace() ) && !is_null( 
$revision->getParentId() ) ) {
-                       $diff = new Diff(
-                               explode( "\n", Revision::newFromId( 
$revision->getParentId() )->getText() ),
-                               explode( "\n", $revision->getText() )
-                       );
-
-                       // Only an order of magnitude more lines then the 
python equivalent, but oh well... >_>
-                       // lines = [ diffOp->closing for diffOp in diff->edits 
if diffOp->type == 'add' ]
-                       $lines = array_map(
-                               function( _DiffOp $diffOp ) {
-                                       return $diffOp->closing;
-                               },
-                               array_filter(
-                                       $diff->edits,
-                                       function( _DiffOp $diffOp ) {
-                                               return $diffOp->type == 'add';
-                                       }
-                               )
-                       );
-
-                       if ( $lines !== array() ) {
-                               $lines = call_user_func_array( 'array_merge', 
$lines );
-                       }
-
-                       $info['addedlines'] = $lines;
-               }
-
-               $fields = array(
-                       'user_id' => $user->getId(),
-                       'time' => $revision->getTimestamp(),
-                       'type' => 'edit-' . $title->getNamespace(),
-                       'info' => $info,
-               );
-
-               return Events::singleton()->newRow( $fields );
-       }
-
-       /**
-        * Returns the user that made the event.
-        *
-        * @since 0.1
-        *
-        * @return User
-        */
-       public function getUser() {
-               if ( $this->user === false ) {
-                       $this->user = User::newFromId( $this->getField( 
'user_id' ) );
-               }
-
-               return $this->user;
-       }
-
-       /**
-        * Returns the age of the event in seconds.
-        *
-        * @since 0.1
-        *
-        * @return integer
-        */
-       public function getAge() {
-               return time() - (int)wfTimestamp( TS_UNIX, $this->getField( 
'time' ) );
-       }
-
-}
diff --git a/includes/specials/SpecialMyCourses.php 
b/includes/specials/SpecialMyCourses.php
index df52565..8a4e302 100644
--- a/includes/specials/SpecialMyCourses.php
+++ b/includes/specials/SpecialMyCourses.php
@@ -1,7 +1,11 @@
 <?php
 
 namespace EducationProgram;
-use IContextSource, Linker;
+
+use IContextSource;
+use Linker;
+use EducationProgram\Events\EventQuery;
+use EducationProgram\Events\EventStore;
 
 /**
  * Page listing the recent actibvity of the users classmates.
@@ -177,17 +181,22 @@
        protected function displayTimeline( Course $course ) {
                $this->addCachedHTML(
                        function( Course $course, IContextSource $context ) {
-                               $eventTable = Events::singleton();
+                               // TODO: inject dependency
+                               $eventStore = new 
\EducationProgram\Events\EventStore( 'ep_events' );
 
-                               $conds = array(
-                                       'course_id' => $course->getId(),
-                                       'time > ' . wfGetDB( DB_SLAVE 
)->addQuotes( wfTimestamp( TS_MW, time() - Settings::get( 
'timelineDurationLimit' ) ) ),
-                               );
+                               $query = new EventQuery();
 
-                               $options = array(
-                                       'LIMIT' => Settings::get( 
'timelineCountLimit' ),
-                                       'ORDER BY' => 
$eventTable->getPrefixedField( 'time' ) . ' DESC'
-                               );
+                               $query->setCourses( $course->getId() );
+
+                               // TODO: inject settings
+                               $timeLimit = wfTimestamp( TS_MW, time() - 
Settings::get( 'timelineDurationLimit' ) );
+                               $query->setTimeLimit( $timeLimit, 
EventQuery::COMP_BIGGER );
+
+                               $query->setRowLimit( Settings::get( 
'timelineCountLimit' ) );
+
+                               $query->setSortOrder( 
EventQuery::ORDER_TIME_DESC );
+
+                               $events = $eventStore->query( $query );
 
                                $html = Linker::link(
                                        $course->getTitle(),
@@ -197,8 +206,6 @@
                                                $course->getField( 'name' )
                                        )
                                );
-
-                               $events = iterator_to_array( 
$eventTable->select( null, $conds, $options ) );
 
                                if ( $events === array() ) {
                                        $html .= $context->msg( 
'ep-dashboard-timeline-empty' )->escaped();
diff --git a/includes/tables/Events.php b/includes/tables/Events.php
index 77f3e9d..6753399 100644
--- a/includes/tables/Events.php
+++ b/includes/tables/Events.php
@@ -38,7 +38,7 @@
         * @return string
         */
        public function getRowClass() {
-               return 'EducationProgram\Event';
+               return 'ORMRow';
        }
 
        /**
diff --git a/tests/phpunit/Events/EditEventCreatorTest.php 
b/tests/phpunit/Events/EditEventCreatorTest.php
index ea874eb..9ed909d 100644
--- a/tests/phpunit/Events/EditEventCreatorTest.php
+++ b/tests/phpunit/Events/EditEventCreatorTest.php
@@ -72,7 +72,7 @@
                $events = $eventCreator->getEventsForEdit( $article, $rev, 
$user );
 
                $this->assertInternalType( 'array', $events );
-               $this->assertContainsOnlyInstancesOf( 'EducationProgram\Event', 
$events );
+               $this->assertContainsOnlyInstancesOf( 
'EducationProgram\Events\Event', $events );
        }
 
 }
diff --git a/tests/phpunit/Events/EventQueryTest.php 
b/tests/phpunit/Events/EventQueryTest.php
new file mode 100644
index 0000000..a0cfbb2
--- /dev/null
+++ b/tests/phpunit/Events/EventQueryTest.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace EducationProgram\Tests\Events;
+
+use EducationProgram\Events\EventQuery;
+
+/**
+ * Unit tests for the EducationProgram\Events\EventQuery class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.3
+ *
+ * @file
+ * @ingroup EducationProgramTest
+ *
+ * @group EducationProgram
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < [email protected] >
+ */
+class EventQueryTest extends \PHPUnit_Framework_TestCase {
+
+       public function setCoursesProvider() {
+               $argLists = array();
+
+               $argLists[] = array( 1 );
+               $argLists[] = array( 42 );
+               $argLists[] = array( array( 1 ) );
+               $argLists[] = array( array( 42 ) );
+               $argLists[] = array( array( 1, 2, 3, 9001 ) );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider setCoursesProvider
+        *
+        * @param $courseIds
+        */
+       public function testSetCourses( $courseIds ) {
+               $query = new EventQuery();
+               $query->setCourses( $courseIds );
+               $this->assertEquals( (array)$courseIds, $query->getCourseIds() 
);
+       }
+
+       public function timeLimitProvider() {
+               $argLists = array();
+
+               $argLists[] = array( '20010115123456', EventQuery::COMP_BIGGER 
);
+               $argLists[] = array( '20010115123456', EventQuery::COMP_SMALLER 
);
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider timeLimitProvider
+        *
+        * @param $timeLimit
+        * @param $comparator
+        */
+       public function testSetTimeLimit( $timeLimit, $comparator ) {
+               $query = new EventQuery();
+               $query->setTimeLimit( $timeLimit, $comparator );
+
+               $this->assertEquals( $timeLimit, $query->getTimeLimit() );
+               $this->assertEquals( $comparator, 
$query->getTimeLimitComparator() );
+       }
+
+       public function rowLimitProvider() {
+               $argLists = array();
+
+               $argLists[] = array( 1 );
+               $argLists[] = array( 42 );
+               $argLists[] = array( 9001 );
+               $argLists[] = array( 7201010 );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider rowLimitProvider
+        *
+        * @param $courseIds
+        */
+       public function testSetRowLimit( $limit ) {
+               $query = new EventQuery();
+               $query->setRowLimit( $limit );
+               $this->assertEquals( $limit, $query->getRowLimit() );
+       }
+
+       public function sortOrderProvider() {
+               $argLists = array();
+
+               $argLists[] = array( EventQuery::ORDER_NONE );
+               $argLists[] = array( EventQuery::ORDER_TIME_ASC );
+               $argLists[] = array( EventQuery::ORDER_TIME_DESC );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider sortOrderProvider
+        *
+        * @param $order
+        */
+       public function testSetSortOrder( $order ) {
+               $query = new EventQuery();
+               $query->setSortOrder( $order );
+               $this->assertEquals( $order, $query->getSortOrder() );
+       }
+
+}
diff --git a/tests/phpunit/Events/EventStoreTest.php 
b/tests/phpunit/Events/EventStoreTest.php
new file mode 100644
index 0000000..248b585
--- /dev/null
+++ b/tests/phpunit/Events/EventStoreTest.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace EducationProgram\Tests\Events;
+
+use EducationProgram\Events\EventStore;
+use EducationProgram\Events\EventQuery;
+use EducationProgram\Events\Event;
+
+/**
+ * Unit tests for the EducationProgram\Events\EventStore class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.3
+ *
+ * @file
+ * @ingroup EducationProgramTest
+ *
+ * @group EducationProgram
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < [email protected] >
+ */
+class EventStoreTest extends \MediaWikiTestCase {
+
+       public function getStore() {
+               return new EventStore( 'ep_events' );
+       }
+
+       public function setUp() {
+               parent::setUp();
+
+               wfGetDB( DB_MASTER )->delete( 'ep_events', '*' );
+
+               $events = array();
+
+               $events[] = new Event(
+                       null,
+                       900001,
+                       1,
+                       '20010115123456',
+                       'foobar',
+                       array( 'baz' )
+               );
+
+               $events[] = new Event(
+                       null,
+                       900002,
+                       1,
+                       '20010115123457',
+                       'foobar',
+                       array( 'bah' )
+               );
+
+               $events[] = new Event(
+                       null,
+                       900001,
+                       1,
+                       '20110115123457',
+                       'foobar',
+                       array( 'spam' )
+               );
+
+               $events[] = new Event(
+                       null,
+                       900001,
+                       2,
+                       '20110115123457',
+                       'foobar',
+                       array( 'hax' )
+               );
+
+               $events[] = new Event(
+                       null,
+                       900001,
+                       1,
+                       '20110115123457',
+                       'nyan',
+                       array( '~=[,,_,,]:3', 42, array( 'o_O' ) )
+               );
+
+               foreach ( $events as $event ) {
+                       $this->getStore()->insertEvent( $event );
+               }
+       }
+
+       public function queryProvider() {
+               $argLists = array();
+
+               $query = new EventQuery();
+               $query->setCourses( 900001 );
+
+               $argLists[] = array( $query, 4 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( 900002 );
+
+               $argLists[] = array( $query, 1 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( 900003 );
+
+               $argLists[] = array( $query, 0 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( array( 900001, 900002, 900003 ) );
+
+               $argLists[] = array( $query, 5 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( 900001 );
+               $query->setRowLimit( 2 );
+
+               $argLists[] = array( $query, 2 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( 900001 );
+               $query->setRowLimit( 2 );
+               $query->setSortOrder( EventQuery::ORDER_TIME_ASC );
+
+               $argLists[] = array( $query, 2 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( 900001 );
+               $query->setTimeLimit( '20050115123457', EventQuery::COMP_BIGGER 
);
+
+               $argLists[] = array( $query, 3 );
+
+
+               $query = new EventQuery();
+               $query->setCourses( 900001 );
+               $query->setTimeLimit( '20050115123457', 
EventQuery::COMP_SMALLER );
+
+               $argLists[] = array( $query, 1 );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider queryProvider
+        *
+        * @param EventQuery $query
+        * @param int $expectedCount
+        */
+       public function testQuery( EventQuery $query, $expectedCount ) {
+               $events = $this->getStore()->query( $query );
+
+               $this->assertInternalType( 'array', $events );
+               $this->assertContainsOnlyInstancesOf( 
'EducationProgram\Events\Event', $events );
+
+               $this->assertCount( $expectedCount, $events );
+       }
+
+       public function eventProvider() {
+               $events = array();
+
+               $events[] = new Event(
+                       null,
+                       900011,
+                       4242,
+                       '20110115123457',
+                       'foobar',
+                       array( 'hax' )
+               );
+
+               $events[] = new Event(
+                       null,
+                       900012,
+                       31337,
+                       '20110115123457',
+                       'nyan',
+                       array( '~=[,,_,,]:3', 42, array( 'o_O' ) )
+               );
+
+               return $this->arrayWrap( $events );
+       }
+
+       /**
+        * @dataProvider eventProvider
+        *
+        * @param Event $event
+        */
+       public function testInsertEvent( Event $event ) {
+               $this->assertTrue( $this->getStore()->insertEvent( $event ), 
'insertEvent returned true' );
+       }
+
+}
diff --git a/tests/phpunit/Events/EventTest.php 
b/tests/phpunit/Events/EventTest.php
new file mode 100644
index 0000000..5e786c6
--- /dev/null
+++ b/tests/phpunit/Events/EventTest.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace EducationProgram\Tests\Events;
+
+use EducationProgram\Events\Event;
+
+/**
+ * Unit tests for the EducationProgram\Events\Event class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 0.3
+ *
+ * @file
+ * @ingroup EducationProgramTest
+ *
+ * @group EducationProgram
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < [email protected] >
+ */
+class EventTest extends \PHPUnit_Framework_TestCase {
+
+       public function constructorProvider() {
+               $argLists = array();
+
+               $argLists[] = array( 1, 2, 3, '20010115123456', 'type-foobar', 
array() );
+               $argLists[] = array( 42, 9001, 7201010, '20010115123456', 
'baz', array( 'o' => 'noez' ) );
+               $argLists[] = array( null, 1, 1, '20010115123456', 'spam', 
array( 'o' => 'noez', 42 ) );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetId( $id, $courseId, $userId, $time, $type, $info 
) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals( $id, $event->getId() );
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetCourseId( $id, $courseId, $userId, $time, $type, 
$info ) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals( $courseId, $event->getCourseId() );
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetUserId( $id, $courseId, $userId, $time, $type, 
$info ) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals( $userId, $event->getUserId() );
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetTime( $id, $courseId, $userId, $time, $type, 
$info ) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals( $time, $event->getTime() );
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetType( $id, $courseId, $userId, $time, $type, 
$info ) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals( $type, $event->getType() );
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetInfo( $id, $courseId, $userId, $time, $type, 
$info ) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals( $info, $event->getInfo() );
+       }
+
+       /**
+        * @dataProvider constructorProvider
+        */
+       public function testGetAge( $id, $courseId, $userId, $time, $type, 
$info ) {
+               $event = new Event( $id, $courseId, $userId, $time, $type, 
$info );
+
+               $this->assertEquals(
+                       time() - (int)wfTimestamp( TS_UNIX, $time ),
+                       $event->getAge()
+               );
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/53349
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I02202d977185eb11702ce8b5948288fd04a9c3f7
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/EducationProgram
Gerrit-Branch: master
Gerrit-Owner: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: Ragesoss <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to