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/TopicHandler.php (9 issues)

1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Newbb;
4
5
/**
6
 * NewBB,  the forum module for XOOPS project
7
 *
8
 * @copyright      XOOPS Project (https://xoops.org)
9
 * @license        GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
10
 * @author         Taiwen Jiang (phppp or D.J.) <[email protected]>
11
 * @since          4.00
12
 */
13
14
use XoopsModules\Newbb\{
15
    Topic
0 ignored issues
show
This use statement conflicts with another class in this namespace, XoopsModules\Newbb\Topic. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
};
17
use XoopsModules\Tag\{
18
    Helper as TagHelper
19
};
20
21
\defined('NEWBB_FUNCTIONS_INI') || require $GLOBALS['xoops']->path('modules/newbb/include/functions.ini.php');
22
23
/**
24
 * Class TopicHandler
25
 */
26
final class TopicHandler extends \XoopsPersistableObjectHandler
27
{
28
    /**
29
     * @param \XoopsDatabase|null $db
30
     */
31
    public function __construct(\XoopsDatabase $db = null)
32
    {
33
        parent::__construct($db, 'newbb_topics', Topic::class, 'topic_id', 'topic_title');
34
    }
35
36
    /**
37
     * @param mixed $id ID
38
     * @param null|array $fields  fields to fetch
39
     * @return mixed|null XoopsObject
40
     */
41
    public function get($id = null, $fields = null) //get($id, $var = null)
42
    {
43
        $var  = $fields;
44
        $ret  = null;
0 ignored issues
show
The assignment to $ret is dead and can be removed.
Loading history...
45
        $tags = $var;
46
        if (!empty($var) && \is_string($var)) {
0 ignored issues
show
The condition is_string($var) is always false.
Loading history...
47
            $tags = [$var];
48
        }
49
        if (!$topicObject = parent::get($id, $tags)) {
50
            return null;
51
        }
52
        $ret = $topicObject;
53
        if (!empty($var) && \is_string($var)) {
0 ignored issues
show
The condition is_string($var) is always false.
Loading history...
54
            $ret = @$topicObject->getVar($var);
55
        }
56
57
        return $ret;
58
    }
59
60
    /**
61
     * insert an object into the database
62
     *
63
     * @param  \XoopsObject $object {@link \XoopsObject} reference to object
64
     * @param  bool        $force  flag to force the query execution despite security settings
65
     * @return mixed       object ID
66
     */
67
    public function insert(\XoopsObject $object, $force = true)
68
    {
69
        $topic = $object;
70
        if (!$topic->getVar('topic_time')) {
71
            $topic->setVar('topic_time', \time());
72
        }
73
        if (!parent::insert($topic, $force) || !$topic->getVar('approved')) {
74
            return $topic->getVar('topic_id');
75
        }
76
77
        $newbbConfig = \newbbLoadConfig();
78
        if (!empty($newbbConfig['do_tag']) && \class_exists('XoopsModules\Tag\FormTag')) {
79
            $tagHandler = TagHelper::getInstance()->getHandler('Tag');
80
            if ($tagHandler) {
0 ignored issues
show
$tagHandler is of type XoopsModules\Tag\TagHandler, thus it always evaluated to true.
Loading history...
81
                $tagHandler->updateByItem($topic->getVar('topic_tags', 'n'), $topic->getVar('topic_id'), 'newbb');
82
            }
83
        }
84
85
        return $topic->getVar('topic_id');
86
    }
87
88
    /**
89
     * @param object|Topic $object
90
     * @param bool  $force
91
     * @return bool
92
     */
93
    public function approve(object $object, bool $force = false): bool
94
    {
95
        $topic_id = $object->getVar('topic_id');
96
        if ($force) {
97
            $sql = 'UPDATE ' . $this->db->prefix('newbb_topics') . " SET approved = -1 WHERE topic_id = {$topic_id}";
98
        } else {
99
            $sql = 'UPDATE ' . $this->db->prefix('newbb_topics') . " SET approved = 1 WHERE topic_id = {$topic_id}";
100
        }
101
        if (!$result = $this->db->queryF($sql)) {
0 ignored issues
show
The assignment to $result is dead and can be removed.
Loading history...
102
            //xoops_error($this->db->error());
103
            return false;
104
        }
105
        $postHandler = Helper::getInstance()->getHandler('Post');
106
        $postsObject = $postHandler->getAll(new \Criteria('topic_id', $topic_id));
107
        foreach (\array_keys($postsObject) as $post_id) {
108
            $postHandler->approve($postsObject[$post_id]);
109
        }
110
        unset($postsObject);
111
        /** @var \XoopsModules\Newbb\StatsHandler $statsHandler */
112
        $statsHandler = Helper::getInstance()->getHandler('Stats');
113
        $statsHandler->update($object->getVar('forum_id'), 'topic');
114
115
        return true;
116
    }
117
118
    /**
119
     * get previous/next topic
120
     *
121
     * @param int $topic_id     current topic ID
122
     * @param int $action
123
     *                          <ul>
124
     *                          <li> -1: previous </li>
125
     *                          <li> 0: current </li>
126
     *                          <li> 1: next </li>
127
     *                          </ul>
128
     * @param int $forum_id     the scope for moving
129
     *                          <ul>
130
     *                          <li> >0 : inside the forum </li>
131
     *                          <li> <= 0: global </li>
132
     *                          </ul>
133
     * @return \XoopsObject|null
134
     */
135
    public function &getByMove(int $topic_id, int $action, int $forum_id = 0)
136
    {
137
        $topic = null;
138
        if (!empty($action)) {
139
            $sql    = 'SELECT * FROM ' . $this->table . ' WHERE 1=1' . (($forum_id > 0) ? ' AND forum_id=' . (int)$forum_id : '') . ' AND topic_id ' . (($action > 0) ? '>' : '<') . (int)$topic_id . ' ORDER BY topic_id ' . (($action > 0) ? 'ASC' : 'DESC') . ' LIMIT 1';
140
            $result = $this->db->query($sql);
141
            if ($this->db->isResultSet($result)) {
142
                $row = $this->db->fetchArray($result);
143
                if ($row) {
144
                    $topic = $this->create(false);
145
                    $topic->assignVars($row);
146
147
                    return $topic;
148
                }
149
            }
150
        }
151
        $topic = $this->get($topic_id);
152
153
        return $topic;
154
    }
155
156
    /**
157
     * @param int $post_id
158
     * @return null|\XoopsObject
159
     */
160
    public function &getByPost(int $post_id): ?\XoopsObject
161
    {
162
        $topic  = null;
163
        $sql    = 'SELECT t.* FROM ' . $this->db->prefix('newbb_topics') . ' t, ' . $this->db->prefix('newbb_posts') . ' p
164
                WHERE t.topic_id = p.topic_id AND p.post_id = ' . (int)$post_id;
165
        $result = $this->db->query($sql);
166
        if (!$this->db->isResultSet($result)) {
167
            //xoops_error($this->db->error());
168
            return $topic;
169
        }
170
        $row   = $this->db->fetchArray($result);
171
        $topic = $this->create(false);
172
        $topic->assignVars($row);
173
174
        return $topic;
175
    }
176
177
    /**
178
     * @param Topic  $topic
179
     * @param string $type
180
     * @return int
181
     */
182
    public function getPostCount(Topic $topic, string $type = ''): int
183
    {
184
        switch ($type) {
185
            case 'pending':
186
                $approved = 0;
187
                break;
188
            case 'deleted':
189
                $approved = -1;
190
                break;
191
            default:
192
                $approved = 1;
193
                break;
194
        }
195
        $criteria = new \CriteriaCompo(new \Criteria('topic_id', $topic->getVar('topic_id')));
0 ignored issues
show
It seems like $topic->getVar('topic_id') can also be of type array and array; however, parameter $value of Criteria::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

195
        $criteria = new \CriteriaCompo(new \Criteria('topic_id', /** @scrutinizer ignore-type */ $topic->getVar('topic_id')));
Loading history...
196
        $criteria->add(new \Criteria('approved', $approved));
197
        /** @var PostHandler $postHandler */
198
        $postHandler = Helper::getInstance()->getHandler('Post');
199
        $count       = $postHandler->getCount($criteria);
200
201
        return $count;
202
    }
203
204
    /**
205
     * @param int $topic_id
206
     * @return null|Post
207
     */
208
    public function &getTopPost(int $topic_id): ?Post
209
    {
210
        $post = null;
211
        $sql  = 'SELECT p.*, t.* FROM ' . $this->db->prefix('newbb_posts') . ' p,
212
            ' . $this->db->prefix('newbb_posts_text') . ' t
213
            WHERE
214
            p.topic_id = ' . $topic_id . ' AND p.pid = 0
215
            AND t.post_id = p.post_id';
216
217
        $result = $this->db->query($sql);
218
        if (!$this->db->isResultSet($result)) {
219
            //xoops_error($this->db->error());
220
            return $post;
221
        }
222
        /** @var PostHandler $postHandler */
223
        $postHandler = Helper::getInstance()->getHandler('Post');
224
        $myrow       = $this->db->fetchArray($result);
225
        /** @var Post $post */
226
        $post = $postHandler->create(false);
227
        $post->assignVars($myrow);
228
229
        return $post;
230
    }
231
232
    /**
233
     * @param int $topic_id
234
     * @return bool|int
235
     */
236
    public function getTopPostId(int $topic_id)
237
    {
238
        $sql    = 'SELECT MIN(post_id) AS post_id FROM ' . $this->db->prefix('newbb_posts') . ' WHERE topic_id = ' . $topic_id . ' AND pid = 0';
239
        $result = $this->db->query($sql);
240
        if (!$this->db->isResultSet($result)) {
241
//            \trigger_error("Query Failed! SQL: $sql- Error: " . $xoopsDB->error(), E_USER_ERROR);
242
            //xoops_error($this->db->error());
243
            return false;
244
        }
245
        [$post_id] = $this->db->fetchRow($result);
246
247
        return $post_id;
248
    }
249
250
    //Added by BigKev to get the next unread post ID based on the $lastreadpost_id
251
    /**
252
     * @param int $topic_id
253
     * @param int $lastreadpost_id
254
     * @return false|mixed
255
     */
256
    public function getNextPostId(int $topic_id, int $lastreadpost_id)
257
    {
258
        $sql    = 'SELECT MIN(post_id) AS post_id FROM ' . $this->db->prefix('newbb_posts') . ' WHERE topic_id = ' . $topic_id . ' AND post_id > ' . $lastreadpost_id . ' ORDER BY post_id LIMIT 1';
259
        $result = $this->db->query($sql);
260
        if (!$this->db->isResultSet($result)) {
261
            //            \trigger_error("Query Failed! SQL: $sql- Error: " . $xoopsDB->error(), E_USER_ERROR);
262
            //xoops_error($this->db->error());
263
            return false;
264
        }
265
        [$post_id] = $this->db->fetchRow($result);
266
267
        return $post_id;
268
    }
269
270
    /**
271
     * @param Topic  $topic
272
     * @param string $order
273
     * @param int    $perpage
274
     * @param int    $start
275
     * @param int    $post_id
276
     * @param string $type
277
     * @return array
278
     */
279
    public function &getAllPosts(Topic $topic, string $order = 'ASC', int $perpage = 10, int &$start = 0, int $post_id = 0, string $type = ''): array
280
    {
281
        $ret     = [];
282
        $perpage = ((int)$perpage > 0) ? (int)$perpage : (empty($GLOBALS['xoopsModuleConfig']['posts_per_page']) ? 10 : $GLOBALS['xoopsModuleConfig']['posts_per_page']);
283
        $start   = (int)$start;
284
        switch ($type) {
285
            case 'pending':
286
                $approveCriteria = ' AND p.approved = 0';
287
                break;
288
            case 'deleted':
289
                $approveCriteria = ' AND p.approved = -1';
290
                break;
291
            default:
292
                $approveCriteria = ' AND p.approved = 1';
293
                break;
294
        }
295
296
        if ($post_id) {
297
            if ('DESC' === $order) {
298
                $operator_for_position = '>';
299
            } else {
300
                $order                 = 'ASC';
301
                $operator_for_position = '<';
302
            }
303
            //$approveCriteria = ' AND approved = 1'; // any others?
304
            $sql    = 'SELECT COUNT(*) FROM ' . $this->db->prefix('newbb_posts') . ' AS p WHERE p.topic_id=' . (int)$topic->getVar('topic_id') . $approveCriteria . " AND p.post_id $operator_for_position $post_id";
305
            $result = $this->db->query($sql);
306
            if (!$this->db->isResultSet($result)) {
307
                //            \trigger_error("Query Failed! SQL: $sql- Error: " . $xoopsDB->error(), E_USER_ERROR);
308
                //xoops_error($this->db->error());
309
                return $ret;
310
            }
311
            [$position] = $this->db->fetchRow($result);
312
            $start = (int)($position / $perpage) * $perpage;
313
        }
314
315
        $sql    = 'SELECT p.*, t.* FROM ' . $this->db->prefix('newbb_posts') . ' p, ' . $this->db->prefix('newbb_posts_text') . ' t WHERE p.topic_id=' . $topic->getVar('topic_id') . ' AND p.post_id = t.post_id' . $approveCriteria . " ORDER BY p.post_id $order";
316
        $result = $this->db->query($sql, $perpage, $start);
317
        if (!$this->db->isResultSet($result)) {
318
            //xoops_error($this->db->error());
319
            return $ret;
320
        }
321
        $postHandler = Helper::getInstance()->getHandler('Post');
322
        while (false !== ($myrow = $this->db->fetchArray($result))) {
323
            $post = $postHandler->create(false);
324
            $post->assignVars($myrow);
325
            $ret[$myrow['post_id']] = $post;
326
            unset($post);
327
        }
328
329
        return $ret;
330
    }
331
332
    /**
333
     * @param array $postArray
334
     * @param int   $pid
335
     * @return mixed
336
     */
337
    public function &getPostTree(array $postArray, int $pid = 0)
338
    {
339
        $postsArray = null;
340
        //        require_once $GLOBALS['xoops']->path('modules/newbb/class/Tree.php');
341
        $newbbTree = new Tree('newbb_posts');
342
        $newbbTree->setPrefix('&nbsp;&nbsp;');
343
        $newbbTree->setPostArray($postArray);
344
        $newbbTree->getPostTree($postsArray, $pid);
345
346
        return $postsArray;
347
    }
348
349
    /**
350
     * @param Topic $topic
351
     * @param array $postArray
352
     * @return array
353
     */
354
    public function showTreeItem(Topic $topic, array &$postArray): array
355
    {
356
        global $viewtopic_users, $myts;
357
358
        $postArray['post_time'] = \newbbFormatTimestamp($postArray['post_time']);
359
360
        if (!empty($postArray['icon'])) {
361
            $postArray['icon'] = '<img src="' . XOOPS_URL . '/images/subject/' . \htmlspecialchars((string)$postArray['icon'], \ENT_QUOTES | \ENT_HTML5) . '" alt="" >';
362
        } else {
363
            $postArray['icon'] = '<a name="' . $postArray['post_id'] . '"><img src="' . XOOPS_URL . '/images/icons/no_posticon.gif" alt="" ></a>';
364
        }
365
366
        $postArray['subject'] = '<a href="viewtopic.php?viewmode=thread&amp;topic_id=' . $topic->getVar('topic_id') . '&amp;forum=' . $postArray['forum_id'] . '&amp;post_id=' . $postArray['post_id'] . '">' . $postArray['subject'] . '</a>';
367
368
        $isActiveUser = false;
0 ignored issues
show
The assignment to $isActiveUser is dead and can be removed.
Loading history...
369
        if (isset($viewtopic_users[$postArray['uid']]['name'])) {
370
            $postArray['poster'] = $viewtopic_users[$postArray['uid']]['name'];
371
            if ($postArray['uid'] > 0) {
372
                $postArray['poster'] = '<a href="' . XOOPS_URL . '/userinfo.php?uid=' . $postArray['uid'] . '">' . $viewtopic_users[$postArray['uid']]['name'] . '</a>';
373
            }
374
        } else {
375
            $postArray['poster'] = empty($postArray['poster_name']) ? \htmlspecialchars((string)$GLOBALS['xoopsConfig']['anonymous'], \ENT_QUOTES | \ENT_HTML5) : $postArray['poster_name'];
376
        }
377
378
        return $postArray;
379
    }
380
381
    /**
382
     * @param Topic $topic
383
     * @param bool  $isApproved
384
     * @return array
385
     */
386
    public function getAllPosters(Topic $topic, bool $isApproved = true): array
387
    {
388
        $ret = [];
389
        $sql = 'SELECT DISTINCT uid FROM ' . $this->db->prefix('newbb_posts') . '  WHERE topic_id=' . $topic->getVar('topic_id') . ' AND uid>0';
390
        if ($isApproved) {
391
            $sql .= ' AND approved = 1';
392
        }
393
        $result = $this->db->query($sql);
394
        if ($this->db->isResultSet($result)) {
395
            while (false !== ($myrow = $this->db->fetchArray($result))) {
396
                $ret[] = $myrow['uid'];
397
            }
398
        }
399
400
        return $ret;
401
    }
402
403
    /**
404
     * @param \XoopsObject $object Topic
405
     * @param bool               $force
406
     * @return bool
407
     */
408
    public function delete(\XoopsObject $object, $force = true): bool
409
    {
410
        $topic = $object;
411
        $topicId = \is_object($topic) ? $topic->getVar('topic_id') : (int)$topic;
412
        if (empty($topicId)) {
413
            return false;
414
        }
415
        $postObject = $this->getTopPost($topicId);
416
        /** @var PostHandler $postHandler */
417
        $postHandler = Helper::getInstance()->getHandler('Post');
418
        $postHandler->myDelete($postObject, false, $force);
0 ignored issues
show
It seems like $postObject can also be of type null; however, parameter $post of XoopsModules\Newbb\PostHandler::myDelete() does only seem to accept XoopsModules\Newbb\Post, maybe add an additional type check? ( Ignorable by Annotation )

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

418
        $postHandler->myDelete(/** @scrutinizer ignore-type */ $postObject, false, $force);
Loading history...
419
420
        $newbbConfig = \newbbLoadConfig();
421
        /** @var \XoopsModules\Tag\TagHandler $tagHandler */
422
        if (!empty($newbbConfig['do_tag']) && \class_exists('TagFormTag') && $tagHandler = TagHelper::getInstance()->getHandler('Tag')) { //@xoops_getModuleHandler('tag', 'tag', true)) {
423
            $tagHandler->updateByItem([], $topicId, 'newbb');
424
        }
425
426
        return true;
427
    }
428
429
    // get permission
430
    // parameter: $type: 'post', 'view',  'reply', 'edit', 'delete', 'addpoll', 'vote', 'attach'
431
    // $gperm_names = "'forum_can_post', 'forum_can_view', 'forum_can_reply', 'forum_can_edit', 'forum_can_delete', 'forum_can_addpoll', 'forum_can_vote', 'forum_can_attach', 'forum_can_noapprove'";
432
433
    /**
434
     * @param int|Forum $forum
435
     * @param int|null  $topic_locked
436
     * @param string    $type
437
     * @return bool
438
     */
439
    public function getPermission($forum, ?int $topic_locked = null, string $type = 'view'): bool
440
    {
441
        $topic_locked ??= 0;
442
        static $_cachedTopicPerms;
443
        require_once \dirname(__DIR__) . '/include/functions.user.php';
444
        if (\newbbIsAdmin($forum)) {
445
            return true;
446
        }
447
448
        $forum_id = \is_object($forum) ? $forum->getVar('forum_id') : (int)$forum;
449
        if ($forum_id < 1) {
450
            return false;
451
        }
452
453
        if ($topic_locked && 'view' !== $type) {
454
            $permission = false;
455
        } else {
456
            /** var PermissionHandler $permHandler */
457
            $permHandler = Helper::getInstance()->getHandler('Permission');
458
            $permission  = $permHandler->getPermission('forum', $type, $forum_id);
459
        }
460
461
        return $permission;
462
    }
463
464
    /**
465
     * clean orphan items from database
466
     *
467
     * @param string $table_link
468
     * @param string $field_link
469
     * @param string $field_object
470
     * @return bool   true on success
471
     */
472
    public function cleanOrphan($table_link = '', $field_link = '', $field_object = ''): bool //cleanOrphan()
473
    {
474
        $this->deleteAll(new \Criteria('topic_time', '0'), true, true);
475
        parent::cleanOrphan($this->db->prefix('newbb_forums'), 'forum_id');
476
        parent::cleanOrphan($this->db->prefix('newbb_posts'), 'topic_id');
477
478
        return true;
479
    }
480
481
    /**
482
     * clean expired objects from database
483
     *
484
     * @param int $expire time limit for expiration
485
     * @return bool true on success
486
     */
487
    public function cleanExpires(int $expire = 0): bool
488
    {
489
        // irmtfan if 0 no cleanup look include/plugin.php
490
        if (!\func_num_args()) {
491
            $newbbConfig = \newbbLoadConfig();
492
            $expire      = isset($newbbConfig['pending_expire']) ? (int)$newbbConfig['pending_expire'] : 7;
493
            $expire      *= 24 * 3600; // days to seconds
494
        }
495
        if (empty($expire)) {
496
            return false;
497
        }
498
        $crit_expire = new \CriteriaCompo(new \Criteria('approved', '0', '<='));
499
        $crit_expire->add(new \Criteria('topic_time', \time() - (int)$expire, '<'));
500
501
        return $this->deleteAll($crit_expire, true/*, true*/);
502
    }
503
504
    // START irmtfan - rewrite topic synchronization function. add pid sync and remove hard-code db access
505
    /**
506
     * @param mixed $object
507
     * @param bool                         $force
508
     * @return bool
509
     */
510
    public function synchronization($object = null, bool $force = true): bool
511
    {
512
        if (!\is_object($object)) {
513
            $object = $this->get((int)$object);
514
        }
515
        if (!\is_object($object) || !$object->getVar('topic_id')) {
516
            return false;
517
        }
518
519
        /** @var PostHandler $postHandler */
520
        $postHandler = Helper::getInstance()->getHandler('Post');
521
        $criteria    = new \CriteriaCompo();
522
        $criteria->add(new \Criteria('topic_id', (string)$object->getVar('topic_id')), 'AND');
523
        $criteria->add(new \Criteria('approved', '1'), 'AND');
524
        $post_ids = $postHandler->getIds($criteria);
525
        if (empty($post_ids)) {
526
            return false;
527
        }
528
        $last_post     = \max($post_ids);
529
        $top_post      = \min($post_ids);
530
        $topic_replies = (is_countable($post_ids) ? \count($post_ids) : 0) - 1;
531
        if ($object->getVar('topic_last_post_id') != $last_post) {
532
            $object->setVar('topic_last_post_id', $last_post);
533
        }
534
        if ($object->getVar('topic_replies') != $topic_replies) {
535
            $object->setVar('topic_replies', $topic_replies);
536
        }
537
        $b1 = $this->insert($object, $force);
538
        $criteria->add(new \Criteria('post_id', $top_post, '<>'), 'AND');
539
        $criteria->add(new \Criteria('pid', '(' . \implode(', ', $post_ids) . ')', 'NOT IN'), 'AND');
540
        $b2       = $postHandler->updateAll('pid', $top_post, $criteria, $force);
541
        $criteria = new \CriteriaCompo();
542
        $criteria->add(new \Criteria('post_id', $top_post, '='), 'AND');
543
        $b3 = $postHandler->updateAll('pid', 0, $criteria, $force);
544
545
        return ($b1 && $b2 && $b3);
546
    }
547
548
    // END irmtfan - rewrite topic synchronization function. add pid sync and remove hard-code db access
549
    // START irmtfan getActivePolls
550
551
    /**
552
     * get all active poll modules in the current xoops installtion.
553
     * @return array $pollDirs = array($dirname1=>$dirname1, $dirname2=>$dirname2, ...) dirnames of all active poll modules
554
     */
555
    public function getActivePolls(): array
556
    {
557
        $pollDirs = [];
558
        $allDirs  = \xoops_getActiveModules();
559
        foreach ($allDirs as $dirname) {
560
            // pollresults.php file is exist in all xoopspoll versions and umfrage versions
561
            if (\file_exists($GLOBALS['xoops']->path('modules/' . $dirname . '/pollresults.php'))) {
562
                $pollDirs[$dirname] = $dirname;
563
            }
564
        }
565
566
        return $pollDirs;
567
    }
568
569
    // END irmtfan getActivePolls
570
571
    // START irmtfan findPollModule
572
573
    /**
574
     * find poll module that is in used in the current newbb installtion.
575
     * @param array $pollDirs  dirnames of all active poll modules
576
     * @return bool|string | true | false
577
     *                         $dir_def: dirname of poll module that is in used in the current newbb installtion.
578
     *                         true: no poll module is installed | newbb has no topic with poll | newbb has no topic
579
     *                         false: errors (see below xoops_errors)
580
     */
581
    public function findPollModule(array $pollDirs = [])
582
    {
583
        $dir_def = '';
584
        if (empty($pollDirs)) {
585
            $pollDirs = $this->getActivePolls();
586
        }
587
        if (empty($pollDirs)) {
588
            return true;
589
        }
590
        // if only one active poll module still we need to check!!!
591
        //if(count($pollDirs) === 1) return end($pollDirs);
592
        $topicPollObjs = $this->getAll(new \Criteria('topic_haspoll', '1'), ['topic_id', 'poll_id']);
593
        if (empty($topicPollObjs)) {
594
            return true;
595
        } // no poll or no topic!!!
596
        foreach ($topicPollObjs as $tObj) {
597
            $poll_idInMod = 0;
598
            foreach ($pollDirs as $dirname) {
599
                $pollObj = $tObj->getPoll($tObj->getVar('poll_id'), $dirname);
600
                if (\is_object($pollObj) && ($pollObj->getVar('poll_id') == $tObj->getVar('poll_id'))) {
601
                    ++$poll_idInMod;
602
                    $dir_def = $dirname;
603
                }
604
            }
605
            // Only one poll module should has this poll_id
606
            // if 0 there is an error
607
            if (0 == $poll_idInMod) {
608
                \xoops_error("Error: Cannot find poll module for poll_id='{$tObj->getVar('poll_id')}'");
609
610
                return false;
611
            }
612
            // if 1 => $dir_def is correct
613
            if (1 == $poll_idInMod) {
614
                return $dir_def;
615
            }
616
            // if more than 1 continue
617
        }
618
        // if there is some topics but no module or more than one module have polls
619
        \xoops_error(\_MD_NEWBB_ERROR_POLL_MODULE_NOT_FOUND);
620
621
        return false;
622
    }
623
    // END irmtfan findPollModule
624
}
625