Issues (340)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

class/OnlineHandler.php (4 issues)

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
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
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