Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
57.45% covered (warning)
57.45%
81 / 141
50.00% covered (danger)
50.00%
15 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
base
57.45% covered (warning)
57.45%
81 / 141
50.00% covered (danger)
50.00%
15 / 30
358.20
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 set_notification_manager
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 set_initial_data
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 get_data
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 set_data
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create_insert_array
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 get_insert_array
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 create_update_array
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 mark_read
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 mark_unread
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_redirect_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 prepare_for_display
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
156
 get_unsubscribe_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_style_class
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_avatar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_reference
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_forum
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_reason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_load_special
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load_special
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_available
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_email_template
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pre_create_insert_array
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 check_user_notification_options
73.33% covered (warning)
73.33%
33 / 45
0.00% covered (danger)
0.00%
0 / 1
19.27
 mark
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 get_authorised_recipients
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
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\type;
15
16/**
17* Base notifications class
18*/
19abstract class base implements \phpbb\notification\type\type_interface
20{
21    /** @var \phpbb\notification\manager */
22    protected $notification_manager;
23
24    /** @var \phpbb\db\driver\driver_interface */
25    protected $db;
26
27    /** @var \phpbb\language\language */
28    protected $language;
29
30    /** @var \phpbb\user */
31    protected $user;
32
33    /** @var \phpbb\auth\auth */
34    protected $auth;
35
36    /** @var string */
37    protected $phpbb_root_path;
38
39    /** @var string */
40    protected $php_ext;
41
42    /** @var string */
43    protected $user_notifications_table;
44
45    /**
46    * Notification option data (for outputting to the user)
47    *
48    * @var bool|array False if the service should use its default data
49    *                     Array of data (including keys 'id', 'lang', and 'group')
50    */
51    public static $notification_option = false;
52
53    /**
54    * The notification_type_id, set upon creation of the class
55    * This is the notification_type_id from the notification_types table
56    *
57    * @var int
58    */
59    protected $notification_type_id;
60
61    /**
62    * Identification data
63    * notification_type_id    - ID of the item type (auto generated, from notification types table)
64    * item_id                - ID of the item (e.g. post_id, msg_id)
65    * item_parent_id        - Parent item id (ex: for topic => forum_id, for post => topic_id, etc)
66    * user_id
67    * notification_read
68    * notification_time
69    * notification_data (special serialized field that each notification type can use to store stuff)
70    *
71    * @var array $data Notification row from the database
72    *         This must be private, all interaction should use __get(), __set(), get_data(), set_data()
73    */
74    private $data = array();
75
76    /**
77     * Notification Type Base Constructor
78     *
79     * @param \phpbb\db\driver\driver_interface $db
80     * @param \phpbb\language\language          $language
81     * @param \phpbb\user                       $user
82     * @param \phpbb\auth\auth                  $auth
83     * @param string                            $phpbb_root_path
84     * @param string                            $php_ext
85     * @param string                            $user_notifications_table
86     */
87    public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table)
88    {
89        $this->db = $db;
90        $this->language = $language;
91        $this->user = $user;
92        $this->auth = $auth;
93
94        $this->phpbb_root_path = $phpbb_root_path;
95        $this->php_ext = $php_ext;
96
97        $this->user_notifications_table = $user_notifications_table;
98    }
99
100    /**
101    * Set notification manager (required)
102    *
103    * @param \phpbb\notification\manager $notification_manager
104    */
105    public function set_notification_manager(\phpbb\notification\manager $notification_manager)
106    {
107        $this->notification_manager = $notification_manager;
108
109        $this->notification_type_id = $this->notification_manager->get_notification_type_id($this->get_type());
110    }
111
112    /**
113    * Set initial data from the database
114    *
115    * @param array $data Row directly from the database
116    */
117    public function set_initial_data($data = array())
118    {
119        // The row from the database (unless this is a new notification we're going to add)
120        $this->data = $data;
121        $this->data['notification_data'] = !empty($this->data['notification_data']) ? unserialize($this->data['notification_data']) : [];
122    }
123
124    /**
125    * Magic method to get data from this notification
126    *
127    * @param mixed $name
128    * @return mixed
129    */
130    public function __get($name)
131    {
132        return $this->data[$name] ?? null;
133    }
134
135
136    /**
137    * Magic method to set data on this notification
138    *
139    * @param mixed $name
140    * @param mixed $value
141    *
142    * @return void
143    */
144    public function __set($name, $value)
145    {
146        $this->data[$name] = $value;
147    }
148
149    /**
150     * Magic method check if a variable is defined and is not null
151     *
152     * @param mixed $name
153     *
154     * @return bool
155     */
156    public function __isset($name)
157    {
158        return isset($this->data[$name]);
159    }
160
161    /**
162    * Magic method to get a string of this notification
163    *
164    * Primarily for testing
165    *
166    * @return mixed
167    */
168    public function __toString()
169    {
170        return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type();
171    }
172
173    /**
174    * Get special data (only important for the classes that extend this)
175    *
176    * @param string|false $name Name of the variable to get, false if all data should be returned
177    * @return mixed
178    */
179    protected function get_data($name)
180    {
181        return ($name === false) ? $this->data['notification_data'] : ($this->data['notification_data'][$name] ?? null);
182    }
183
184    /**
185    * Set special data (only important for the classes that extend this)
186    *
187    * @param string $name Name of the variable to set
188    * @param mixed $value Value to set to the variable
189    */
190    protected function set_data($name, $value)
191    {
192        $this->data['notification_data'][$name] = $value;
193    }
194
195    /**
196    * {@inheritdoc}
197    */
198    public function create_insert_array($type_data, $pre_create_data = array())
199    {
200        // Defaults
201        $this->data = array_merge(array(
202            'item_id'                => static::get_item_id($type_data),
203            'notification_type_id'    => $this->notification_type_id,
204            'item_parent_id'        => static::get_item_parent_id($type_data),
205
206            'notification_time'        => time(),
207            'notification_read'        => false,
208
209            'notification_data'        => array(),
210        ), $this->data);
211    }
212
213    /**
214    * {@inheritdoc}
215    */
216    public function get_insert_array()
217    {
218        $data = $this->data;
219
220        $data['notification_data'] = serialize($data['notification_data']);
221
222        return $data;
223    }
224
225    /**
226    * Function for preparing the data for update in an SQL query
227    * (The service handles insertion)
228    *
229    * @param array $type_data Data unique to this notification type
230    * @return array Array of data ready to be updated in the database
231    */
232    public function create_update_array($type_data)
233    {
234        $this->create_insert_array($type_data);
235        $data = $this->get_insert_array();
236
237        // Unset data unique to each row
238        unset(
239            $data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function)
240            $data['notification_id'],
241            $data['notification_read'],
242            $data['user_id']
243        );
244
245        return $data;
246    }
247
248    /**
249    * Mark this item read
250    *
251    * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
252    * @return string|null If $return is False, nothing will be returned, else the sql code to update this item
253    */
254    public function mark_read($return = false)
255    {
256        return $this->mark(false, $return);
257    }
258
259    /**
260    * Mark this item unread
261    *
262    * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
263    * @return string|null If $return is False, nothing will be returned, else the sql code to update this item
264    */
265    public function mark_unread($return = false)
266    {
267        return $this->mark(true, $return);
268    }
269
270    /**
271    * {inheritDoc}
272    */
273    public function get_redirect_url()
274    {
275        return $this->get_url();
276    }
277
278    /**
279    * Prepare to output the notification to the template
280    *
281    * @return array Template variables
282    */
283    public function prepare_for_display()
284    {
285        $mark_hash = generate_link_hash('mark_notification_read');
286
287        if ($this->get_url())
288        {
289            $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;hash=' . $mark_hash);
290        }
291        else
292        {
293            $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : '');
294
295            $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;hash=' . $mark_hash . '&amp;redirect=' . urlencode($redirect));
296        }
297
298        $avatar = $this->get_avatar();
299
300        return [
301            'NOTIFICATION_ID'    => $this->notification_id,
302            'STYLING'            => $this->get_style_class(),
303            'FORMATTED_TITLE'    => $this->get_title(),
304            'REFERENCE'            => $this->get_reference(),
305            'FORUM'                => $this->get_forum(),
306            'REASON'            => $this->get_reason(),
307            'URL'                => $this->get_url(),
308            'TIME'                   => $this->user->format_date($this->notification_time),
309            'UNREAD'            => !$this->notification_read,
310
311            'AVATAR_SOURCE'        => $avatar ? $avatar['src'] : '',
312            'AVATAR_TITLE'        => $avatar ? $avatar['title'] : '',
313            'AVATAR_TYPE'        => $avatar ? $avatar['type'] : '',
314
315            'AVATAR_WIDTH'        => $avatar ? $avatar['width'] : 0,
316            'AVATAR_HEIGHT'        => $avatar ? $avatar['height'] : 0,
317
318            'AVATAR_HTML'        => $avatar ? $avatar['html'] : '',
319            'AVATAR_LAZY'        => $avatar ? $avatar['lazy'] : true,
320
321            'U_MARK_READ'        => (!$this->notification_read) ? $u_mark_read : '',
322        ];
323    }
324
325    /**
326    * -------------- Fall back functions -------------------
327    */
328
329    /**
330    * URL to unsubscribe to this notification (fall back)
331    *
332    * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item
333    * @return false
334    */
335    public function get_unsubscribe_url($method = false)
336    {
337        return false;
338    }
339
340    /**
341    * Get the CSS style class of the notification (fall back)
342    *
343    * @return string
344    */
345    public function get_style_class()
346    {
347        return '';
348    }
349
350    /**
351    * Get the user's avatar (fall back)
352    *
353    * @return array
354    */
355    public function get_avatar()
356    {
357        return [];
358    }
359
360    /**
361    * Get the reference of the notification (fall back)
362    *
363    * @return string
364    */
365    public function get_reference()
366    {
367        return '';
368    }
369
370    /**
371    * Get the forum of the notification reference (fall back)
372    *
373    * @return string
374    */
375    public function get_forum()
376    {
377        return '';
378    }
379
380    /**
381    * Get the reason for the notification (fall back)
382    *
383    * @return string
384    */
385    public function get_reason()
386    {
387        return '';
388    }
389
390    /**
391    * Get the special items to load (fall back)
392    *
393    * @return array
394    */
395    public function get_load_special()
396    {
397        return array();
398    }
399
400    /**
401     * Load the special items (fall back)
402     *
403     * @param array $data
404     * @param array $notifications
405     */
406    public function load_special($data, $notifications)
407    {
408    }
409
410    /**
411    * Is available (fall back)
412    *
413    * @return bool
414    */
415    public function is_available()
416    {
417        return true;
418    }
419
420    /**
421     * {@inheritdoc}
422     */
423    public function get_email_template()
424    {
425        return false;
426    }
427
428    /**
429     * Pre create insert array function (fall back)
430     *
431     * @param array $type_data
432     * @param array $notify_users
433     * @return array
434     */
435    public function pre_create_insert_array($type_data, $notify_users)
436    {
437        return array();
438    }
439
440    /**
441    * -------------- Helper functions -------------------
442    */
443
444    /**
445     * Find the users who want to receive notifications (helper)
446     *
447     * @param array|bool $user_ids User IDs to check if they want to receive notifications
448     *                             (Bool False to check all users besides anonymous and bots (USER_IGNORE))
449     * @param array      $options
450     * @return array
451     */
452    protected function check_user_notification_options($user_ids = false, $options = array())
453    {
454        $options = array_merge(array(
455            'ignore_users'        => array(),
456            'item_type'            => $this->get_type(),
457            'item_id'            => 0, // Global by default
458        ), $options);
459
460        if ($user_ids === false)
461        {
462            $user_ids = array();
463
464            $sql = 'SELECT user_id
465                FROM ' . USERS_TABLE . '
466                WHERE user_id <> ' . ANONYMOUS . '
467                    AND user_type <> ' . USER_IGNORE;
468            $result = $this->db->sql_query($sql);
469            while ($row = $this->db->sql_fetchrow($result))
470            {
471                $user_ids[] = $row['user_id'];
472            }
473            $this->db->sql_freeresult($result);
474        }
475
476        if (empty($user_ids))
477        {
478            return array();
479        }
480
481        $rowset = $output = array();
482
483        $sql = 'SELECT user_id, method, notify
484            FROM ' . $this->user_notifications_table . '
485            WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . "
486                AND item_type = '" . $this->db->sql_escape($options['item_type']) . "'
487                AND item_id = " . (int) $options['item_id'];
488        $result = $this->db->sql_query($sql);
489
490        while ($row = $this->db->sql_fetchrow($result))
491        {
492            if (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))
493            {
494                continue;
495            }
496
497            if (!isset($rowset[$row['user_id']]))
498            {
499                $rowset[$row['user_id']] = array();
500            }
501            $rowset[$row['user_id']][$row['method']] = $row['notify'];
502
503            if (!isset($output[$row['user_id']]))
504            {
505                $output[$row['user_id']] = array();
506            }
507            if ($row['notify'])
508            {
509                $output[$row['user_id']][] = $row['method'];
510            }
511        }
512
513        $this->db->sql_freeresult($result);
514
515        $default_methods = $this->notification_manager->get_default_methods();
516
517        foreach ($user_ids as $user_id)
518        {
519            if (isset($options['ignore_users'][$user_id]))
520            {
521                continue;
522            }
523            if (!array_key_exists($user_id, $rowset))
524            {
525                // No rows at all for this user, use the default methods
526                $output[$user_id] = $default_methods;
527            }
528            else
529            {
530                foreach ($default_methods as $default_method)
531                {
532                    if (!array_key_exists($default_method, $rowset[$user_id]))
533                    {
534                        // No user preference for this type recorded, but it should be enabled by default.
535                        $output[$user_id][] = $default_method;
536                    }
537                }
538            }
539        }
540
541        return $output;
542    }
543
544    /**
545    * Mark this item read/unread helper
546    *
547    * @param bool $unread Unread (True/False) (Default: False)
548    * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
549    * @return string|null If $return is False, nothing will be returned, else the sql code to update this item
550    */
551    protected function mark($unread = true, $return = false)
552    {
553        $this->notification_read = (bool) !$unread;
554
555        if ($return)
556        {
557            $where = array(
558                'notification_type_id = ' . (int) $this->notification_type_id,
559                'item_id = ' . (int) $this->item_id,
560                'user_id = ' . (int) $this->user_id,
561            );
562
563            $where = implode(' AND ', $where);
564            return $where;
565        }
566        else
567        {
568            $this->notification_manager->mark_notifications($this->get_type(), (int) $this->item_id, (int) $this->user_id, false, $this->notification_read);
569        }
570
571        return null;
572    }
573
574    /**
575     * Get a list of users that are authorised to receive notifications
576     *
577     * @param array $users Array of users that have subscribed to a notification
578     * @param int $forum_id Forum ID of the forum
579     * @param array $options Array of notification options
580     * @param bool $sort Whether the users array should be sorted. Default: false
581     * @return array Array of users that are authorised recipients
582     */
583    protected function get_authorised_recipients($users, $forum_id, $options, $sort = false)
584    {
585        if (empty($users))
586        {
587            return array();
588        }
589
590        $users = array_unique($users);
591
592        if ($sort)
593        {
594            sort($users);
595        }
596
597        $auth_read = $this->auth->acl_get_list($users, 'f_read', $forum_id);
598
599        if (empty($auth_read))
600        {
601            return array();
602        }
603
604        return $this->check_user_notification_options($auth_read[$forum_id]['f_read'], $options);
605    }
606}