Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.60% covered (warning)
80.60%
241 / 299
37.50% covered (danger)
37.50%
12 / 32
CRAP
0.00% covered (danger)
0.00%
0 / 1
manager
80.60% covered (warning)
80.60%
241 / 299
37.50% covered (danger)
37.50%
12 / 32
193.14
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 load_notifications
40.00% covered (danger)
40.00%
4 / 10
0.00% covered (danger)
0.00%
0 / 1
4.94
 mark_notifications_read
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 mark_notifications
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
5.26
 mark_notifications_read_by_parent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 mark_notifications_by_parent
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 mark_notifications_by_id
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 add_notifications
96.77% covered (success)
96.77%
30 / 31
0.00% covered (danger)
0.00%
0 / 1
4
 add_notifications_for_users
92.11% covered (success)
92.11%
35 / 38
0.00% covered (danger)
0.00%
0 / 1
11.06
 update_notifications
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 update_notification
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 delete_notifications
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 delete_notifications_by_types
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 get_subscription_types
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
7
 get_subscription_methods
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 get_subscription_methods_instances
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 get_available_subscription_methods
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 get_user_notifications
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 get_global_subscriptions
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
10.01
 add_subscription
61.29% covered (warning)
61.29%
19 / 31
0.00% covered (danger)
0.00%
0 / 1
8.09
 delete_subscription
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
5
 disable_notifications
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 purge_notifications
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 enable_notifications
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 prune_notifications
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 get_default_methods
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 get_item_type_class
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 get_method_class
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 load_object
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
 get_notification_type_id
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 get_notification_type_ids
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 get_notified_users
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\notification;
15
16use phpbb\exception\runtime_exception;
17use Symfony\Component\DependencyInjection\ContainerInterface;
18
19/**
20* Notifications service class
21*/
22class manager
23{
24    /** @var array */
25    protected $notification_types;
26
27    /** @var array */
28    protected $subscription_types;
29
30    /** @var method\method_interface[] */
31    protected $notification_methods;
32
33    /** @var ContainerInterface */
34    protected $phpbb_container;
35
36    /** @var \phpbb\user_loader */
37    protected $user_loader;
38
39    /** @var \phpbb\event\dispatcher_interface */
40    protected $phpbb_dispatcher;
41
42    /** @var \phpbb\db\driver\driver_interface */
43    protected $db;
44
45    /** @var \phpbb\cache\service */
46    protected $cache;
47
48    /** @var \phpbb\language\language */
49    protected $language;
50
51    /** @var \phpbb\user */
52    protected $user;
53
54    /** @var string */
55    protected $notification_types_table;
56
57    /** @var string */
58    protected $user_notifications_table;
59
60    /**
61    * Notification Constructor
62    *
63    * @param array $notification_types
64    * @param array $notification_methods
65    * @param ContainerInterface $phpbb_container
66    * @param \phpbb\user_loader $user_loader
67    * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher
68    * @param \phpbb\db\driver\driver_interface $db
69    * @param \phpbb\cache\service $cache
70    * @param \phpbb\language\language $language
71    * @param \phpbb\user $user
72    * @param string $notification_types_table
73    * @param string $user_notifications_table
74    */
75    public function __construct($notification_types, $notification_methods, ContainerInterface $phpbb_container, \phpbb\user_loader $user_loader, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\language\language $language, \phpbb\user $user, $notification_types_table, $user_notifications_table)
76    {
77        $this->notification_types = $notification_types;
78        $this->notification_methods = $notification_methods;
79        $this->phpbb_container = $phpbb_container;
80
81        $this->user_loader = $user_loader;
82        $this->phpbb_dispatcher = $phpbb_dispatcher;
83        $this->db = $db;
84        $this->cache = $cache;
85        $this->language = $language;
86        $this->user = $user;
87
88        $this->notification_types_table = $notification_types_table;
89        $this->user_notifications_table = $user_notifications_table;
90    }
91
92    /**
93    * Load the user's notifications for a given method
94    *
95    * @param string $method_name
96    * @param array $options Optional options to control what notifications are loaded
97    *                notification_id        Notification id to load (or array of notification ids)
98    *                user_id                User id to load notifications for (Default: $user->data['user_id'])
99    *                order_by            Order by (Default: notification_time)
100    *                order_dir            Order direction (Default: DESC)
101    *                 limit                Number of notifications to load (Default: 5)
102    *                 start                Notifications offset (Default: 0)
103    *                 all_unread            Load all unread notifications? If set to true, count_unread is set to true (Default: false)
104    *                 count_unread        Count all unread notifications? (Default: false)
105    *                 count_total            Count all notifications? (Default: false)
106    * @return array Array of information based on the request with keys:
107    *    'notifications'        array of notification type objects
108    *    'unread_count'        number of unread notifications the user has if count_unread is true in the options
109    *    'total_count'        number of notifications the user has if count_total is true in the options
110    * @throws \phpbb\notification\exception when the method doesn't refer to a class extending \phpbb\notification\method\method_interface
111    */
112    public function load_notifications($method_name, array $options = array())
113    {
114        $method = $this->get_method_class($method_name);
115
116        if (! $method instanceof \phpbb\notification\method\method_interface)
117        {
118            throw new \phpbb\notification\exception($this->language->lang('NOTIFICATION_METHOD_INVALID', $method_name));
119        }
120        else if ($method->is_available())
121        {
122            return $method->load_notifications($options);
123        }
124        else
125        {
126            return array(
127                'notifications'        => array(),
128                'unread_count'        => 0,
129                'total_count'        => 0,
130            );
131        }
132    }
133
134    /**
135     * Mark notifications read or unread for all available methods
136     *
137     * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types
138     * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids
139     * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids
140     * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
141     *
142     * @deprecated since 3.2
143     */
144    public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false)
145    {
146        $this->mark_notifications($notification_type_name, $item_id, $user_id, $time);
147    }
148
149    /**
150    * Mark notifications read or unread for all available methods
151    *
152    * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types
153    * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids
154    * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids
155    * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
156    * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True)
157    */
158    public function mark_notifications($notification_type_name, $item_id, $user_id, $time = false, $mark_read = true)
159    {
160        if (is_array($notification_type_name))
161        {
162            $notification_type_id = $this->get_notification_type_ids($notification_type_name);
163        }
164        else if ($notification_type_name !== false)
165        {
166            $notification_type_id = $this->get_notification_type_id($notification_type_name);
167        }
168        else
169        {
170            $notification_type_id = false;
171        }
172
173        /** @var method\method_interface $method */
174        foreach ($this->get_available_subscription_methods() as $method)
175        {
176            $method->mark_notifications($notification_type_id, $item_id, $user_id, $time, $mark_read);
177        }
178    }
179
180    /**
181     * Mark notifications read or unread from a parent identifier for all available methods
182     *
183     * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
184     * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids
185     * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids
186     * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
187     *
188     * @deprecated since 3.2
189     */
190    public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false)
191    {
192        $this->mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time);
193    }
194
195    /**
196    * Mark notifications read or unread from a parent identifier for all available methods
197    *
198    * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
199    * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids
200    * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids
201    * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
202    * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True)
203    */
204    public function mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false, $mark_read = true)
205    {
206        if (is_array($notification_type_name))
207        {
208            $notification_type_id = $this->get_notification_type_ids($notification_type_name);
209        }
210        else
211        {
212            $notification_type_id = $this->get_notification_type_id($notification_type_name);
213        }
214
215        /** @var method\method_interface $method */
216        foreach ($this->get_available_subscription_methods() as $method)
217        {
218            $method->mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time, $mark_read);
219        }
220    }
221
222    /**
223    * Mark notifications read or unread for a given method
224    *
225    * @param string $method_name
226    * @param array $notification_id Notification id or array of notification ids.
227    * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False)
228    * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True)
229    */
230    public function mark_notifications_by_id($method_name, $notification_id, $time = false, $mark_read = true)
231    {
232        $method = $this->get_method_class($method_name);
233
234        if ($method instanceof \phpbb\notification\method\method_interface && $method->is_available())
235        {
236            $method->mark_notifications_by_id($notification_id, $time, $mark_read);
237        }
238    }
239
240    /**
241    * Add a notification
242    *
243    * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
244    *            Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive
245    *             a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array
246    * @param array $data Data specific for this type that will be inserted
247    * @param array $options Optional options to control what notifications are loaded
248    *             ignore_users    array of data to specify which users should not receive certain types of notifications
249    * @return array Information about what users were notified and how they were notified
250    */
251    public function add_notifications($notification_type_name, $data, array $options = array())
252    {
253        $options = array_merge(array(
254            'ignore_users'        => array(),
255        ), $options);
256
257        $notified_users = [];
258        $add_notifications_override = false;
259
260        /**
261        * Get notification data before find_users_for_notification() execute
262        *
263        * @event core.notification_manager_add_notifications_before
264        * @var    bool            add_notifications_override    Flag indicating whether function should return after event
265        * @var    array|string    notification_type_name        Type identifier or array of item types
266        * @var    string            data                        Data specific for this notification type that will be inserted
267        * @var    array             notified_users                Array of notified users
268        * @var    string            options                        Optional options to control what notifications are loaded
269        * @since 3.3.6-RC1
270        */
271        $vars = [
272            'add_notifications_override',
273            'notification_type_name',
274            'data',
275            'notified_users',
276            'options',
277        ];
278        extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications_before', compact($vars)));
279
280        if ($add_notifications_override)
281        {
282            return $notified_users;
283        }
284
285        if (is_array($notification_type_name))
286        {
287            $temp_options = $options;
288
289            foreach ($notification_type_name as $type)
290            {
291                $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users;
292                $notified_users += $this->add_notifications($type, $data, $temp_options);
293            }
294
295            return $notified_users;
296        }
297
298        // find out which users want to receive this type of notification
299        $notify_users = $this->get_item_type_class($notification_type_name)->find_users_for_notification($data, $options);
300
301        /**
302        * Allow filtering the notify_users array for a notification that is about to be sent.
303        * Here, $notify_users is already filtered by f_read and the ignored list included in the options variable
304        *
305        * @event core.notification_manager_add_notifications
306        * @var    string    notification_type_name        The notification type identifier
307        * @var    array     data                        Data specific for the notification_type_name used will be inserted
308        * @var    array     notify_users                The array of userid that are going to be notified for this notification. Set to array() to cancel.
309        * @var    array     options                        The options that were used when this method was called (read only)
310        *
311        * @since 3.1.3-RC1
312        */
313        $vars = array(
314            'notification_type_name',
315            'data',
316            'notify_users',
317            'options',
318        );
319        extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications', compact($vars)));
320
321        $this->add_notifications_for_users($notification_type_name, $data, $notify_users);
322
323        return $notify_users;
324    }
325
326    /**
327    * Add a notification for specific users
328    *
329    * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
330    * @param array $data Data specific for this type that will be inserted
331    * @param array $notify_users User list to notify
332    */
333    public function add_notifications_for_users($notification_type_name, $data, $notify_users)
334    {
335        if (is_array($notification_type_name))
336        {
337            foreach ($notification_type_name as $type)
338            {
339                $this->add_notifications_for_users($type, $data, $notify_users);
340            }
341
342            return;
343        }
344
345        $notification_type_id = $this->get_notification_type_id($notification_type_name);
346
347        $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data);
348
349        $user_ids = array();
350        $notification_methods = array();
351
352        // Never send notifications to the anonymous user!
353        unset($notify_users[ANONYMOUS]);
354
355        // Make sure not to send new notifications to users who've already been notified about this item
356        // This may happen when an item was added, but now new users are able to see the item
357        // We remove each user which was already notified by at least one method.
358        /** @var method\method_interface $method */
359        foreach ($this->get_subscription_methods_instances() as $method)
360        {
361            $notified_users = $method->get_notified_users($notification_type_id, array('item_id' => $item_id));
362
363            foreach ($notified_users as $user => $notifications)
364            {
365                unset($notify_users[$user]);
366            }
367        }
368
369        /**
370        * Allow filtering the $notify_users array by $notification_type_name for a notification that is about to be sent.
371        * Here, $notify_users is already filtered from users who've already been notified.
372        *
373        * @event core.notification_manager_add_notifications_for_users_modify_data
374        * @var    string    notification_type_name        The notification type identifier
375        * @var    array     data                        Data specific for this type that will be inserted
376        * @var    array     notify_users                User list to notify
377        *
378        * @since 3.2.10-RC1
379        * @since 3.3.1-RC1
380        */
381        $vars = [
382            'notification_type_name',
383            'data',
384            'notify_users',
385        ];
386        extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications_for_users_modify_data', compact($vars)));
387
388        if (!count($notify_users))
389        {
390            return;
391        }
392
393        // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications)
394        $notification = $this->get_item_type_class($notification_type_name);
395        $pre_create_data = $notification->pre_create_insert_array($data, $notify_users);
396        unset($notification);
397
398        // Go through each user so we can insert a row in the DB and then notify them by their desired means
399        foreach ($notify_users as $user => $methods)
400        {
401            $notification = $this->get_item_type_class($notification_type_name);
402
403            $notification->user_id = (int) $user;
404
405            // Generate the insert_array
406            $notification->create_insert_array($data, $pre_create_data);
407
408            // Users are needed to send notifications
409            $user_ids = array_merge($user_ids, $notification->users_to_query());
410
411            foreach ($methods as $method)
412            {
413                // Do not load non-existent notification methods
414                if (!isset($this->notification_methods[$method]))
415                {
416                    continue;
417                }
418
419                // Setup the notification methods and add the notification to the queue
420                if (!isset($notification_methods[$method]))
421                {
422                    $notification_methods[$method] = $this->get_method_class($method);
423                }
424                $notification_methods[$method]->add_to_queue($notification);
425            }
426        }
427
428        // We need to load all of the users to send notifications
429        $this->user_loader->load_users($user_ids);
430
431        // run the queue for each method to send notifications
432        foreach ($notification_methods as $method)
433        {
434            $method->notify();
435        }
436    }
437
438    /**
439    * Update notification
440    *
441    * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)
442    * @param array $data Data specific for this type that will be updated
443    * @param array $options
444    */
445    public function update_notifications($notification_type_name, array $data, array $options = array())
446    {
447        if (is_array($notification_type_name))
448        {
449            foreach ($notification_type_name as $type)
450            {
451                $this->update_notifications($type, $data);
452            }
453
454            return;
455        }
456
457        $this->update_notification($this->get_item_type_class($notification_type_name), $data, $options);
458    }
459
460    /**
461    * Update a notification
462    *
463    * @param \phpbb\notification\type\type_interface $notification The notification
464    * @param array $data Data specific for this type that will be updated
465    * @param array $options
466    */
467    public function update_notification(\phpbb\notification\type\type_interface $notification, array $data, array $options = array())
468    {
469        if (empty($options))
470        {
471            $options['item_id'] = $notification->get_item_id($data);
472        }
473
474        /** @var method\method_interface $method */
475        foreach ($this->get_available_subscription_methods() as $method)
476        {
477            $method->update_notification($notification, $data, $options);
478        }
479    }
480
481    /**
482     * Delete notifications of specified type
483     *
484     * @param string $notification_type_name Type identifier
485     * @param int|array $item_id Identifier within the type (or array of ids)
486     * @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked)
487     * @param mixed $user_id User id (Default: false; not checked)
488     *
489     * @return void
490    */
491    public function delete_notifications(string $notification_type_name, $item_id, $parent_id = false, $user_id = false): void
492    {
493        $notification_type_id = $this->get_notification_type_id($notification_type_name);
494
495        /** @var method\method_interface $method */
496        foreach ($this->get_available_subscription_methods() as $method)
497        {
498            $method->delete_notifications($notification_type_id, $item_id, $parent_id, $user_id);
499        }
500    }
501
502    /**
503     * Delete notifications specified by multiple types
504     *
505     * @param array $notification_type_names Array of item types (only acceptable if the $item_id is identical for the specified types)
506     * @param int|array $item_id Identifier within the type (or array of ids)
507     * @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked)
508     * @param mixed $user_id User id (Default: false; not checked)
509     *
510     * @return void
511     */
512    public function delete_notifications_by_types(array $notification_type_names, $item_id, $parent_id = false, $user_id = false): void
513    {
514        foreach ($notification_type_names as $type)
515        {
516            $this->delete_notifications($type, $item_id, $parent_id, $user_id);
517        }
518    }
519
520    /**
521    * Get all of the subscription types
522    *
523    * @return array Array of item types
524    */
525    public function get_subscription_types()
526    {
527        if ($this->subscription_types === null)
528        {
529            $this->subscription_types = array();
530
531            foreach ($this->notification_types as $type_name => $data)
532            {
533                /** @var type\base $type */
534                $type = $this->get_item_type_class($type_name);
535
536                if ($type instanceof \phpbb\notification\type\type_interface && $type->is_available())
537                {
538                    $options = array_merge(array(
539                        'type'    => $type,
540                        'id'    => $type->get_type(),
541                        'lang'    => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()),
542                        'group'    => 'NOTIFICATION_GROUP_MISCELLANEOUS',
543                    ), (($type::$notification_option !== false) ? $type::$notification_option : array()));
544
545                    $this->subscription_types[$options['group']][$options['id']] = $options;
546                }
547            }
548
549            // Move Miscellaneous to the very last section
550            if (isset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']))
551            {
552                $miscellaneous = $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'];
553                unset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']);
554                $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous;
555            }
556        }
557
558        return $this->subscription_types;
559    }
560
561    /**
562    * Get all of the subscription methods
563    *
564    * @return array Array of methods
565    */
566    public function get_subscription_methods()
567    {
568        $subscription_methods = array();
569
570        /** @var method\method_interface $method */
571        foreach ($this->get_available_subscription_methods() as $method_name => $method)
572        {
573            $subscription_methods[$method_name] = array(
574                'method'    => $method,
575                'id'        => $method->get_type(),
576                'lang'        => str_replace('.', '_', strtoupper($method->get_type())),
577            );
578        }
579
580        return $subscription_methods;
581    }
582
583    /**
584    * Get all of the subscription methods
585    *
586    * @return array Array of method's instances
587    */
588    private function get_subscription_methods_instances()
589    {
590        $subscription_methods = array();
591
592        foreach ($this->notification_methods as $method_name => $data)
593        {
594            $method = $this->get_method_class($method_name);
595
596            if ($method instanceof \phpbb\notification\method\method_interface)
597            {
598                $subscription_methods[$method_name] = $method;
599            }
600        }
601
602        return $subscription_methods;
603    }
604
605    /**
606    * Get all of the available subscription methods
607    *
608    * @return array Array of method's instances
609    */
610    private function get_available_subscription_methods()
611    {
612        $subscription_methods = array();
613
614        /** @var method\method_interface $method */
615        foreach ($this->get_subscription_methods_instances() as $method_name => $method)
616        {
617            if ($method->is_available())
618            {
619                $subscription_methods[$method_name] = $method;
620            }
621        }
622
623        return $subscription_methods;
624    }
625
626
627    /**
628    * Get user's notification data
629    *
630    * @param int $user_id The user_id of the user to get the notifications for
631    *
632    * @return array User's notification
633    */
634    protected function get_user_notifications($user_id)
635    {
636        $sql = 'SELECT method, notify, item_type
637                FROM ' . $this->user_notifications_table . '
638                WHERE user_id = ' . (int) $user_id . '
639                    AND item_id = 0';
640
641        $result = $this->db->sql_query($sql);
642        $user_notifications = array();
643
644        while ($row = $this->db->sql_fetchrow($result))
645        {
646            $user_notifications[$row['item_type']][] = $row;
647        }
648
649        $this->db->sql_freeresult($result);
650
651        return $user_notifications;
652    }
653
654    /**
655    * Get global subscriptions (item_id = 0)
656    *
657    * @param bool|int $user_id The user_id to add the subscription for (bool false for current user)
658    *
659    * @return array Subscriptions
660    */
661    public function get_global_subscriptions($user_id = false)
662    {
663        $user_id = $user_id ?: $this->user->data['user_id'];
664
665        $subscriptions = array();
666        $default_methods = $this->get_default_methods();
667
668        $user_notifications = $this->get_user_notifications($user_id);
669
670        foreach ($this->get_subscription_types() as $types)
671        {
672            foreach ($types as $id => $type)
673            {
674                $type_subscriptions = $default_methods;
675                if (!empty($user_notifications[$id]))
676                {
677                    foreach ($user_notifications[$id] as $user_notification)
678                    {
679                        $key = array_search($user_notification['method'], $type_subscriptions, true);
680                        if (!$user_notification['notify'])
681                        {
682                            if ($key !== false)
683                            {
684                                unset($type_subscriptions[$key]);
685                            }
686
687                            continue;
688                        }
689                        else if ($key === false)
690                        {
691                            $type_subscriptions[] = $user_notification['method'];
692                        }
693                    }
694                }
695
696                if (!empty($type_subscriptions))
697                {
698                    $subscriptions[$id] = $type_subscriptions;
699                }
700            }
701        }
702
703        return $subscriptions;
704    }
705
706    /**
707    * Add a subscription
708    *
709    * @param string $item_type Type identifier of the subscription
710    * @param int $item_id The id of the item
711    * @param string $method The method of the notification e.g. 'board', 'email', or 'jabber'
712    *                       (if null a subscription will be added for all the defaults methods)
713    * @param bool|int $user_id The user_id to add the subscription for (bool false for current user)
714    */
715    public function add_subscription($item_type, $item_id = 0, $method = null, $user_id = false)
716    {
717        if ($method === null)
718        {
719            foreach ($this->get_default_methods() as $method_name)
720            {
721                $this->add_subscription($item_type, $item_id, $method_name, $user_id);
722            }
723
724            return;
725        }
726
727        $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id;
728
729        $sql = 'SELECT notify
730            FROM ' . $this->user_notifications_table . "
731            WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
732                AND item_id = " . (int) $item_id . '
733                AND user_id = ' .(int) $user_id . "
734                AND method = '" . $this->db->sql_escape($method) . "'";
735        $this->db->sql_query($sql);
736        $current = $this->db->sql_fetchfield('notify');
737        $this->db->sql_freeresult();
738
739        if ($current === false)
740        {
741            $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' .
742                $this->db->sql_build_array('INSERT', array(
743                    'item_type'        => $item_type,
744                    'item_id'        => (int) $item_id,
745                    'user_id'        => (int) $user_id,
746                    'method'        => $method,
747                    'notify'        => 1,
748                ));
749            $this->db->sql_query($sql);
750        }
751        else if (!$current)
752        {
753            $sql = 'UPDATE ' . $this->user_notifications_table . "
754                SET notify = 1
755                WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
756                    AND item_id = " . (int) $item_id . '
757                    AND user_id = ' .(int) $user_id . "
758                    AND method = '" . $this->db->sql_escape($method) . "'";
759            $this->db->sql_query($sql);
760        }
761    }
762
763    /**
764    * Delete a subscription
765    *
766    * @param string $item_type Type identifier of the subscription
767    * @param int $item_id The id of the item
768    * @param string $method The method of the notification e.g. 'board', 'email', or 'jabber'
769    * @param bool|int $user_id The user_id to add the subscription for (bool false for current user)
770    */
771    public function delete_subscription($item_type, $item_id = 0, $method = null, $user_id = false)
772    {
773        if ($method === null)
774        {
775            foreach ($this->get_default_methods() as $method_name)
776            {
777                $this->delete_subscription($item_type, $item_id, $method_name, $user_id);
778            }
779
780            return;
781        }
782
783        $user_id = $user_id ?: $this->user->data['user_id'];
784
785        $sql = 'UPDATE ' . $this->user_notifications_table . "
786            SET notify = 0
787            WHERE item_type = '" . $this->db->sql_escape($item_type) . "'
788                AND item_id = " . (int) $item_id . '
789                AND user_id = ' .(int) $user_id . "
790                AND method = '" . $this->db->sql_escape($method) . "'";
791        $this->db->sql_query($sql);
792
793        if (!$this->db->sql_affectedrows())
794        {
795            $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' .
796                $this->db->sql_build_array('INSERT', array(
797                    'item_type'        => $item_type,
798                    'item_id'        => (int) $item_id,
799                    'user_id'        => (int) $user_id,
800                    'method'        => $method,
801                    'notify'        => 0,
802                ));
803            $this->db->sql_query($sql);
804        }
805    }
806
807    /**
808    * Disable all notifications of a certain type
809    *
810    * This should be called when an extension which has notification types
811    * is disabled so that all those notifications are hidden and do not
812    * cause errors
813    *
814    * @param string $notification_type_name Type identifier of the subscription
815    */
816    public function disable_notifications($notification_type_name)
817    {
818        $sql = 'UPDATE ' . $this->notification_types_table . "
819            SET notification_type_enabled = 0
820            WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'";
821        $this->db->sql_query($sql);
822    }
823
824    /**
825    * Purge all notifications of a certain type
826    *
827    * This should be called when an extension which has notification types
828    * is purged so that all those notifications are removed
829    *
830    * @param string $notification_type_name Type identifier of the subscription
831    */
832    public function purge_notifications($notification_type_name)
833    {
834        // If a notification is never used, its type will not be added to the database
835        // nor its id cached. If this method is called by an extension during the
836        // purge step, and that extension never used its notifications,
837        // get_notification_type_id() will throw an exception. However,
838        // because no notification type was added to the database,
839        // there is nothing to delete, so we can silently drop the exception.
840        try
841        {
842            $notification_type_id = $this->get_notification_type_id($notification_type_name);
843
844            /** @var method\method_interface $method */
845            foreach ($this->get_available_subscription_methods() as $method)
846            {
847                $method->purge_notifications($notification_type_id);
848            }
849
850        }
851        catch (\phpbb\notification\exception $e)
852        {
853            // Continue
854        }
855    }
856
857    /**
858    * Enable all notifications of a certain type
859    *
860    * This should be called when an extension which has notification types
861    * that was disabled is re-enabled so that all those notifications that
862    * were hidden are shown again
863    *
864    * @param string $notification_type_name Type identifier of the subscription
865    */
866    public function enable_notifications($notification_type_name)
867    {
868        $sql = 'UPDATE ' . $this->notification_types_table . "
869            SET notification_type_enabled = 1
870            WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'";
871        $this->db->sql_query($sql);
872    }
873
874    /**
875    * Delete all notifications older than a certain time
876    *
877    * @param int $timestamp Unix timestamp to delete all notifications that were created before
878    * @param bool $only_read True (default) to only prune read notifications
879    */
880    public function prune_notifications($timestamp, $only_read = true)
881    {
882        /** @var method\method_interface $method */
883        foreach ($this->get_available_subscription_methods() as $method)
884        {
885            $method->prune_notifications($timestamp, $only_read);
886        }
887    }
888
889    /**
890     * Helper to get the list of methods enabled by default
891     *
892     * @return string[] Default method types
893     */
894    public function get_default_methods(): array
895    {
896        $default_methods = array();
897
898        foreach ($this->notification_methods as $method)
899        {
900            if ($method->is_enabled_by_default() && $method->is_available())
901            {
902                $default_methods[] = $method->get_type();
903            }
904        }
905
906        return $default_methods;
907    }
908
909    /**
910     * Helper to get the notifications item type class and set it up
911     *
912     * @param string $notification_type_name
913     * @param array $data
914     *
915     * @return type\type_interface
916     * @throws runtime_exception When type name is not o notification type
917     */
918    public function get_item_type_class($notification_type_name, $data = array())
919    {
920        $item = $this->load_object($notification_type_name);
921
922        if (!$item instanceof type\type_interface)
923        {
924            throw new runtime_exception('Supplied type name returned invalid service: ' . $notification_type_name);
925        }
926
927        $item->set_initial_data($data);
928
929        return $item;
930    }
931
932    /**
933     * Helper to get the notifications method class and set it up
934     *
935     * @param string $method_name
936     *
937     * @return method\method_interface
938     * @throws runtime_exception When object name is not o notification method
939     */
940    public function get_method_class($method_name)
941    {
942        $object = $this->load_object($method_name);
943
944        if (!$object instanceof method\method_interface)
945        {
946            throw new runtime_exception('Supplied method name returned invalid service: ' . $method_name);
947        }
948
949        return $object;
950    }
951
952    /**
953     * Helper to load objects (notification types/methods)
954     *
955     * @param string $object_name
956     *
957     * @return method\method_interface|type\type_interface
958     * @psalm-suppress NullableReturnStatement Invalid service will result in exception
959     * @throws runtime_exception When object name is not o notification method or type
960     */
961    protected function load_object($object_name)
962    {
963        $object = $this->phpbb_container->get($object_name);
964
965        if (method_exists($object, 'set_notification_manager'))
966        {
967            $object->set_notification_manager($this);
968        }
969
970        if (!$object instanceof method\method_interface && !$object instanceof type\type_interface)
971        {
972            throw new runtime_exception('Supplied object name returned invalid service: ' . $object_name);
973        }
974
975        return $object;
976    }
977
978    /**
979    * Get the notification type id from the name
980    *
981    * @param string $notification_type_name The name
982    * @return int the notification_type_id
983    * @throws \phpbb\notification\exception
984    */
985    public function get_notification_type_id($notification_type_name)
986    {
987        $notification_type_ids = [];
988
989        $sql = 'SELECT notification_type_id, notification_type_name
990            FROM ' . $this->notification_types_table;
991        $result = $this->db->sql_query($sql, 604800); // cache for one week
992        while ($row = $this->db->sql_fetchrow($result))
993        {
994            $notification_type_ids[$row['notification_type_name']] = (int) $row['notification_type_id'];
995        }
996        $this->db->sql_freeresult($result);
997
998        if (!isset($notification_type_ids[$notification_type_name]))
999        {
1000            if (!isset($this->notification_types[$notification_type_name]) && !isset($this->notification_types['notification.type.' . $notification_type_name]))
1001            {
1002                throw new \phpbb\notification\exception('NOTIFICATION_TYPE_NOT_EXIST', array($notification_type_name));
1003            }
1004
1005            $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array(
1006                'notification_type_name'        => $notification_type_name,
1007                'notification_type_enabled'        => 1,
1008            ));
1009            $this->db->sql_query($sql);
1010
1011            // expose new notification type ID for this request
1012            $notification_type_ids[$notification_type_name] = (int) $this->db->sql_nextid();
1013
1014            // destroy cache, we have a new addition which we have to to load next time
1015            $this->cache->destroy('sql', $this->notification_types_table);
1016        }
1017
1018        return $notification_type_ids[$notification_type_name];
1019    }
1020
1021    /**
1022    * Get notification type ids (as an array)
1023    *
1024    * @param string|array $notification_type_names Notification type names
1025    * @return array Array of integers
1026    */
1027    public function get_notification_type_ids($notification_type_names)
1028    {
1029        if (!is_array($notification_type_names))
1030        {
1031            $notification_type_names = array($notification_type_names);
1032        }
1033
1034        $notification_type_ids = array();
1035
1036        foreach ($notification_type_names as $name)
1037        {
1038            $notification_type_ids[$name] = $this->get_notification_type_id($name);
1039        }
1040
1041        return $notification_type_ids;
1042    }
1043
1044    /**
1045    * Find the users which are already notified
1046    *
1047    * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to retrieve all item types
1048    * @param array $options
1049    * @return array The list of the notified users
1050    */
1051    public function get_notified_users($notification_type_name, array $options)
1052    {
1053        $notification_type_id = $this->get_notification_type_id($notification_type_name);
1054
1055        $notified_users = array();
1056
1057        /** @var method\method_interface $method */
1058        foreach ($this->get_available_subscription_methods() as $method)
1059        {
1060            $notified_users = $notified_users + $method->get_notified_users($notification_type_id, $options);
1061        }
1062
1063        return $notified_users;
1064    }
1065}