Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
21.05% covered (danger)
21.05%
16 / 76
26.32% covered (danger)
26.32%
5 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Notification
21.33% covered (danger)
21.33%
16 / 75
26.32% covered (danger)
26.32%
5 / 19
468.14
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 insert
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
20
 newFromRow
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 toDbArray
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getEvent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTimestamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReadTimestamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRead
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBundleHash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTargetPages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setBundledNotifications
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBundledNotifications
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canBeBundled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBundlingKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setBundledElements
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSortingKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 selectFields
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Notifications\Model;
4
5use InvalidArgumentException;
6use MediaWiki\Extension\Notifications\Bundleable;
7use MediaWiki\Extension\Notifications\Hooks\HookRunner;
8use MediaWiki\Extension\Notifications\Mapper\NotificationMapper;
9use MediaWiki\Extension\Notifications\Notifier;
10use MediaWiki\Extension\Notifications\NotifUser;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\User\User;
13use stdClass;
14
15class Notification extends AbstractEntity implements Bundleable {
16
17    /**
18     * @var User
19     */
20    protected $user;
21
22    /**
23     * @var Event
24     */
25    protected $event;
26
27    /**
28     * The target page object for the notification if there is one. Null means
29     * the information has not been loaded.
30     *
31     * @var TargetPage[]|null
32     */
33    protected $targetPages;
34
35    /**
36     * @var string
37     */
38    protected $timestamp;
39
40    /**
41     * @var string|null
42     */
43    protected $readTimestamp;
44
45    /**
46     * The hash used to determine if a set of event could be bundled
47     * @var string
48     */
49    protected $bundleHash = '';
50
51    /**
52     * @var Notification[]
53     */
54    protected $bundledNotifications;
55
56    /**
57     * Do not use this constructor.
58     */
59    protected function __construct() {
60    }
61
62    /**
63     * Creates an Notification object based on event and user
64     * @param array $info The following keys are required:
65     * - 'event' The Event being notified about.
66     * - 'user' The User being notified.
67     * @return Notification
68     */
69    public static function create( array $info ) {
70        $obj = new Notification();
71        static $validFields = [ 'event', 'user' ];
72
73        foreach ( $validFields as $field ) {
74            if ( isset( $info[$field] ) ) {
75                $obj->$field = $info[$field];
76            } else {
77                throw new InvalidArgumentException( "Field $field is required" );
78            }
79        }
80
81        if ( !$obj->user instanceof User ) {
82            throw new InvalidArgumentException( 'Invalid user parameter, expected: User object' );
83        }
84
85        if ( !$obj->event instanceof Event ) {
86            throw new InvalidArgumentException( 'Invalid event parameter, expected: Event object' );
87        }
88
89        // Notification timestamp should be the same as event timestamp
90        $obj->timestamp = $obj->event->getTimestamp();
91        // Safe fallback
92        if ( !$obj->timestamp ) {
93            $obj->timestamp = wfTimestampNow();
94        }
95
96        // @Todo - Database insert logic should not be inside the model
97        $obj->insert();
98
99        return $obj;
100    }
101
102    /**
103     * Adds this new notification object to the backend storage.
104     */
105    protected function insert() {
106        global $wgEchoNotifications;
107
108        $notifMapper = new NotificationMapper();
109
110        $services = MediaWikiServices::getInstance();
111        $hookRunner = new HookRunner( $services->getHookContainer() );
112        // Get the bundle key for this event if web bundling is enabled
113        $bundleKey = '';
114        if ( !empty( $wgEchoNotifications[$this->event->getType()]['bundle']['web'] ) ) {
115            Notifier::getBundleRules( $this->event, $bundleKey );
116        }
117
118        if ( $bundleKey ) {
119            $hash = md5( $bundleKey );
120            $this->bundleHash = $hash;
121        }
122
123        $notifUser = NotifUser::newFromUser( $this->user );
124
125        // Add listener to refresh notification count upon insert
126        $notifMapper->attachListener( 'insert', 'refresh-notif-count',
127            static function () use ( $notifUser ) {
128                $notifUser->resetNotificationCount();
129            }
130        );
131
132        $notifMapper->insert( $this );
133
134        if ( $this->event->getCategory() === 'edit-user-talk' ) {
135            $services->getTalkPageNotificationManager()
136                ->setUserHasNewMessages( $this->user );
137        }
138        $hookRunner->onEchoCreateNotificationComplete( $this );
139    }
140
141    /**
142     * Load a notification record from std class
143     * @param stdClass $row
144     * @param TargetPage[]|null $targetPages An array of TargetPage instances, or null if not loaded.
145     * @return Notification|false False if failed to load/unserialize
146     */
147    public static function newFromRow( $row, array $targetPages = null ) {
148        $notification = new Notification();
149
150        if ( property_exists( $row, 'event_type' ) ) {
151            $notification->event = Event::newFromRow( $row );
152        } else {
153            $notification->event = Event::newFromID( $row->notification_event );
154        }
155
156        if ( $notification->event === false ) {
157            return false;
158        }
159
160        $notification->targetPages = $targetPages;
161        $notification->user = User::newFromId( $row->notification_user );
162        // Notification timestamp should never be empty
163        $notification->timestamp = wfTimestamp( TS_MW, $row->notification_timestamp );
164        $notification->readTimestamp = wfTimestampOrNull( TS_MW, $row->notification_read_timestamp );
165        $notification->bundleHash = $row->notification_bundle_hash;
166
167        return $notification;
168    }
169
170    /**
171     * Convert object property to database row array
172     * @return array
173     */
174    public function toDbArray() {
175        return [
176            'notification_event' => $this->event->getId(),
177            'notification_user' => $this->user->getId(),
178            'notification_timestamp' => $this->timestamp,
179            'notification_read_timestamp' => $this->readTimestamp,
180            'notification_bundle_hash' => $this->bundleHash,
181        ];
182    }
183
184    /**
185     * Getter method
186     * @return Event The event for this notification
187     */
188    public function getEvent() {
189        return $this->event;
190    }
191
192    /**
193     * Getter method
194     * @return User The recipient of this notification
195     */
196    public function getUser() {
197        return $this->user;
198    }
199
200    /**
201     * Getter method
202     * @return string Notification creation timestamp
203     */
204    public function getTimestamp() {
205        return $this->timestamp;
206    }
207
208    /**
209     * Getter method
210     * @return string|null Notification read timestamp
211     */
212    public function getReadTimestamp() {
213        return $this->readTimestamp;
214    }
215
216    public function isRead() {
217        return $this->getReadTimestamp() !== null;
218    }
219
220    /**
221     * Getter method
222     * @return string|null Notification bundle hash
223     */
224    public function getBundleHash() {
225        return $this->bundleHash;
226    }
227
228    /**
229     * Getter method. Returns an array of TargetPage's, or null if they have
230     * not been loaded.
231     *
232     * @return TargetPage[]|null
233     */
234    public function getTargetPages() {
235        return $this->targetPages;
236    }
237
238    public function setBundledNotifications( array $notifications ) {
239        $this->bundledNotifications = $notifications;
240    }
241
242    public function getBundledNotifications() {
243        return $this->bundledNotifications;
244    }
245
246    /**
247     * @inheritDoc
248     */
249    public function canBeBundled() {
250        return !$this->isRead();
251    }
252
253    /**
254     * @inheritDoc
255     */
256    public function getBundlingKey() {
257        return $this->getBundleHash();
258    }
259
260    /**
261     * @inheritDoc
262     */
263    public function setBundledElements( array $bundleables ) {
264        $this->setBundledNotifications( $bundleables );
265    }
266
267    /**
268     * @inheritDoc
269     */
270    public function getSortingKey() {
271        return ( $this->isRead() ? '0' : '1' ) . '_' . $this->getTimestamp();
272    }
273
274    /**
275     * Return the list of fields that should be selected to create
276     * a new event with Notification::newFromRow
277     * @return string[]
278     */
279    public static function selectFields() {
280        return array_merge( Event::selectFields(), [
281            'notification_event',
282            'notification_user',
283            'notification_timestamp',
284            'notification_read_timestamp',
285            'notification_bundle_hash',
286        ] );
287    }
288}
289
290class_alias( Notification::class, 'EchoNotification' );