2014-07-18 03:58:21 +00:00
|
|
|
<?php
|
|
|
|
|
2022-11-02 20:47:04 +00:00
|
|
|
namespace MediaWiki\Extension\Notifications\Mapper;
|
|
|
|
|
2022-11-02 21:34:17 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Model\Event;
|
2022-11-02 20:47:04 +00:00
|
|
|
use MWException;
|
|
|
|
use User;
|
|
|
|
|
2014-07-18 03:58:21 +00:00
|
|
|
/**
|
2022-11-02 21:34:17 +00:00
|
|
|
* Database mapper for Event model, which is an immutable class, there should
|
2014-07-18 03:58:21 +00:00
|
|
|
* not be any update to it
|
|
|
|
*/
|
2022-11-02 20:47:04 +00:00
|
|
|
class EventMapper extends AbstractMapper {
|
2014-07-18 03:58:21 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert an event record
|
|
|
|
*
|
2022-11-02 21:34:17 +00:00
|
|
|
* @param Event $event
|
2022-06-23 09:17:10 +00:00
|
|
|
* @return int
|
2014-07-18 03:58:21 +00:00
|
|
|
*/
|
2022-11-02 21:34:17 +00:00
|
|
|
public function insert( Event $event ) {
|
2021-05-03 07:28:02 +00:00
|
|
|
$dbw = $this->dbFactory->getEchoDb( DB_PRIMARY );
|
2014-07-18 03:58:21 +00:00
|
|
|
|
|
|
|
$row = $event->toDbArray();
|
|
|
|
|
2018-10-26 21:36:39 +00:00
|
|
|
$dbw->insert( 'echo_event', $row, __METHOD__ );
|
2014-07-18 03:58:21 +00:00
|
|
|
|
2018-10-26 21:36:39 +00:00
|
|
|
$id = $dbw->insertId();
|
2015-10-01 13:48:52 +00:00
|
|
|
|
2018-10-26 21:36:39 +00:00
|
|
|
$listeners = $this->getMethodListeners( __FUNCTION__ );
|
|
|
|
foreach ( $listeners as $listener ) {
|
2020-06-07 00:59:39 +00:00
|
|
|
$dbw->onTransactionCommitOrIdle( $listener, __METHOD__ );
|
2014-07-18 03:58:21 +00:00
|
|
|
}
|
2018-10-26 21:36:39 +00:00
|
|
|
|
|
|
|
return $id;
|
2014-07-18 03:58:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-11-02 21:34:17 +00:00
|
|
|
* Create an Event by id
|
2014-07-18 03:58:21 +00:00
|
|
|
*
|
2017-08-09 15:20:55 +00:00
|
|
|
* @param int $id
|
2021-05-13 00:04:54 +00:00
|
|
|
* @param bool $fromPrimary
|
2022-11-02 21:34:17 +00:00
|
|
|
* @return Event|false False if it wouldn't load/unserialize
|
2014-07-18 03:58:21 +00:00
|
|
|
* @throws MWException
|
|
|
|
*/
|
2021-05-13 00:04:54 +00:00
|
|
|
public function fetchById( $id, $fromPrimary = false ) {
|
|
|
|
$db = $fromPrimary ? $this->dbFactory->getEchoDb( DB_PRIMARY ) : $this->dbFactory->getEchoDb( DB_REPLICA );
|
2014-07-18 03:58:21 +00:00
|
|
|
|
2022-11-02 21:34:17 +00:00
|
|
|
$row = $db->selectRow( 'echo_event', Event::selectFields(), [ 'event_id' => $id ], __METHOD__ );
|
2014-07-18 03:58:21 +00:00
|
|
|
|
2021-05-13 00:04:54 +00:00
|
|
|
// If the row was not found, fall back on the primary database if it makes sense to do so
|
2021-09-04 00:46:13 +00:00
|
|
|
if ( !$row && !$fromPrimary && $this->dbFactory->canRetryPrimary() ) {
|
2014-07-18 03:58:21 +00:00
|
|
|
return $this->fetchById( $id, true );
|
|
|
|
} elseif ( !$row ) {
|
2022-11-02 21:34:17 +00:00
|
|
|
throw new MWException( "No Event found with ID: $id" );
|
2014-07-18 03:58:21 +00:00
|
|
|
}
|
|
|
|
|
2022-11-02 21:34:17 +00:00
|
|
|
return Event::newFromRow( $row );
|
2014-07-18 03:58:21 +00:00
|
|
|
}
|
|
|
|
|
2016-03-04 19:23:02 +00:00
|
|
|
/**
|
|
|
|
* @param int[] $eventIds
|
|
|
|
* @param bool $deleted
|
|
|
|
*/
|
2019-10-23 10:44:35 +00:00
|
|
|
public function toggleDeleted( array $eventIds, $deleted ) {
|
2021-05-03 07:28:02 +00:00
|
|
|
$dbw = $this->dbFactory->getEchoDb( DB_PRIMARY );
|
2016-03-04 19:23:02 +00:00
|
|
|
|
|
|
|
$selectDeleted = $deleted ? 0 : 1;
|
|
|
|
$setDeleted = $deleted ? 1 : 0;
|
2018-10-26 19:46:58 +00:00
|
|
|
$dbw->update(
|
2016-03-04 19:23:02 +00:00
|
|
|
'echo_event',
|
2016-12-05 18:51:07 +00:00
|
|
|
[
|
2016-03-04 19:23:02 +00:00
|
|
|
'event_deleted' => $setDeleted,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
|
|
|
[
|
2016-03-04 19:23:02 +00:00
|
|
|
'event_deleted' => $selectDeleted,
|
|
|
|
'event_id' => $eventIds,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2016-03-04 19:23:02 +00:00
|
|
|
__METHOD__
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-07-27 14:52:18 +00:00
|
|
|
/**
|
|
|
|
* Fetch events associated with a page
|
|
|
|
*
|
|
|
|
* @param int $pageId
|
2022-11-02 21:34:17 +00:00
|
|
|
* @return Event[] Events
|
2016-07-27 14:52:18 +00:00
|
|
|
*/
|
|
|
|
public function fetchByPage( $pageId ) {
|
2016-12-05 18:51:07 +00:00
|
|
|
$events = [];
|
2019-04-10 20:09:40 +00:00
|
|
|
$seenEventIds = [];
|
2017-09-24 05:23:47 +00:00
|
|
|
$dbr = $this->dbFactory->getEchoDb( DB_REPLICA );
|
2019-04-10 20:09:40 +00:00
|
|
|
|
|
|
|
// From echo_event
|
|
|
|
$res = $dbr->select(
|
|
|
|
[ 'echo_event' ],
|
2022-11-02 21:34:17 +00:00
|
|
|
Event::selectFields(),
|
2020-06-07 00:59:39 +00:00
|
|
|
[ 'event_page_id' => $pageId ],
|
|
|
|
__METHOD__
|
2019-04-10 20:09:40 +00:00
|
|
|
);
|
|
|
|
if ( $res ) {
|
|
|
|
foreach ( $res as $row ) {
|
2022-11-02 21:34:17 +00:00
|
|
|
$event = Event::newFromRow( $row );
|
2019-04-10 20:09:40 +00:00
|
|
|
$events[] = $event;
|
|
|
|
$seenEventIds[] = $event->getId();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// From echo_target_page
|
|
|
|
$conds = [ 'etp_page' => $pageId ];
|
|
|
|
if ( $seenEventIds ) {
|
|
|
|
// Some events have both a title and target page(s).
|
|
|
|
// Skip the events that were already found in the echo_event table (the query above).
|
|
|
|
$conds[] = 'event_id NOT IN ( ' . $dbr->makeList( $seenEventIds ) . ' )';
|
|
|
|
}
|
2016-07-27 14:52:18 +00:00
|
|
|
$res = $dbr->select(
|
2016-12-05 18:51:07 +00:00
|
|
|
[ 'echo_event', 'echo_target_page' ],
|
2022-11-02 21:34:17 +00:00
|
|
|
Event::selectFields(),
|
2019-04-10 20:09:40 +00:00
|
|
|
$conds,
|
2016-07-27 14:52:18 +00:00
|
|
|
__METHOD__,
|
2021-04-28 04:51:12 +00:00
|
|
|
[ 'DISTINCT' ],
|
2016-12-05 18:51:07 +00:00
|
|
|
[ 'echo_target_page' => [ 'INNER JOIN', 'event_id=etp_event' ] ]
|
2016-07-27 14:52:18 +00:00
|
|
|
);
|
|
|
|
if ( $res ) {
|
|
|
|
foreach ( $res as $row ) {
|
2022-11-02 21:34:17 +00:00
|
|
|
$events[] = Event::newFromRow( $row );
|
2016-07-27 14:52:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $events;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch event IDs associated with a page
|
|
|
|
*
|
|
|
|
* @param int $pageId
|
|
|
|
* @return int[] Event IDs
|
|
|
|
*/
|
|
|
|
public function fetchIdsByPage( $pageId ) {
|
|
|
|
$events = $this->fetchByPage( $pageId );
|
|
|
|
$eventIds = array_map(
|
2022-11-02 21:34:17 +00:00
|
|
|
static function ( Event $event ) {
|
2016-07-27 14:52:18 +00:00
|
|
|
return $event->getId();
|
|
|
|
},
|
|
|
|
$events
|
|
|
|
);
|
|
|
|
return $eventIds;
|
|
|
|
}
|
|
|
|
|
2016-03-04 19:23:02 +00:00
|
|
|
/**
|
|
|
|
* Fetch events unread by a user and associated with a page
|
|
|
|
*
|
|
|
|
* @param User $user
|
|
|
|
* @param int $pageId
|
2022-11-02 21:34:17 +00:00
|
|
|
* @return Event[]
|
2016-03-04 19:23:02 +00:00
|
|
|
*/
|
|
|
|
public function fetchUnreadByUserAndPage( User $user, $pageId ) {
|
2017-09-24 05:23:47 +00:00
|
|
|
$dbr = $this->dbFactory->getEchoDb( DB_REPLICA );
|
2022-11-02 21:34:17 +00:00
|
|
|
$fields = array_merge( Event::selectFields(), [ 'notification_timestamp' ] );
|
2016-03-04 19:23:02 +00:00
|
|
|
|
|
|
|
$res = $dbr->select(
|
2016-12-05 18:51:07 +00:00
|
|
|
[ 'echo_event', 'echo_notification', 'echo_target_page' ],
|
2019-03-02 20:25:33 +00:00
|
|
|
$fields,
|
2016-12-05 18:51:07 +00:00
|
|
|
[
|
2016-03-04 19:23:02 +00:00
|
|
|
'event_deleted' => 0,
|
2016-09-07 17:38:12 +00:00
|
|
|
'notification_user' => $user->getId(),
|
2016-03-04 19:23:02 +00:00
|
|
|
'notification_read_timestamp' => null,
|
2016-09-07 17:38:12 +00:00
|
|
|
'etp_page' => $pageId,
|
2016-12-05 18:51:07 +00:00
|
|
|
],
|
2016-03-04 19:23:02 +00:00
|
|
|
__METHOD__,
|
2019-12-21 05:45:14 +00:00
|
|
|
[],
|
2016-12-05 18:51:07 +00:00
|
|
|
[
|
|
|
|
'echo_target_page' => [ 'INNER JOIN', 'etp_event=event_id' ],
|
|
|
|
'echo_notification' => [ 'INNER JOIN', [ 'notification_event=event_id' ] ],
|
|
|
|
]
|
2016-03-04 19:23:02 +00:00
|
|
|
);
|
|
|
|
|
2016-12-05 18:51:07 +00:00
|
|
|
$data = [];
|
2016-03-04 19:23:02 +00:00
|
|
|
foreach ( $res as $row ) {
|
2022-11-02 21:34:17 +00:00
|
|
|
$data[] = Event::newFromRow( $row );
|
2016-03-04 19:23:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2019-04-23 00:16:21 +00:00
|
|
|
/**
|
|
|
|
* Find out which of the given event IDs are orphaned, and delete them.
|
|
|
|
*
|
|
|
|
* An event is orphaned if it is not referred to by any rows in the echo_notification or
|
|
|
|
* echo_email_batch tables. If $ignoreUserId is set, rows for that user are not considered when
|
|
|
|
* determining orphanhood; if $ignoreUserTable is set, this only applies to that table.
|
2021-05-13 00:04:54 +00:00
|
|
|
* Use this when you've just recently deleted rows related to this user on the primary database, so that
|
2019-04-23 00:16:21 +00:00
|
|
|
* this function won't refuse to delete recently-orphaned events because it still sees the
|
|
|
|
* recently-deleted rows on the replica.
|
|
|
|
*
|
|
|
|
* @param array $eventIds Event IDs to check to see if they have become orphaned
|
|
|
|
* @param int|null $ignoreUserId Allow events to be deleted if the only referring rows
|
|
|
|
* have this user ID
|
|
|
|
* @param string|null $ignoreUserTable Restrict $ignoreUserId to this table only
|
|
|
|
* ('echo_notification' or 'echo_email_batch')
|
|
|
|
*/
|
|
|
|
public function deleteOrphanedEvents( array $eventIds, $ignoreUserId = null, $ignoreUserTable = null ) {
|
2021-05-03 07:28:02 +00:00
|
|
|
$dbw = $this->dbFactory->getEchoDb( DB_PRIMARY );
|
2019-04-23 00:16:21 +00:00
|
|
|
$dbr = $this->dbFactory->getEchoDb( DB_REPLICA );
|
|
|
|
|
|
|
|
$notifJoinConds = [];
|
|
|
|
$emailJoinConds = [];
|
|
|
|
if ( $ignoreUserId !== null ) {
|
|
|
|
if ( $ignoreUserTable === null || $ignoreUserTable === 'echo_notification' ) {
|
|
|
|
$notifJoinConds[] = 'notification_user != ' . $dbr->addQuotes( $ignoreUserId );
|
|
|
|
}
|
|
|
|
if ( $ignoreUserTable === null || $ignoreUserTable === 'echo_email_batch' ) {
|
|
|
|
$emailJoinConds[] = 'eeb_user_id != ' . $dbr->addQuotes( $ignoreUserId );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$orphanedEventIds = $dbr->selectFieldValues(
|
|
|
|
[ 'echo_event', 'echo_notification', 'echo_email_batch' ],
|
|
|
|
'event_id',
|
|
|
|
[
|
|
|
|
'event_id' => $eventIds,
|
|
|
|
'notification_timestamp' => null,
|
|
|
|
'eeb_user_id' => null
|
|
|
|
],
|
|
|
|
__METHOD__,
|
|
|
|
[],
|
|
|
|
[
|
|
|
|
'echo_notification' => [ 'LEFT JOIN', array_merge( [
|
|
|
|
'notification_event=event_id'
|
|
|
|
], $notifJoinConds ) ],
|
|
|
|
'echo_email_batch' => [ 'LEFT JOIN', array_merge( [
|
|
|
|
'eeb_event_id=event_id'
|
|
|
|
], $emailJoinConds ) ]
|
|
|
|
]
|
|
|
|
);
|
|
|
|
if ( $orphanedEventIds ) {
|
2020-06-07 00:59:39 +00:00
|
|
|
$dbw->delete( 'echo_event', [ 'event_id' => $orphanedEventIds ], __METHOD__ );
|
|
|
|
$dbw->delete( 'echo_target_page', [ 'etp_event' => $orphanedEventIds ], __METHOD__ );
|
2019-04-23 00:16:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-18 03:58:21 +00:00
|
|
|
}
|