Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.56% covered (warning)
75.56%
68 / 90
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserNotificationGateway
75.56% covered (warning)
75.56%
68 / 90
42.86% covered (danger)
42.86%
3 / 7
24.27
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getDB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 markRead
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
5.00
 markUnRead
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 markAllRead
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
2.00
 getCappedNotificationCount
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
3.00
 getUnreadNotifications
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Notifications\Gateway;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Extension\Notifications\DbFactory;
7use MediaWiki\Extension\Notifications\NotifUser;
8use MediaWiki\User\UserIdentity;
9
10/**
11 * Database gateway which handles direct database interaction with the
12 * echo_notification & echo_event for a user, that wouldn't require
13 * loading data into models
14 */
15class UserNotificationGateway {
16
17    /**
18     * @var DbFactory
19     */
20    protected $dbFactory;
21
22    /**
23     * @var UserIdentity
24     */
25    protected $user;
26
27    /**
28     * The tables for this gateway.
29     *
30     * @var string
31     */
32    protected static $eventTable = 'echo_event';
33
34    /**
35     * The tables for this gateway.
36     *
37     * @var string
38     */
39    protected static $notificationTable = 'echo_notification';
40
41    /**
42     * @var Config
43     */
44    private $config;
45
46    /**
47     * @param UserIdentity $user
48     * @param DbFactory $dbFactory
49     * @param Config $config
50     */
51    public function __construct( UserIdentity $user, DbFactory $dbFactory, Config $config ) {
52        $this->user = $user;
53        $this->dbFactory = $dbFactory;
54        $this->config = $config;
55    }
56
57    public function getDB( $dbSource ) {
58        return $this->dbFactory->getEchoDb( $dbSource );
59    }
60
61    /**
62     * Mark notifications as read
63     * @param array $eventIDs
64     * @return bool Returns true when data has been updated in DB, false on
65     *   failure, or when there was nothing to update
66     */
67    public function markRead( array $eventIDs ) {
68        if ( !$eventIDs ) {
69            return false;
70        }
71
72        $dbw = $this->getDB( DB_PRIMARY );
73        if ( $dbw->isReadOnly() ) {
74            return false;
75        }
76
77        $success = true;
78        foreach (
79            array_chunk( $eventIDs, $this->config->get( 'UpdateRowsPerQuery' ) ) as $batch
80        ) {
81            $dbw->newUpdateQueryBuilder()
82                ->update( self::$notificationTable )
83                ->set( [ 'notification_read_timestamp' => $dbw->timestamp( wfTimestampNow() ) ] )
84                ->where( [
85                    'notification_user' => $this->user->getId(),
86                    'notification_event' => $batch,
87                    'notification_read_timestamp' => null,
88                ] )
89                ->caller( __METHOD__ )
90                ->execute();
91            $success = $dbw->affectedRows() && $success;
92        }
93
94        return $success;
95    }
96
97    /**
98     * Mark notifications as unread
99     * @param array $eventIDs
100     * @return bool Returns true when data has been updated in DB, false on
101     *   failure, or when there was nothing to update
102     */
103    public function markUnRead( array $eventIDs ) {
104        if ( !$eventIDs ) {
105            return false;
106        }
107
108        $dbw = $this->getDB( DB_PRIMARY );
109        if ( $dbw->isReadOnly() ) {
110            return false;
111        }
112
113        $success = true;
114        foreach (
115            array_chunk( $eventIDs, $this->config->get( 'UpdateRowsPerQuery' ) ) as $batch
116        ) {
117            $dbw->newUpdateQueryBuilder()
118                ->update( self::$notificationTable )
119                ->set( [ 'notification_read_timestamp' => null ] )
120                ->where( [
121                    'notification_user' => $this->user->getId(),
122                    'notification_event' => $batch,
123                    $dbw->expr( 'notification_read_timestamp', '!=', null ),
124                ] )
125                ->caller( __METHOD__ )
126                ->execute();
127            $success = $dbw->affectedRows() && $success;
128        }
129        return $success;
130    }
131
132    /**
133     * Mark all notification as read, use NotifUser::markAllRead() instead
134     * @deprecated may need this when running in a job or revive this when we
135     * have updateJoin()
136     */
137    public function markAllRead() {
138        $dbw = $this->getDB( DB_PRIMARY );
139        if ( $dbw->isReadOnly() ) {
140            return false;
141        }
142
143        $dbw->newUpdateQueryBuilder()
144            ->update( self::$notificationTable )
145            ->set( [ 'notification_read_timestamp' => $dbw->timestamp( wfTimestampNow() ) ] )
146            ->where( [
147                'notification_user' => $this->user->getId(),
148                'notification_read_timestamp' => null,
149            ] )
150            ->caller( __METHOD__ )
151            ->execute();
152
153        return true;
154    }
155
156    /**
157     * Get notification count for the types specified
158     * @param int $dbSource use primary database or replica storage to pull count
159     * @param array $eventTypesToLoad event types to retrieve
160     * @param int $cap Max count
161     * @return int
162     */
163    public function getCappedNotificationCount(
164        $dbSource,
165        array $eventTypesToLoad = [],
166        $cap = NotifUser::MAX_BADGE_COUNT
167    ) {
168        // double check
169        if ( !in_array( $dbSource, [ DB_REPLICA, DB_PRIMARY ] ) ) {
170            $dbSource = DB_REPLICA;
171        }
172
173        if ( !$eventTypesToLoad ) {
174            return 0;
175        }
176
177        $db = $this->getDB( $dbSource );
178        return $db->newSelectQueryBuilder()
179            ->select( '1' )
180            ->from( self::$notificationTable )
181            ->leftJoin( self::$eventTable, null, 'notification_event=event_id' )
182            ->where( [
183                'notification_user' => $this->user->getId(),
184                'notification_read_timestamp' => null,
185                'event_deleted' => 0,
186                'event_type' => $eventTypesToLoad,
187            ] )
188            ->limit( $cap )
189            ->caller( __METHOD__ )
190            ->fetchRowCount();
191    }
192
193    /**
194     * IMPORTANT: should only call this function if the number of unread notification
195     * is reasonable, for example, unread notification count is less than the max
196     * display defined in oNotifUser::MAX_BADGE_COUNT
197     * @param string $type
198     * @return int[]
199     */
200    public function getUnreadNotifications( $type ) {
201        $dbr = $this->getDB( DB_REPLICA );
202        $res = $dbr->newSelectQueryBuilder()
203            ->select( 'notification_event' )
204            ->from( self::$notificationTable )
205            ->join( self::$eventTable, null, 'notification_event = event_id' )
206            ->where( [
207                'notification_user' => $this->user->getId(),
208                'notification_read_timestamp' => null,
209                'event_deleted' => 0,
210                'event_type' => $type,
211            ] )
212            ->caller( __METHOD__ )
213            ->fetchResultSet();
214
215        $eventIds = [];
216        foreach ( $res as $row ) {
217            $eventIds[$row->notification_event] = $row->notification_event;
218        }
219
220        return $eventIds;
221    }
222
223}