OnlineHandler   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 207
dl 0
loc 382
rs 5.04
c 6
b 0
f 0
wmc 57

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A getCount() 0 14 4
B render() 0 55 10
A getAll() 0 23 6
A garbageCollection() 0 9 1
A update() 0 27 6
B write() 0 52 7
B showOnline() 0 57 10
A init() 0 18 4
B checkStatus() 0 28 7

How to fix   Complexity   

Complex Class

Complex classes like OnlineHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OnlineHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace XoopsModules\Newbb;
6
7
/**
8
 * NewBB,  the forum module for XOOPS project
9
 *
10
 * @copyright      XOOPS Project (https://xoops.org)
11
 * @license        GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
12
 * @author         Taiwen Jiang (phppp or D.J.) <[email protected]>
13
 * @since          4.00
14
 */
15
16
17
use Xmf\IPAddress;
18
use XoopsDatabase;
19
20
/** @var \XoopsOnlineHandler $xoopsOnlineHandler */
21
require_once \dirname(__DIR__) . '/include/functions.config.php';
22
23
/**
24
 * Class OnlineHandler
25
 */
26
class OnlineHandler
27
{
28
    public ?XoopsDatabase $db;
29
    public int            $forum_id;
30
31
    /**
32
     * @var Forum|int|null|string
33
     */
34
    public           $forumObject;
35
    public int            $topic_id;
36
    public array          $user_ids = [];
37
38
    /**
39
     * OnlineHandler constructor.
40
     */
41
    public function __construct(XoopsDatabase $db = null)
42
    {
43
        if (null === $db) {
44
            $db = \XoopsDatabaseFactory::getDatabaseConnection();
45
        }
46
        $this->db = $db;
47
    }
48
49
    /**
50
     * @param Forum|string|int|null $forum
51
     * @param Topic|null $forumtopic
52
     * @throws \Exception
53
     */
54
    public function init($forum = null, Topic $forumtopic = null): void
55
    {
56
        if (\is_object($forum)) {
57
            $this->forum_id    = $forum->getVar('forum_id');
58
        } else {
59
            $this->forum_id    = (int)$forum;
60
        }
61
        $this->forumObject = $forum;
62
        if (\is_object($forumtopic)) {
63
            $this->topic_id = $forumtopic->getVar('topic_id');
64
            if (empty($this->forum_id)) {
65
                $this->forum_id = $forumtopic->getVar('forum_id');
66
            }
67
        } else {
68
            $this->topic_id = (int)$forumtopic;
69
        }
70
71
        $this->update();
72
    }
73
74
    /**
75
     * @throws \Exception
76
     */
77
    public function update(): void
78
    {
79
        global $xoopsModule;
80
81
        // set gc probabillity to 10% for now..
82
        if (\random_int(1, 100) < 60) {
83
            $this->garbageCollection(150);
84
        }
85
        if (\is_object($GLOBALS['xoopsUser'])) {
86
            $uid   = $GLOBALS['xoopsUser']->getVar('uid');
87
            $uname = $GLOBALS['xoopsUser']->getVar('uname');
88
            $name  = $GLOBALS['xoopsUser']->getVar('name');
89
        } else {
90
            $uid   = 0;
91
            $uname = '';
92
            $name  = '';
93
        }
94
95
        /** @var \XoopsOnlineHandler $xoopsOnlineHandler */
96
        $xoopsOnlineHandler = \xoops_getHandler('online');
97
        $xoopsupdate        = $xoopsOnlineHandler->write($uid, $uname, \time(), $xoopsModule->getVar('mid'), \Xmf\IPAddress::fromRequest()->asReadable());
98
        if (!$xoopsupdate) {
99
            //xoops_error("newbb online upate error");
100
        }
101
102
        $uname = (empty($GLOBALS['xoopsModuleConfig']['show_realname']) || empty($name)) ? $uname : $name;
103
        $this->write($uid, $uname, \time(), $this->forum_id, IPAddress::fromRequest()->asReadable(), $this->topic_id);
104
    }
105
106
    /**
107
     * @param \Smarty $xoopsTpl
108
     */
109
    public function render(\Smarty $xoopsTpl): void
110
    {
111
        require_once \dirname(__DIR__) . '/include/functions.render.php';
112
        require_once \dirname(__DIR__) . '/include/functions.user.php';
113
        $criteria = null;
114
        if ($this->topic_id) {
115
            $criteria = new \Criteria('online_topic', $this->topic_id);
116
        } elseif ($this->forum_id) {
117
            $criteria = new \Criteria('online_forum', $this->forum_id);
118
        }
119
        $users     = $this->getAll($criteria);
120
        $num_total = \count($users);
121
122
        $num_user     = 0;
123
        $users_id     = [];
124
        $users_online = [];
125
        foreach ($users as $i => $iValue) {
126
            if (empty($iValue['online_uid'])) {
127
                continue;
128
            }
129
            $users_id[]                          = $iValue['online_uid'];
130
            $users_online[$iValue['online_uid']] = [
131
                'link'  => XOOPS_URL . '/userinfo.php?uid=' . $iValue['online_uid'],
132
                'uname' => $iValue['online_uname'],
133
            ];
134
            ++$num_user;
135
        }
136
        $num_anonymous           = $num_total - $num_user;
137
        $online                  = [];
138
        $online['image']         = \newbbDisplayImage('whosonline');
139
        $online['num_total']     = $num_total;
140
        $online['num_user']      = $num_user;
141
        $online['num_anonymous'] = $num_anonymous;
142
        $administrator_list      = \newbbIsModuleAdministrators($users_id);
143
        $moderator_list          = [];
144
        $member_list             = \array_diff(\array_keys($administrator_list), $users_id);
145
        if ($member_list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $member_list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
146
            if (\is_object($this->forumObject)) {
147
                $moderator_list = $this->forumObject->getVar('forum_moderator');
148
            } else {
149
                $moderator_list = \newbbIsForumModerators($member_list);
150
            }
151
        }
152
        foreach ($users_online as $uid => $user) {
153
            if (!empty($administrator_list[$uid])) {
154
                $user['level'] = 2;
155
            } elseif (!empty($moderator_list[$uid])) {
156
                $user['level'] = 1;
157
            } else {
158
                $user['level'] = 0;
159
            }
160
            $online['users'][] = $user;
161
        }
162
163
        $xoopsTpl->assign_by_ref('online', $online);
164
    }
165
166
    /**
167
     *  Deprecated
168
     *
169
     * @return ((int|mixed|string)[][]|int|mixed)[]
170
     *
171
     * @psalm-return array{image: mixed, statistik: mixed, num_total: 0|positive-int, num_user: 0|positive-int, num_anonymous: int, users?: non-empty-list<array{link: string, uname: mixed, level: 0|1|2}>}
172
     */
173
    public function showOnline(): array
174
    {
175
        require_once \dirname(__DIR__) . '/include/functions.render.php';
176
        require_once \dirname(__DIR__) . '/include/functions.user.php';
177
        $criteria = null;
178
        if ($this->topic_id) {
179
            $criteria = new \Criteria('online_topic', $this->topic_id);
180
        } elseif ($this->forum_id) {
181
            $criteria = new \Criteria('online_forum', $this->forum_id);
182
        }
183
        $users     = $this->getAll($criteria);
184
        $num_total = \count($users);
185
186
        $num_user     = 0;
187
        $users_id     = [];
188
        $users_online = [];
189
        foreach ($users as $i => $iValue) {
190
            if (empty($iValue['online_uid'])) {
191
                continue;
192
            }
193
            $users_id[]                          = $iValue['online_uid'];
194
            $users_online[$iValue['online_uid']] = [
195
                'link'  => XOOPS_URL . '/userinfo.php?uid=' . $iValue['online_uid'],
196
                'uname' => $iValue['online_uname'],
197
            ];
198
            ++$num_user;
199
        }
200
        $num_anonymous           = $num_total - $num_user;
201
        $online                  = [];
202
        $online['image']         = \newbbDisplayImage('whosonline');
203
        $online['statistik']     = \newbbDisplayImage('statistik');
204
        $online['num_total']     = $num_total;
205
        $online['num_user']      = $num_user;
206
        $online['num_anonymous'] = $num_anonymous;
207
        $administrator_list      = \newbbIsModuleAdministrators($users_id);
208
        $moderator_list          = [];
209
        $member_list             = \array_diff($users_id, \array_keys($administrator_list));
210
        if ($member_list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $member_list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
211
            if (\is_object($this->forumObject)) {
212
                $moderator_list = $this->forumObject->getVar('forum_moderator');
213
            } else {
214
                $moderator_list = \newbbIsForumModerators($member_list);
215
            }
216
        }
217
218
        foreach ($users_online as $uid => $user) {
219
            if (\in_array($uid, $administrator_list, true)) {
220
                $user['level'] = 2;
221
            } elseif (\in_array($uid, $moderator_list, true)) {
222
                $user['level'] = 1;
223
            } else {
224
                $user['level'] = 0;
225
            }
226
            $online['users'][] = $user;
227
        }
228
229
        return $online;
230
    }
231
232
    /**
233
     * Write online information to the database
234
     *
235
     * @param int    $uid      UID of the active user
236
     * @param string $uname    Username
237
     * @param int    $time
238
     * @param int    $forum_id Current forum_id
239
     * @param string $ip       User's IP adress
240
     * @param int    $topic_id
241
     * @return bool   TRUE on success
242
     * @internal param string $timestamp
243
     */
244
    public function write(int $uid, string $uname, int $time, int $forum_id, string $ip, int $topic_id): bool
245
    {
246
        global $xoopsModule, $xoopsDB;
247
248
        $uid = (int)$uid;
249
        if ($uid > 0) {
250
            $sql = 'SELECT COUNT(*) FROM ' . $this->db->prefix('newbb_online') . ' WHERE online_uid=' . $uid;
0 ignored issues
show
Bug introduced by
The method prefix() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

250
            $sql = 'SELECT COUNT(*) FROM ' . $this->db->/** @scrutinizer ignore-call */ prefix('newbb_online') . ' WHERE online_uid=' . $uid;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
251
        } else {
252
            $sql = 'SELECT COUNT(*) FROM ' . $this->db->prefix('newbb_online') . ' WHERE online_uid=' . $uid . " AND online_ip='" . $ip . "'";
253
        }
254
        $result = $this->db->queryF($sql);
255
        if (!$this->db->isResultSet($result)) {
256
            \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
257
        }
258
        [$count] = $this->db->fetchRow($result);
259
        if ($count > 0) {
260
            $sql = 'UPDATE ' . $this->db->prefix('newbb_online') . " SET online_updated= '" . $time . "', online_forum = '" . $forum_id . "', online_topic = '" . $topic_id . "' WHERE online_uid = " . $uid;
261
            if (0 === $uid) {
262
                $sql .= " AND online_ip='" . $ip . "'";
263
            }
264
        } else {
265
            $sql = \sprintf('INSERT INTO `%s` (online_uid, online_uname, online_updated, online_ip, online_forum, online_topic) VALUES (%u, %s, %u, %s, %u, %u)', $this->db->prefix('newbb_online'), $uid, $this->db->quote($uname), $time, $this->db->quote($ip), $forum_id, $topic_id);
266
        }
267
        if (!$this->db->queryF($sql)) {
268
            //xoops_error($this->db->error());
269
            return false;
270
        }
271
272
        /** @var \XoopsOnlineHandler $xoopsOnlineHandler */
273
        $xoopsOnlineHandler = \xoops_getHandler('online');
274
        $xoopsOnlineTable   = $xoopsOnlineHandler->table;
275
276
        $sql = 'DELETE FROM '
277
               . $this->db->prefix('newbb_online')
278
               . ' WHERE'
279
               . ' ( online_uid > 0 AND online_uid NOT IN ( SELECT online_uid FROM '
280
               . $xoopsOnlineTable
281
               . ' WHERE online_module ='
282
               . $xoopsModule->getVar('mid')
283
               . ' ) )'
284
               . ' OR ( online_uid = 0 AND online_ip NOT IN ( SELECT online_ip FROM '
285
               . $xoopsOnlineTable
286
               . ' WHERE online_module ='
287
               . $xoopsModule->getVar('mid')
288
               . ' AND online_uid = 0 ) )';
289
290
        $result = $this->db->queryF($sql);
291
        if ($result) {
292
            return true;
293
        }
294
        //xoops_error($this->db->error());
295
        return false;
296
    }
297
298
    /**
299
     * Garbage Collection
300
     *
301
     * Delete all online information that has not been updated for a certain time
302
     *
303
     * @param int $expire Expiration time in seconds
304
     */
305
    public function garbageCollection(int $expire): void
306
    {
307
        global $xoopsModule;
308
        $sql = 'DELETE FROM ' . $this->db->prefix('newbb_online') . ' WHERE online_updated < ' . (\time() - (int)$expire);
309
        $this->db->queryF($sql);
310
311
        /** @var \XoopsOnlineHandler $xoopsOnlineHandler */
312
        $xoopsOnlineHandler = \xoops_getHandler('online');
313
        $xoopsOnlineHandler->gc($expire);
314
    }
315
316
    /**
317
     *  Get an array of online information
318
     *
319
     * @param \CriteriaElement|null $criteria {@link \CriteriaElement}
320
     *
321
     * @return array Array of associative arrays of online information
322
     *
323
     * @psalm-return list<mixed>
324
     */
325
    public function getAll(\CriteriaElement $criteria = null): array
326
    {
327
        $ret   = [];
328
        $limit = $start = 0;
329
        $sql   = 'SELECT * FROM ' . $this->db->prefix('newbb_online');
330
         if (\is_object($criteria) && \is_subclass_of($criteria, \CriteriaElement::class)) {
331
            $sql   .= ' ' . $criteria->renderWhere();
0 ignored issues
show
Bug introduced by
The method renderWhere() does not exist on CriteriaElement. Did you maybe mean render()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

331
            $sql   .= ' ' . $criteria->/** @scrutinizer ignore-call */ renderWhere();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
332
            $limit = $criteria->getLimit();
333
            $start = $criteria->getStart();
334
        }
335
        $result = $this->db->query($sql, $limit, $start);
336
        if ($this->db->isResultSet($result)) {
337
            while (false !== ($myrow = $this->db->fetchArray($result))) {
338
                $ret[] = $myrow;
339
                if ($myrow['online_uid'] > 0) {
340
                    $this->user_ids[] = $myrow['online_uid'];
341
                }
342
                unset($myrow);
343
            }
344
            $this->user_ids = \array_unique($this->user_ids);
345
        }
346
347
        return $ret;
348
    }
349
350
    /**
351
     * @param array $uids
352
     *
353
     * @return int[]
354
     *
355
     * @psalm-return array<1>
356
     */
357
    public function checkStatus(array $uids): array
358
    {
359
        $online_users = [];
360
        $ret          = [];
361
        if (!empty($this->user_ids)) {
362
            $online_users = $this->user_ids;
363
        } else {
364
            $sql = 'SELECT online_uid FROM ' . $this->db->prefix('newbb_online');
365
            if ($uids !== []) {
366
                $sql .= ' WHERE online_uid IN (' . \implode(', ', \array_map('\intval', $uids)) . ')';
367
            }
368
369
            $result = $this->db->query($sql);
370
            if (!$this->db->isResultSet($result)) {
371
//                \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
372
                return $ret;
373
            }
374
            while ([$uid] = $this->db->fetchRow($result)) {
375
                $online_users[] = $uid;
376
            }
377
        }
378
        foreach ($uids as $uid) {
379
            if (\in_array($uid, $online_users, true)) {
380
                $ret[$uid] = 1;
381
            }
382
        }
383
384
        return $ret;
385
    }
386
387
    /**
388
     *  Count the number of online users
389
     *
390
     * @param \CriteriaElement|null $criteria
391
     *
392
     * @return bool
393
     */
394
    public function getCount(\CriteriaElement $criteria = null): bool
395
    {
396
        $sql = 'SELECT COUNT(*) FROM ' . $this->db->prefix('newbb_online');
397
         if (\is_object($criteria) && \is_subclass_of($criteria, \CriteriaElement::class)) {
398
            $sql .= ' ' . $criteria->renderWhere();
399
        }
400
        $result = $this->db->query($sql);
401
        if (!$this->db->isResultSet($result)) {
402
            //                \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
403
            return false;
404
        }
405
        [$ret] = $this->db->fetchRow($result);
406
407
        return $ret;
408
    }
409
}
410