Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
70.62% covered (warning)
70.62%
137 / 194
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
viewonline_helper
70.62% covered (warning)
70.62%
137 / 194
42.86% covered (danger)
42.86%
3 / 7
120.58
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 get_forum_ids
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 get_user_page
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 get_location
97.00% covered (success)
97.00%
97 / 100
0.00% covered (danger)
0.00%
0 / 1
34
 get_number_guests
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 get_forum_data
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
3
 get_session_data_rowset
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
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\members;
15
16use phpbb\filesystem\helper as filesystem_helper;
17
18/**
19 * Class to handle viewonline related tasks
20 */
21class viewonline_helper
22{
23    /** @var \phpbb\db\driver\driver_interface */
24    protected $db;
25
26    /** @var \phpbb\config\config */
27    protected $config;
28
29    /** @var \phpbb\event\dispatcher_interface */
30    protected $dispatcher;
31
32    /** @var \phpbb\routing\router */
33    protected $router;
34
35    /** @var \phpbb\controller\helper */
36    protected $helper;
37
38    /** @var \phpbb\language\language */
39    protected $language;
40
41    /** @var \phpbb\auth\auth */
42    protected $auth;
43
44    /** @var string */
45    protected $phpbb_root_path;
46
47    /** @var string */
48    protected $php_ex;
49
50    /** @var string */
51    protected $phpbb_adm_relative_path;
52
53    /** @var string */
54    protected $forums_table;
55
56    /** @var string */
57    protected $topics_table;
58
59    /** @var string */
60    protected $users_table;
61
62    /** @var string */
63    protected $sessions_table;
64
65    /**
66     * @param \phpbb\db\driver\driver_interface $db
67     * @param \phpbb\config\config $config
68     * @param \phpbb\event\dispatcher_interface $dispatcher
69     * @param \phpbb\routing\router $router
70     * @param \phpbb\controller\helper $helper
71     * @param \phpbb\language\language $language
72     * @param \phpbb\auth\auth $auth
73     * @param string $phpbb_root_path
74     * @param string $php_ex
75     * @param string $phpbb_adm_relative_path
76     * @param string $users_table
77     * @param string $sessions_table
78     * @param string $topics_table
79     * @param string $forums_table
80     */
81    public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\routing\router $router, \phpbb\controller\helper $helper, \phpbb\language\language $language, \phpbb\auth\auth $auth, string $phpbb_root_path, string $php_ex, string $phpbb_adm_relative_path, string $users_table, string $sessions_table, string $topics_table, string $forums_table)
82    {
83        $this->db = $db;
84        $this->config = $config;
85        $this->dispatcher = $dispatcher;
86
87        $this->router = $router;
88        $this->helper = $helper;
89        $this->language = $language;
90        $this->auth = $auth;
91        $this->phpbb_root_path = $phpbb_root_path;
92        $this->php_ex = $php_ex;
93        $this->phpbb_adm_relative_path = $phpbb_adm_relative_path;
94
95        $this->users_table = $users_table;
96        $this->sessions_table = $sessions_table;
97        $this->topics_table = $topics_table;
98        $this->forums_table = $forums_table;
99    }
100
101    /**
102     * Get forum IDs for topics
103     *
104     * Retrieve forum IDs and add the data into the session data array
105     * Array structure matches sql_fethrowset() result array
106     *
107     * @param array $session_data_rowset Users' session data array
108     * @return void
109     */
110    public function get_forum_ids(array &$session_data_rowset): void
111    {
112        $topic_ids = $match = [];
113        foreach ($session_data_rowset as $number => $row)
114        {
115            if ($row['session_forum_id'] == 0 && preg_match('#t=([0-9]+)#', $row['session_page'], $match))
116            {
117                $topic_ids[$number] = (int) $match[1];
118            }
119        }
120
121        if (count($topic_ids = array_unique($topic_ids)))
122        {
123            $sql_ary = [
124                'SELECT'    => 't.topic_id, t.forum_id',
125                'FROM'        => [
126                    $this->topics_table => 't',
127                ],
128                'WHERE'        => $this->db->sql_in_set('t.topic_id', $topic_ids),
129                'ORDER_BY'    => 't.topic_id',
130            ];
131            $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
132            $forum_ids_rowset = $this->db->sql_fetchrowset($result);
133            $this->db->sql_freeresult($result);
134
135            foreach ($forum_ids_rowset as $forum_ids_row)
136            {
137                $session_data_row_number = array_search((int) $forum_ids_row['topic_id'], $topic_ids);
138                $session_data_rowset[$session_data_row_number]['session_forum_id'] = (int) $forum_ids_row['forum_id'];
139            }
140        }
141    }
142
143    /**
144     * Get user page
145     *
146     * @param string $session_page User's session page
147     * @return array Match array filled by preg_match()
148     */
149    public function get_user_page($session_page): array
150    {
151        $session_page = filesystem_helper::clean_path($session_page);
152
153        if (str_starts_with($session_page, './'))
154        {
155            $session_page = substr($session_page, 2);
156        }
157
158        preg_match('#^((\.\./)*([a-z0-9/_-]+))#i', $session_page, $on_page);
159        if (empty($on_page))
160        {
161            $on_page[1] = '';
162        }
163
164        return $on_page;
165    }
166
167    /**
168     * Given a certain page, it returns the title of the page and a link to it.
169     * There are 2 strategies to detect the page the user is in:
170     *  - Try to get the controller route from the router service
171     *  - And second, if is not possible to get the controller route, the
172     * page is analyzed by path, analyzing the file that is accessed. This
173     * is mostly for legacy pages, and should be removed in the future.
174     *
175     * @param string $session_page
176     * @param int $forum_id
177     * @return array
178     */
179    public function get_location(string $session_page, int $forum_id): array
180    {
181        try
182        {
183            $match = $this->router->match($session_page);
184
185            switch ($match['_route'])
186            {
187                case 'phpbb_help_bbcode_controller':
188                case 'phpbb_help_faq_controller':
189                    $location = $this->language->lang('VIEWING_FAQ');
190                    $location_url = $this->helper->route('phpbb_help_faq_controller');
191                    break;
192
193                case 'phpbb_members_online':
194                case 'phpbb_members_online_whois':
195                    $location = $this->language->lang('VIEWING_ONLINE');
196                    $location_url = $this->helper->route('phpbb_members_online');
197                    break;
198
199                case 'phpbb_members_team':
200                    $location_url = append_sid($this->phpbb_root_path . "memberlist." . $this->php_ex);
201                    $location = $this->language->lang('VIEWING_MEMBERS');
202                    break;
203
204                case 'phpbb_report_pm_controller':
205                case 'phpbb_report_post_controller':
206                    $location = $this->language->lang('REPORTING_POST');
207                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
208                    break;
209
210                default:
211                    // Is a route, but not in the switch
212                    $location = $this->language->lang('INDEX');
213                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
214            }
215        }
216        catch (\RuntimeException $e) // Urls without route
217        {
218            $on_page = $this->get_user_page($session_page);
219
220            switch ($on_page[1])
221            {
222                case 'index':
223                    $location = $this->language->lang('INDEX');
224                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
225                    break;
226
227                case $this->phpbb_adm_relative_path . 'index':
228                    $location = $this->language->lang('ACP');
229                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
230                    break;
231
232                case 'posting':
233                case 'viewforum':
234                case 'viewtopic':
235
236                    $forum_data = $this->get_forum_data();
237
238                    if ($forum_id && $this->auth->acl_get('f_list', $forum_id))
239                    {
240                        $location = '';
241                        $location_url = append_sid($this->phpbb_root_path . "viewforum." . $this->php_ex, 'f=' . $forum_id);
242
243                        if ($forum_data[$forum_id]['forum_type'] == FORUM_LINK)
244                        {
245                            $location = $this->language->lang('READING_LINK', $forum_data[$forum_id]['forum_name']);
246                            break;
247                        }
248
249                        switch ($on_page[1])
250                        {
251                            case 'posting':
252                                preg_match('#mode=([a-z]+)#', $session_page, $on_page);
253                                $posting_mode = (!empty($on_page[1])) ? $on_page[1] : '';
254
255                                switch ($posting_mode)
256                                {
257                                    case 'reply':
258                                    case 'quote':
259                                        $location = $this->language->lang('REPLYING_MESSAGE', $forum_data[$forum_id]['forum_name']);
260                                        break;
261
262                                    default:
263                                        $location = $this->language->lang('POSTING_MESSAGE', $forum_data[$forum_id]['forum_name']);
264                                        break;
265                                }
266                                break;
267
268                            case 'viewtopic':
269                                $location = $this->language->lang('READING_TOPIC', $forum_data[$forum_id]['forum_name']);
270                                break;
271
272                            case 'viewforum':
273                                $location = $this->language->lang('READING_FORUM', $forum_data[$forum_id]['forum_name']);
274                                break;
275                        }
276                    }
277                    else
278                    {
279                        $location = $this->language->lang('INDEX');
280                        $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
281                    }
282                    break;
283
284                case 'search':
285                    $location = $this->language->lang('SEARCHING_FORUMS');
286                    $location_url = append_sid($this->phpbb_root_path . "search." . $this->php_ex);
287                    break;
288
289                case 'memberlist':
290                    $location_url = append_sid($this->phpbb_root_path . "memberlist." . $this->php_ex);
291
292                    if (str_contains($session_page, 'mode=viewprofile'))
293                    {
294                        $location = $this->language->lang('VIEWING_MEMBER_PROFILE');
295                    }
296                    else if (str_contains($session_page, 'mode=contactadmin'))
297                    {
298                        $location = $this->language->lang('VIEWING_CONTACT_ADMIN');
299                        $location_url = append_sid($this->phpbb_root_path . "memberlist." . $this->php_ex, 'mode=contactadmin');
300                    }
301                    else
302                    {
303                        $location = $this->language->lang('VIEWING_MEMBERS');
304                    }
305                    break;
306
307                case 'mcp':
308                    $location = $this->language->lang('VIEWING_MCP');
309                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
310                    break;
311
312                case 'ucp':
313                    $location = $this->language->lang('VIEWING_UCP');
314
315                    // Grab some common modules
316                    $url_params = [
317                        'mode=register'        => 'VIEWING_REGISTER',
318                        'i=pm&mode=compose'    => 'POSTING_PRIVATE_MESSAGE',
319                        'i=pm&'                => 'VIEWING_PRIVATE_MESSAGES',
320                        'i=profile&'        => 'CHANGING_PROFILE',
321                        'i=prefs&'            => 'CHANGING_PREFERENCES',
322                    ];
323
324                    foreach ($url_params as $param => $lang)
325                    {
326                        if (strpos($session_page, $param) !== false)
327                        {
328                            $location = $this->language->lang($lang);
329                            break;
330                        }
331                    }
332
333                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
334                    break;
335
336                default:
337                    $location = $this->language->lang('INDEX');
338                    $location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
339                    break;
340            }
341        }
342
343        return [$location, $location_url];
344    }
345
346    /**
347     * Get number of guests online
348     *
349     * @return int
350     */
351    public function get_number_guests(): int
352    {
353        switch ($this->db->get_sql_layer())
354        {
355            case 'sqlite3':
356                $sql = 'SELECT COUNT(session_ip) as num_guests
357                    FROM (
358                        SELECT DISTINCT session_ip
359                            FROM ' . $this->sessions_table . '
360                            WHERE session_user_id = ' . ANONYMOUS . '
361                                AND session_time >= ' . (time() - ($this->config['load_online_time'] * 60)) .
362                    ')';
363                break;
364
365            default:
366                $sql = 'SELECT COUNT(DISTINCT session_ip) as num_guests
367                    FROM ' . $this->sessions_table . '
368                    WHERE session_user_id = ' . ANONYMOUS . '
369                        AND session_time >= ' . (time() - ($this->config['load_online_time'] * 60));
370                break;
371        }
372        $result = $this->db->sql_query($sql);
373        $guest_counter = (int) $this->db->sql_fetchfield('num_guests');
374        $this->db->sql_freeresult($result);
375
376        return $guest_counter;
377    }
378
379    /**
380     * Get forum data
381     *
382     * @return array
383     */
384    public function get_forum_data(): array
385    {
386        static $forum_data;
387
388        if (isset($forum_data))
389        {
390            return $forum_data;
391        }
392
393        // Forum info
394        $sql_ary = [
395            'SELECT'    => 'f.forum_id, f.forum_name, f.parent_id, f.forum_type, f.left_id, f.right_id',
396            'FROM'        => [
397                $this->forums_table    => 'f',
398            ],
399            'ORDER_BY'    => 'f.left_id ASC',
400        ];
401
402        /**
403         * Modify the forum data SQL query for getting additional fields if needed
404         *
405         * @event core.viewonline_modify_forum_data_sql
406         * @var    array    sql_ary            The SQL array
407         * @since 3.1.5-RC1
408         */
409        $vars = ['sql_ary'];
410        extract($this->dispatcher->trigger_event('core.viewonline_modify_forum_data_sql', compact($vars)));
411
412        $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary), 600);
413        unset($sql_ary);
414
415        $forum_data = [];
416        while ($row = $this->db->sql_fetchrow($result))
417        {
418            $forum_data[$row['forum_id']] = $row;
419        }
420        $this->db->sql_freeresult($result);
421
422        return $forum_data;
423    }
424
425    /**
426     * Build and execute the sessions query and return the rowset.
427     *
428     * @param bool $show_guests
429     * @param string $order_by
430     * @param int &$guest_counter
431     * @return array
432     */
433    public function get_session_data_rowset(bool &$show_guests, string $order_by, int &$guest_counter): array
434    {
435        $forum_data = $this->get_forum_data();
436
437        $sql_ary = [
438            'SELECT'    => 'u.user_id, u.username, u.username_clean, u.user_type, u.user_colour, s.session_id, s.session_time, s.session_page, s.session_ip, s.session_browser, s.session_viewonline, s.session_forum_id',
439            'FROM'      => [
440                $this->users_table     => 'u',
441                $this->sessions_table  => 's',
442            ],
443            'WHERE'     => 'u.user_id = s.session_user_id
444                AND s.session_time >= ' . (time() - ($this->config['load_online_time'] * 60)) .
445                (($show_guests) ? '' : ' AND s.session_user_id <> ' . ANONYMOUS),
446            'ORDER_BY'  => $order_by,
447        ];
448
449        /**
450         * Modify the SQL query for getting the user data to display viewonline list
451         *
452         * @event core.viewonline_modify_sql
453         * @var    array    sql_ary            The SQL array
454         * @var    bool    show_guests        Do we display guests in the list
455         * @var    int        guest_counter    Number of guests displayed
456         * @var    array    forum_data        Array with forum data
457         * @since 3.1.0-a1
458         * @changed 3.1.0-a2 Added vars guest_counter and forum_data
459         */
460        $vars = ['sql_ary', 'show_guests', 'guest_counter', 'forum_data'];
461        extract($this->dispatcher->trigger_event('core.viewonline_modify_sql', compact($vars)));
462
463        $result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
464        $session_data_rowset = $this->db->sql_fetchrowset($result);
465        $this->db->sql_freeresult($result);
466
467        return $session_data_rowset;
468    }
469}