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/TopicRenderer.php (13 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 Xmf\Request;
15
use XoopsModules\Newbb;
16
17
/**
18
 * Topic Renderer
19
 *
20
 * @author    D.J. (phppp)
21
 * @copyright copyright &copy; Xoops Project
22
 */
23
class TopicRenderer
24
{
25
    public array $vars = [];
26
    /**
27
     * reference to moduleConfig
28
     */
29
    public array $config;
30
    /**
31
     * Current user has no access to current page
32
     */
33
    private bool $noperm = false;
34
    /**
35
     * For multiple forums
36
     */
37
    public bool $is_multiple = false;
38
    /**
39
     * force to parse vars (run against static vars) irmtfan
40
     */
41
    public bool $force = false;
42
    /**
43
     * Vistitor's level: 0 - anonymous; 1 - user; 2 - moderator or admin
44
     */
45
    public int   $userlevel = 0;
46
    public array $query     = [];
47
    /**
48
     *  reference to an object handler
49
     *
50
     * @var \XoopsObjectHandler|\XoopsPersistableObjectHandler
51
     */
52
    private $handler;
53
    /**
54
     * Requested page
55
     */
56
    private string $page = 'list.topic.php';
57
    /**
58
     * query variables
59
     */
60
    private array $args = ['forum', 'uid', 'lastposter', 'type', 'status', 'mode', 'sort', 'order', 'start', 'since'];
61
    /**
62
     * Constructor
63
     */
64
    //    public function TopicRenderer()
65
    public function __construct()
66
    {
67
        $this->handler = Helper::getInstance()->getHandler('Topic');
68
    }
69
70
    /**
71
     * Access the only instance of this class
72
     * @return TopicRenderer
73
     */
74
    public static function getInstance(): TopicRenderer
75
    {
76
        static $instance;
77
        if (null === $instance) {
78
            $instance = new self();
79
        }
80
81
        return $instance;
82
    }
83
84
    public function init(): void
85
    {
86
        $this->noperm = false;
87
        $this->query  = [];
88
    }
89
90
    /**
91
     * @param string       $var    
92
     * @param string|mixed[] $val
93
     * @return mixed[]|int|string
94
     */
95
    public function setVar(string $var, $val)
96
    {
97
        switch ($var) {
98
            case 'forum':
99
                if (\is_numeric($val)) {
100
                    $val = (int)$val;
101
                    // START irmtfan - if the forum is array
102
                } elseif (\is_array($val)) {
103
                    $val = \implode('|', $val);
104
                    //} elseif (!empty($val)) {
105
                    //    $val = implode("|", array_map("intval", explode(", ", $val)));
106
                }
107
                // END irmtfan - if the forum is array
108
                break;
109
            case 'type':
110
            case 'mode':
111
            case 'order':
112
            case 'start':
113
            case 'since':
114
                $val = (int)$val;
115
                break;
116
            case 'uid': // irmtfan add multi topic poster
117
            case 'lastposter': // irmtfan add multi lastposter
118
                break;
119
            case 'status':
120
                // START irmtfan to accept multiple status
121
                $val = \is_array($val) ? $val : [$val];
122
                $val = \implode(',', $val);
123
                //$val = (in_array($val, array_keys($this->getStatus( $this->userlevel ))) ) ? $val : "all"; //irmtfan no need to check if status is empty or not
124
                //if ($val === "all" && !$this->is_multiple) $val = ""; irmtfan commented because it is done in sort
125
                // END irmtfan to accept multiple status
126
		$this->vars[$var] = $val;
127
                break;
128
            default:
129
                break;
130
        }
131
132
        return $val;
133
    }
134
135
    /**
136
     * @param array $vars
137
     */
138
    public function setVars(array $vars = []): void
139
    {
140
        $this->init();
141
142
        foreach ($vars as $var => $val) {
143
            if (!\in_array($var, $this->args, true)) {
144
                continue;
145
            }
146
        }
147
        $this->parseVars();
148
    }
149
150
    /**
151
     * @param string $hash request hash, i.e. get, post
152
     */
153
    public function setVarsFromRequest(string $hash = 'get'): void
154
    {
155
        $this->init();
156
157
        foreach ($this->args as $var) {
158
            if (Request::hasVar($var, $hash)) {
159
                continue;
160
            }
161
            switch ($var) {
162
                case 'forum':
163
                case 'status':
164
                  //  $this->setVar($var, Request::getArray($var, [], $hash));
165
			    $this->setVar('status', Request::getArray('status', [], $hash));
166
                    break;
167
                default:
168
                    $this->setVar($var, Request::getString($var, '', $hash));
169
                    break;
170
            }
171
        }
172
        $this->parseVars();
173
    }
174
175
    /**
176
     * @param string|null $status
177
     */
178
    public function myParseStatus(string $status = null): void
179
    {
180
        switch ($status) {
181
            case 'digest':
182
                $this->query['where'][] = 't.topic_digest = 1';
183
                break;
184
            case 'undigest':
185
                $this->query['where'][] = 't.topic_digest = 0';
186
                break;
187
            case 'sticky':
188
                $this->query['where'][] = 't.topic_sticky = 1';
189
                break;
190
            case 'unsticky':
191
                $this->query['where'][] = 't.topic_sticky = 0';
192
                break;
193
            case 'lock':
194
                $this->query['where'][] = 't.topic_status = 1';
195
                break;
196
            case 'unlock':
197
                $this->query['where'][] = 't.topic_status = 0';
198
                break;
199
            case 'poll':
200
                $this->query['where'][] = 't.topic_haspoll = 1';
201
                break;
202
            case 'unpoll':
203
                $this->query['where'][] = 't.topic_haspoll = 0';
204
                break;
205
            case 'voted':
206
                $this->query['where'][] = 't.votes > 0';
207
                break;
208
            case 'unvoted':
209
                $this->query['where'][] = 't.votes < 1';
210
                break;
211
            case 'replied':
212
                $this->query['where'][] = 't.topic_replies > 0';
213
                break;
214
            case 'unreplied':
215
                $this->query['where'][] = 't.topic_replies < 1';
216
                break;
217
            case 'viewed':
218
                $this->query['where'][] = 't.topic_views > 0';
219
                break;
220
            case 'unviewed':
221
                $this->query['where'][] = 't.topic_views < 1';
222
                break;
223
            case 'read':
224
                // Skip
225
                if (empty($this->config['read_mode'])) {
226
                    // Use database
227
                } elseif (2 == $this->config['read_mode']) {
228
                    // START irmtfan use read_uid to find the unread posts when the user is logged in
229
                    $read_uid = \is_object($GLOBALS['xoopsUser']) ? $GLOBALS['xoopsUser']->getVar('uid') : 0;
230
                    if (!empty($read_uid)) {
231
                        $this->query['join'][]  = 'LEFT JOIN ' . $this->handler->db->prefix('newbb_reads_topic') . ' AS r ON r.read_item = t.topic_id AND r.uid = ' . $read_uid . ' ';
232
                        $this->query['where'][] = 'r.post_id = t.topic_last_post_id';
233
                    }
234
235
                    // END irmtfan change criteria to get from uid p.uid = last post submit user id
236
                    // User cookie
237
                } elseif (1 == $this->config['read_mode']) {
238
                    // START irmtfan fix read_mode = 1 bugs - for all users (member and anon)
239
                    $startdate = !empty($this->vars['since']) ? (\time() - \newbbGetSinceTime($this->vars['since'])) : 0;
240
                    $lastvisit = \max($GLOBALS['last_visit'], $startdate);
241
                    if ($lastvisit) {
242
                        $readmode1query = '';
243
                        if ($lastvisit > $startdate) {
244
                            $readmode1query = 'p.post_time < ' . $lastvisit;
245
                        }
246
                        $topics         = [];
247
                        $topic_lastread = \newbbGetCookie('LT', true);
248
                        if ((is_countable($topic_lastread) ? \count($topic_lastread) : 0) > 0) {
0 ignored issues
show
It seems like $topic_lastread can also be of type string; however, parameter $value of count() does only seem to accept Countable|array, 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

248
                        if ((is_countable($topic_lastread) ? \count(/** @scrutinizer ignore-type */ $topic_lastread) : 0) > 0) {
Loading history...
249
                            foreach ($topic_lastread as $id => $time) {
250
                                if ($time > $lastvisit) {
251
                                    $topics[] = $id;
252
                                }
253
                            }
254
                        }
255
                        if (\count($topics) > 0) {
256
                            $topicquery = ' t.topic_id IN (' . \implode(',', $topics) . ')';
257
                            // because it should be OR
258
                            $readmode1query = ($readmode1query !== '' && $readmode1query !== '0') ? '(' . $readmode1query . ' OR ' . $topicquery . ')' : $topicquery;
259
                        }
260
                        $this->query['where'][] = $readmode1query;
261
                    }
262
                    // END irmtfan fix read_mode = 1 bugs - for all users (member and anon)
263
                }
264
                break;
265
            case 'unread':
266
                // Skip
267
                if (empty($this->config['read_mode'])) {
268
                    // Use database
269
                } elseif (2 == $this->config['read_mode']) {
270
                    // START irmtfan use read_uid to find the unread posts when the user is logged in
271
                    $read_uid = \is_object($GLOBALS['xoopsUser']) ? $GLOBALS['xoopsUser']->getVar('uid') : 0;
272
                    if (!empty($read_uid)) {
273
                        $this->query['join'][]  = 'LEFT JOIN ' . $this->handler->db->prefix('newbb_reads_topic') . ' AS r ON r.read_item = t.topic_id AND r.uid = ' . $read_uid . ' ';
274
                        $this->query['where'][] = '(r.read_id IS NULL OR r.post_id < t.topic_last_post_id)';
275
                    }
276
277
                    // END irmtfan change criteria to get from uid p.uid = last post submit user id
278
                    // User cookie
279
                } elseif (1 == $this->config['read_mode']) {
280
                    // START irmtfan fix read_mode = 1 bugs - for all users (member and anon)
281
                    $startdate = !empty($this->vars['since']) ? (\time() - \newbbGetSinceTime($this->vars['since'])) : 0;
282
                    $lastvisit = \max($GLOBALS['last_visit'], $startdate);
283
                    if ($lastvisit) {
284
                        if ($lastvisit > $startdate) {
285
                            $this->query['where'][] = 'p.post_time > ' . $lastvisit;
286
                        }
287
                        $topics         = [];
288
                        $topic_lastread = \newbbGetCookie('LT', true);
289
                        if ((is_countable($topic_lastread) ? \count($topic_lastread) : 0) > 0) {
290
                            foreach ($topic_lastread as $id => $time) {
291
                                if ($time > $lastvisit) {
292
                                    $topics[] = $id;
293
                                }
294
                            }
295
                        }
296
                        if (\count($topics) > 0) {
297
                            $this->query['where'][] = ' t.topic_id NOT IN (' . \implode(',', $topics) . ')';
298
                        }
299
                    }
300
                    // END irmtfan fix read_mode = 1 bugs - for all users (member and anon)
301
                }
302
                break;
303
            case 'pending':
304
                if ($this->userlevel < 2) {
305
                    $this->noperm = true;
306
                } else {
307
                    $this->query['where'][] = 't.approved = 0';
308
                }
309
                break;
310
            case 'deleted':
311
                if ($this->userlevel < 2) {
312
                    $this->noperm = true;
313
                } else {
314
                    $this->query['where'][] = 't.approved = -1';
315
                }
316
                break;
317
            case 'all': // For viewall.php; do not display sticky topics at first
318
            case 'active': // same as 'all'
319
                $this->query['where'][] = 't.approved = 1';
320
                break;
321
            default: // irmtfan do nothing
322
                break;
323
        }
324
    }
325
326
    /**
327
     * @param string $var
328
     * @param string|int|array|null $val
329
     */
330
    public function parseVar(string $var, $val): void
331
    {
332
        switch ($var) {
333
            case 'forum':
334
                /** @var ForumHandler $forumHandler */ $forumHandler = \XoopsModules\Newbb\Helper::getInstance()->getHandler('Forum');
335
                // START irmtfan - get forum Ids by values. parse positive values to forum IDs and negative values to category IDs. value=0 => all valid forums
336
                // Get accessible forums
337
                $accessForums = $forumHandler->getIdsByValues(\array_map('\intval', @\explode('|', (string)$val)));
338
                // Filter specified forums if any
339
                //if (!empty($val) && $_forums = @explode('|', $val)) {
340
                //$accessForums = array_intersect($accessForums, array_map('intval', $_forums));
341
                //}
342
                $this->vars['forum'] = $this->setVar('forum', $accessForums);
343
                // END irmtfan - get forum Ids by values. parse positive values to forum IDs and negative values to category IDs. value=0 => all valid forums
344
345
                if ($accessForums === []) {
346
                    $this->noperm = true;
347
                    // irmtfan - it just return return the forum_id only when the forum_id is the first allowed forum - no need for this code implode is enough removed.
348
                    //} elseif (count($accessForums) === 1) {
349
                    //$this->query["where"][] = "t.forum_id = " . $accessForums[0];
350
                } else {
351
                    $this->query['where'][] = 't.forum_id IN ( ' . \implode(', ', $accessForums) . ' )';
352
                }
353
                break;
354
            case 'uid': // irmtfan add multi topic poster
355
                if (-1 !== $val) {
356
                    $val                    = \implode(',', \array_map('\intval', \explode(',', $val)));
0 ignored issues
show
It seems like $val can also be of type array and null; however, parameter $string of explode() 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

356
                    $val                    = \implode(',', \array_map('\intval', \explode(',', /** @scrutinizer ignore-type */ $val)));
Loading history...
357
                    $this->query['where'][] = 't.topic_poster IN ( ' . $val . ' )';
358
                }
359
                break;
360
            case 'lastposter': // irmtfan add multi lastposter
361
                if (-1 !== $val) {
362
                    $val                    = \implode(',', \array_map('\intval', \explode(',', $val)));
363
                    $this->query['where'][] = 'p.uid IN ( ' . $val . ' )';
364
                }
365
                break;
366
            case 'since':
367
                if (!empty($val)) {
368
                    // START irmtfan if unread && read_mode = 1 and last_visit > startdate do not add where query | to accept multiple status
369
                    $startdate = \time() - \newbbGetSinceTime($val);
370
                    if (\in_array('unread', \explode(',', (string) $this->vars['status']), true) && 1 == $this->config['read_mode']
371
                        && $GLOBALS['last_visit'] > $startdate) {
372
                        break;
373
                    }
374
                    // irmtfan digest_time | to accept multiple status
375
                    if (\in_array('digest', \explode(',', (string) $this->vars['status']), true)) {
376
                        $this->query['where'][] = 't.digest_time > ' . $startdate;
377
                    }
378
                    // irmtfan - should be >= instead of =
379
                    $this->query['where'][] = 'p.post_time >= ' . $startdate;
380
                    // END irmtfan if unread && read_mode = 1 and last_visit > startdate do not add where query
381
                }
382
                break;
383
            case 'type':
384
                if (!empty($val)) {
385
                    $this->query['where'][] = 't.type_id = ' . $val;
0 ignored issues
show
Are you sure $val of type array|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

385
                    $this->query['where'][] = 't.type_id = ' . /** @scrutinizer ignore-type */ $val;
Loading history...
386
                }
387
                break;
388
            case 'status':
389
                // START irmtfan to accept multiple status
390
                $val = \explode(',', $val);
391
                // irmtfan - add 'all' to always parse t.approved = 1
392
                if (0 === \count(\array_intersect($val, ['all', 'active', 'pending', 'deleted']))) {
393
                    $val[] = 'all';
394
                }
395
                foreach ($val as $key => $status) {
396
                    $this->myParseStatus($status);
397
                }
398
                // END irmtfan to accept multiple status
399
                break;
400
            case 'sort':
401
                $sort = $this->getSort($val, 'sort');
0 ignored issues
show
It seems like $val can also be of type array; however, parameter $header of XoopsModules\Newbb\TopicRenderer::getSort() does only seem to accept null|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

401
                $sort = $this->getSort(/** @scrutinizer ignore-type */ $val, 'sort');
Loading history...
402
                if ($sort) {
403
                    $this->query['sort'][] = $sort . (empty($this->vars['order']) ? ' DESC' : ' ASC');
0 ignored issues
show
Are you sure $sort of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

403
                    $this->query['sort'][] = /** @scrutinizer ignore-type */ $sort . (empty($this->vars['order']) ? ' DESC' : ' ASC');
Loading history...
404
                } else { // irmtfan if sort is not in the list
405
                    $this->query['sort'][] = 't.topic_last_post_id' . (empty($this->vars['order']) ? ' DESC' : ' ASC');
406
                }
407
                break;
408
            default:
409
                break;
410
        }
411
    }
412
413
    /**
414
     * @return true
415
     */
416
    public function parseVars(): bool
417
    {
418
        static $parsed;
419
        // irmtfan - force to parse vars (run against static vars)
420
        if (null !== $parsed && !$this->force) {
421
            return true;
422
        }
423
424
        if (!isset($this->vars['forum'])) {
425
            $this->vars['forum'] = null;
426
        }
427
        //irmtfan parse status for rendering topic correctly - if empty($_GET(status)) it will show all topics include deleted and pendings. 'all' instead of all
428
        if (!isset($this->vars['status'])) {
429
            $this->vars['status'] = 'all';
430
        }
431
        // irmtfan if sort is not set or is empty get a default sort- if empty($_GET(sort)) | if sort=null eg: /list.topic.php?sort=
432
        if (empty($this->vars['sort'])) {
433
            $this->vars['sort'] = 'lastpost';
434
        } // use lastpost instead of sticky
435
436
        foreach ($this->vars as $var => $val) {
437
            $this->parseVar($var, $val);
438
            if (empty($val)) {
439
                unset($this->vars[$var]);
440
            }
441
        }
442
        $parsed = true;
443
444
        return true;
445
    }
446
447
    /**
448
     * @param string|null $header
449
     * @param string|null $var
450
     * @return array|string|null
451
     */
452
    public function getSort(string $header = null, string $var = null)
453
    {
454
        $headers = [
455
            'topic'           => [
456
                'title' => \_MD_NEWBB_TOPICS,
457
                'sort'  => 't.topic_title',
458
            ],
459
            'forum'           => [
460
                'title' => \_MD_NEWBB_FORUM,
461
                'sort'  => 't.forum_id',
462
            ],
463
            'poster'          => [
464
                'title' => \_MD_NEWBB_TOPICPOSTER, /*irmtfan _MD_NEWBB_POSTER to _MD_NEWBB_TOPICPOSTER*/
465
                'sort'  => 't.topic_poster',
466
            ],
467
            'replies'         => [
468
                'title' => \_MD_NEWBB_REPLIES,
469
                'sort'  => 't.topic_replies',
470
            ],
471
            'views'           => [
472
                'title' => \_MD_NEWBB_VIEWS,
473
                'sort'  => 't.topic_views',
474
            ],
475
            'lastpost'        => [ // irmtfan show topic_page_jump_icon smarty
476
                                   'title' => \_MD_NEWBB_LASTPOST,
477
                                   /*irmtfan _MD_NEWBB_DATE to _MD_NEWBB_LASTPOSTTIME again change to _MD_LASTPOST*/
478
                                   'sort'  => 't.topic_last_post_id',
479
            ],
480
            // START irmtfan add more sorts
481
            'lastposttime'    => [ // irmtfan same as lastpost
482
                                   'title' => \_MD_NEWBB_LASTPOSTTIME,
483
                                   'sort'  => 't.topic_last_post_id',
484
            ],
485
            'lastposter'      => [ // irmtfan
486
                                   'title' => \_MD_NEWBB_POSTER,
487
                                   'sort'  => 'p.uid', // poster uid
488
            ],
489
            'lastpostmsgicon' => [ // irmtfan
490
                                   'title' => \_MD_NEWBB_MESSAGEICON,
491
                                   'sort'  => 'p.icon', // post message icon
492
            ],
493
            'ratings'         => [
494
                'title' => \_MD_NEWBB_RATINGS,
495
                'sort'  => 't.rating', // irmtfan t.topic_rating to t.rating
496
            ],
497
            'votes'           => [
498
                'title' => \_MD_NEWBB_VOTES,
499
                'sort'  => 't.votes',
500
            ],
501
            'publish'         => [
502
                'title' => \_MD_NEWBB_TOPICTIME,
503
                'sort'  => 't.topic_id',
504
            ],
505
            'digest'          => [
506
                'title' => \_MD_NEWBB_DIGEST,
507
                'sort'  => 't.digest_time',
508
            ],
509
            'sticky'          => [
510
                'title' => \_MD_NEWBB_STICKY,
511
                'sort'  => 't.topic_sticky',
512
            ],
513
            'lock'            => [
514
                'title' => \_MD_NEWBB_LOCK,
515
                'sort'  => 't.topic_status',
516
            ],
517
            'poll'            => [
518
                'title' => \_MD_NEWBB_POLL_POLL,
519
                'sort'  => 't.poll_id',
520
            ],
521
        ];
522
        $types   = $this->getTypes();
523
        if (!empty($types)) {
524
            $headers['type'] = [
525
                'title' => \_MD_NEWBB_TYPE,
526
                'sort'  => 't.type_id',
527
            ];
528
        }
529
        if (2 === $this->userlevel) {
530
            $headers['approve'] = [
531
                'title' => \_MD_NEWBB_APPROVE,
532
                'sort'  => 't.approved',
533
            ];
534
        }
535
        // END irmtfan add more sorts
536
        if (empty($header) && empty($var)) {
537
            return $headers;
538
        }
539
        if (!empty($var) && !empty($header)) {
540
            return @$headers[$header][$var];
541
        }
542
        if (empty($var)) {
543
            return @$headers[$header];
544
        }
545
        $ret = null;
546
        foreach (\array_keys($headers) as $key) {
547
            $ret[$key] = @$headers[$key][$var];
548
        }
549
550
        return $ret;
551
    }
552
553
    // START irmtfan add Display topic headers function
554
    /**
555
     * @param string|null $header
556
     * @return array
557
     */
558
    public function getHeader(string $header = null): array
559
    {
560
        $headersSort = $this->getSort('', 'title');
0 ignored issues
show
Are you sure the assignment to $headersSort is correct as $this->getSort('', 'title') targeting XoopsModules\Newbb\TopicRenderer::getSort() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
561
        // additional headers - important: those cannot be in sort anyway
562
        $headers = \array_merge(
563
            $headersSort,
0 ignored issues
show
$headersSort of type null is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

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

563
            /** @scrutinizer ignore-type */ $headersSort,
Loading history...
564
            [
565
                'attachment' => \_MD_NEWBB_TOPICSHASATT, // show attachment smarty
566
                'read'       => \_MD_NEWBB_MARK_UNREAD . '|' . \_MD_NEWBB_MARK_READ, // read/unread show topic_folder smarty
567
                'pagenav'    => \_MD_NEWBB_PAGENAV_DISPLAY, // show topic_page_jump smarty - sort by topic_replies?
568
            ]
569
        );
570
571
        return $this->getFromKeys($headers, $header);
572
    }
573
574
    // END irmtfan add Display topic headers function
575
    /**
576
     * @param int|null     $type
577
     * @param null|string $status
578
     *
579
     * @return array
580
     */
581
    public function getStatus(int $type = null, string $status = null): array
582
    {
583
        $links       = [
584
            //""            => "", /* irmtfan remove empty array */
585
            'all'       => _ALL,
586
            'digest'    => \_MD_NEWBB_DIGEST,
587
            'undigest'  => \_MD_NEWBB_UNDIGEST, // irmtfan add
588
            'sticky'    => \_MD_NEWBB_STICKY, // irmtfan add
589
            'unsticky'  => \_MD_NEWBB_UNSTICKY, // irmtfan add
590
            'lock'      => \_MD_NEWBB_LOCK, // irmtfan add
591
            'unlock'    => \_MD_NEWBB_UNLOCK, // irmtfan add
592
            'poll'      => \_MD_NEWBB_TOPICHASPOLL, // irmtfan add
593
            'unpoll'    => \_MD_NEWBB_TOPICHASNOTPOLL, // irmtfan add
594
            'voted'     => \_MD_NEWBB_VOTED, // irmtfan add
595
            'unvoted'   => \_MD_NEWBB_UNVOTED, // irmtfan add
596
            'viewed'    => \_MD_NEWBB_VIEWED, // irmtfan add
597
            'unviewed'  => \_MD_NEWBB_UNVIEWED, // irmtfan add
598
            'replied'   => \_MD_NEWBB_REPLIED, // irmtfan add
599
            'unreplied' => \_MD_NEWBB_UNREPLIED,
600
            'read'      => \_MD_NEWBB_READ, // irmtfan add
601
            'unread'    => \_MD_NEWBB_UNREAD,
602
        ];
603
        $links_admin = [
604
            'active'  => \_MD_NEWBB_TYPE_ADMIN,
605
            'pending' => \_MD_NEWBB_TYPE_PENDING,
606
            'deleted' => \_MD_NEWBB_TYPE_DELETED,
607
        ];
608
609
        // all status, for admin
610
        if ($type > 1) {
611
            $links = \array_merge($links, $links_admin); // irmtfan to accept multiple status
612
        }
613
614
        return $this->getFromKeys($links, $status); // irmtfan to accept multiple status
615
    }
616
617
    /**
618
     * @param \Smarty $xoopsTpl
619
     * @throws \RuntimeException
620
     */
621
    public function buildSelection(\Smarty $xoopsTpl): void
622
    {
623
        $selection         = ['action' => $this->page];
624
        $selection['vars'] = $this->vars;
625
        require_once \dirname(__DIR__) . '/include/functions.forum.php';
626
        $forum_selected     = empty($this->vars['forum']) ? null : \explode('|', (string) @$this->vars['forum']);
627
        $selection['forum'] = '<select name="forum[]" multiple="multiple">';
628
        $selection['forum'] .= '<option value="0">' . \_MD_NEWBB_ALL . '</option>';
629
        $selection['forum'] .= \newbbForumSelectBox($forum_selected);
630
        $selection['forum'] .= '</select>';
631
632
        $sort_selected     = $this->vars['sort'];
633
        $sorts             = $this->getSort('', 'title');
0 ignored issues
show
Are you sure the assignment to $sorts is correct as $this->getSort('', 'title') targeting XoopsModules\Newbb\TopicRenderer::getSort() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
634
        $selection['sort'] = "<select name='sort'>";
635
        if (!\is_array($sorts)) {
0 ignored issues
show
The condition is_array($sorts) is always false.
Loading history...
636
            throw new \RuntimeException('$sorts must be an array.');
637
        }
638
        foreach ($sorts as $sort => $title) {
639
            $selection['sort'] .= "<option value='" . $sort . "' " . (($sort == $sort_selected) ? " selected='selected'" : '') . '>' . $title . '</option>';
640
        }
641
        $selection['sort'] .= '</select>';
642
643
        $selection['order'] = "<select name='order'>";
644
        $selection['order'] .= "<option value='0' " . (empty($this->vars['order']) ? " selected='selected'" : '') . '>' . _DESCENDING . '</option>';
645
        $selection['order'] .= "<option value='1' " . (!empty($this->vars['order']) ? " selected='selected'" : '') . '>' . _ASCENDING . '</option>';
646
        $selection['order'] .= '</select>';
647
648
        $since              = $this->vars['since'] ?? $this->config['since_default'];
649
        $selection['since'] = \newbbSinceSelectBox($since);
650
651
        $xoopsTpl->assign_by_ref('selection', $selection);
652
    }
653
654
    /**
655
     * @param \Smarty $xoopsTpl
656
     */
657
    public function buildSearch(\Smarty $xoopsTpl): void
658
    {
659
        $search             = [];
660
        $search['forum']    = @$this->vars['forum'];
661
        $search['since']    = @$this->vars['since'];
662
        $search['searchin'] = 'both';
663
664
        $xoopsTpl->assign_by_ref('search', $search);
665
    }
666
667
    /**
668
     * @param \Smarty $xoopsTpl
669
     * @throws \RuntimeException
670
     */
671
    public function buildHeaders(\Smarty $xoopsTpl): void
672
    {
673
        $headers_data = [];
674
        $args = [];
675
        foreach ($this->vars as $var => $val) {
676
            if ('sort' === $var || 'order' === $var) {
677
                continue;
678
            }
679
            $args[] = "{$var}={$val}";
680
        }
681
682
        $headers = $this->getSort('', 'title');
0 ignored issues
show
Are you sure the assignment to $headers is correct as $this->getSort('', 'title') targeting XoopsModules\Newbb\TopicRenderer::getSort() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
683
        if (!\is_array($headers)) {
0 ignored issues
show
The condition is_array($headers) is always false.
Loading history...
684
            throw new \RuntimeException('$headers must be an array.');
685
        }
686
        foreach ($headers as $header => $title) {
687
            $_args = ["sort={$header}"];
688
            if (@$this->vars['sort'] == $header) {
689
                $_args[] = 'order=' . ((@$this->vars['order'] + 1) % 2);
690
            }
691
            $headers_data[$header]['title'] = $title;
692
            $headers_data[$header]['link']  = $this->page . '?' . \implode('&amp;', \array_merge($args, $_args));
693
        }
694
        $xoopsTpl->assign_by_ref('headers', $headers_data);
695
    }
696
697
    /**
698
     * @param \Smarty $xoopsTpl
699
     */
700
    public function buildFilters(\Smarty $xoopsTpl): void
701
    {
702
        $args = [];
703
        foreach ($this->vars as $var => $val) {
704
            if ('status' === $var) {
705
                continue;
706
            }
707
            $args[] = "{$var}={$val}";
708
        }
709
710
        $links = $this->getStatus($this->userlevel);
711
712
        $status = [];
713
        foreach ($links as $link => $title) {
714
            $_args                  = ["status={$link}"];
715
            $status[$link]['title'] = $title;
716
            $status[$link]['link']  = $this->page . '?' . \implode('&amp;', [...$args, ...$_args]);
717
        }
718
        $xoopsTpl->assign_by_ref('filters', $status);
719
    }
720
721
    /**
722
     * @param int|null $type_id
723
     * @return mixed
724
     */
725
    public function getTypes(int $type_id = null)
726
    {
727
        static $types;
728
        if (!isset($types)) {
729
            /** @var TypeHandler $typeHandler */
730
            $typeHandler = Helper::getInstance()->getHandler('Type');
731
732
            $types = $typeHandler->getByForum(\explode('|', (string)@$this->vars['forum']));
733
        }
734
735
        if (($type_id === null || $type_id === 0)) {
736
            return $types;
737
        }
738
739
        return @$types[$type_id];
740
    }
741
742
    /**
743
     * @param \Smarty $xoopsTpl
744
     *
745
     * @return true
746
     */
747
    public function buildTypes(\Smarty $xoopsTpl): ?bool
748
    {
749
        $status = [];
750
        if (!$types = $this->getTypes()) {
751
            return true;
752
        }
753
754
        $args = [];
755
        foreach ($this->vars as $var => $val) {
756
            if ('type' === $var) {
757
                continue;
758
            }
759
            $args[] = "{$var}={$val}";
760
        }
761
762
        foreach ($types as $id => $type) {
763
            $_args                = ["type={$id}"];
764
            $status[$id]['title'] = $type['type_name'];
765
            $status[$id]['link']  = $this->page . '?' . \implode('&amp;', [...$args, ...$_args]);
766
        }
767
        $xoopsTpl->assign_by_ref('types', $status);
768
        return true;
769
    }
770
771
    /**
772
     * @param \Smarty $xoopsTpl
773
     *
774
     * @return true
775
     */
776
    public function buildCurrent(\Smarty $xoopsTpl): bool
777
    {
778
        if (empty($this->vars['status']) && !$this->is_multiple) {
779
            return true;
780
        }
781
782
        $args = [];
783
        foreach ($this->vars as $var => $val) {
784
            $args[] = "{$var}={$val}";
785
        }
786
787
        $status          = [];
788
        $status['title'] = \implode(',', $this->getStatus($this->userlevel, $this->vars['status'])); // irmtfan to accept multiple status
789
        //$status['link'] = $this->page.(empty($this->vars['status']) ? '' : '?status='.$this->vars['status']);
790
        $status['link'] = $this->page . ($args === [] ? '' : '?' . \implode('&amp;', $args));
791
792
        $xoopsTpl->assign_by_ref('current', $status);
793
        return true;
794
    }
795
796
    /**
797
     * @param \Smarty $xoopsTpl
798
     */
799
    public function buildPagenav(\Smarty $xoopsTpl): void
800
    {
801
        $count_topic = $this->getCount();
802
        if ($count_topic > $this->config['topics_per_page']) {
803
            $args = [];
804
            foreach ($this->vars as $var => $val) {
805
                if ('start' === $var) {
806
                    continue;
807
                }
808
                $args[] = "{$var}={$val}";
809
            }
810
            require_once $GLOBALS['xoops']->path('class/pagenav.php');
811
            $nav = new \XoopsPageNav($count_topic, $this->config['topics_per_page'], @$this->vars['start'], 'start', \implode('&amp;', $args));
812
            if (isset($GLOBALS['xoopsModuleConfig']['do_rewrite'])) {
813
                $nav->url = \formatURL(Request::getString('SERVER_NAME', '', 'SERVER')) . ' /' . $nav->url;
814
            }
815
            if ('select' === $this->config['pagenav_display']) {
816
                $navi = $nav->renderSelect();
817
            } elseif ('image' === $this->config['pagenav_display']) {
818
                $navi = $nav->renderImageNav(4);
819
            } else {
820
                $navi = $nav->renderNav(4);
821
            }
822
            $xoopsTpl->assign('pagenav', $navi);
823
        } else {
824
            $xoopsTpl->assign('pagenav', '');
825
        }
826
    }
827
828
    /**
829
     * @return int
830
     */
831
    public function getCount(): int
832
    {
833
        if ($this->noperm) {
834
            return 0;
835
        }
836
837
        $selects = [];
838
        $froms   = [];
839
        $joins   = [];
840
        $wheres  = [];
841
842
        // topic fields
843
        $selects[] = 'COUNT(*)';
844
845
        $froms[]  = $this->handler->db->prefix('newbb_topics') . ' AS t ';
846
        $joins[]  = 'LEFT JOIN ' . $this->handler->db->prefix('newbb_posts') . ' AS p ON p.post_id = t.topic_last_post_id';
847
        $wheres[] = '1 = 1';
848
849
        $sql = '    SELECT ' . \implode(', ', $selects) . '     FROM ' . \implode(', ', $froms) . '        ' . \implode(' ', $joins) . (!empty($this->query['join']) ? '        ' . \implode(' ', $this->query['join']) : '') . // irmtfan bug fix: Undefined index: join when post_excerpt = 0
850
               '     WHERE ' . \implode(' AND ', $wheres) . '        AND ' . @\implode(' AND ', @$this->query['where']);
851
852
        $result = $this->handler->db->query($sql);
853
        if (!$this->handler->db->isResultSet($result)) {
854
            //            \trigger_error("Query Failed! SQL: $sql- Error: " . $this->handler->db->error(), E_USER_ERROR);
855
            return 0;
856
        }
857
        [$count] = $this->handler->db->fetchRow($result);
858
859
        return (int)$count;
860
    }
861
862
    /**
863
     * @param \Smarty|null $xoopsTpl
864
     * @return array|void
865
     */
866
    public function renderTopics(\Smarty $xoopsTpl = null)
867
    {
868
        $myts = \MyTextSanitizer::getInstance(); // irmtfan Instanciate
869
870
        $ret = [];
871
        //$this->parseVars();
872
873
        if ($this->noperm) {
874
            if (\is_object($xoopsTpl)) {
875
                $xoopsTpl->assign_by_ref('topics', $ret);
876
877
                return;
878
            }
879
880
            return $ret;
881
        }
882
883
        $selects = [];
884
        $froms   = [];
885
        $joins   = [];
886
        $wheres  = [];
887
888
        // topic fields
889
        $selects[] = 't.*';
890
        // post fields
891
        $selects[] = 'p.post_time as last_post_time, p.poster_name as last_poster_name, p.icon, p.post_id, p.uid';
892
893
        $froms[]  = $this->handler->db->prefix('newbb_topics') . ' AS t ';
894
        $joins[]  = 'LEFT JOIN ' . $this->handler->db->prefix('newbb_posts') . ' AS p ON p.post_id = t.topic_last_post_id';
895
        $wheres[] = '1 = 1';
896
897
        if (!empty($this->config['post_excerpt'])) {
898
            $selects[]             = 'p.post_karma, p.require_reply, pt.post_text';
899
            $this->query['join'][] = 'LEFT JOIN ' . $this->handler->db->prefix('newbb_posts_text') . ' AS pt ON pt.post_id = t.topic_last_post_id';
900
        }
901
        //if (empty($this->query["sort"])) $this->query["sort"][] = 't.topic_last_post_id DESC'; // irmtfan commented no need
902
903
        $sql = '    SELECT ' . \implode(', ', $selects) . '     FROM ' . \implode(', ', $froms) . '        ' . \implode(' ', $joins) . (!empty($this->query['join']) ? '        ' . \implode(' ', $this->query['join']) : '') . // irmtfan bug fix: Undefined index join when post_excerpt = 0
904
               '     WHERE ' . \implode(' AND ', $wheres) . '        AND ' . @\implode(' AND ', @$this->query['where']) . '     ORDER BY ' . \implode(', ', $this->query['sort']);
905
906
        $result = $this->handler->db->query($sql, $this->config['topics_per_page'], @$this->vars['start']);
907
        if (!$this->handler->db->isResultSet($result)) {
908
            if (\is_object($xoopsTpl)) {
909
                $xoopsTpl->assign_by_ref('topics', $ret);
910
911
                return;
912
            }
913
914
            return $ret;
915
        }
916
917
        require_once \dirname(__DIR__) . '/include/functions.render.php';
918
        require_once \dirname(__DIR__) . '/include/functions.session.php';
919
        require_once \dirname(__DIR__) . '/include/functions.time.php';
920
        require_once \dirname(__DIR__) . '/include/functions.read.php';
921
        require_once \dirname(__DIR__) . '/include/functions.topic.php';
922
923
        $sticky    = 0;
924
        $topics    = [];
925
        $posters   = [];
926
        $reads     = [];
927
        $types     = [];
0 ignored issues
show
The assignment to $types is dead and can be removed.
Loading history...
928
        $forums    = [];
929
        $anonymous = \htmlspecialchars((string)$GLOBALS['xoopsConfig']['anonymous'], \ENT_QUOTES | \ENT_HTML5);
930
931
        while (false !== ($myrow = $this->handler->db->fetchArray($result))) {
932
            if ($myrow['topic_sticky']) {
933
                ++$sticky;
934
            }
935
936
            // ------------------------------------------------------
937
            // START irmtfan remove topic_icon hardcode smarty
938
            // topic_icon: just regular topic_icon
939
            if (!empty($myrow['icon'])) {
940
                $topic_icon = '<img style="text-align:middle;" src="' . XOOPS_URL . '/images/subject/' . \htmlspecialchars((string)$myrow['icon'], \ENT_QUOTES | \ENT_HTML5) . '" alt="" >';
941
            } else {
942
                $topic_icon = '<img style="text-align:middle;" src="' . XOOPS_URL . '/images/icons/no_posticon.gif" alt="" >';
943
            }
944
            // END irmtfan remove topic_icon hardcode smarty
945
946
            // ------------------------------------------------------
947
            // rating_img
948
            $rating = \number_format($myrow['rating'] / 2, 0);
949
            // irmtfan - add alt key for rating
950
            if ($rating < 1) {
951
                $rating_img = \newbbDisplayImage('blank');
952
            } else {
953
                $rating_img = \newbbDisplayImage('rate' . $rating, \constant('_MD_NEWBB_RATE' . $rating));
954
            }
955
956
            // ------------------------------------------------------
957
            // topic_page_jump
958
            $topic_page_jump      = '';
959
            $topic_page_jump_icon = '';
0 ignored issues
show
The assignment to $topic_page_jump_icon is dead and can be removed.
Loading history...
960
            $totalpages           = \ceil(($myrow['topic_replies'] + 1) / $this->config['posts_per_page']);
961
            if ($totalpages > 1) {
962
                $topic_page_jump .= '&nbsp;&nbsp;';
963
                $append          = false;
964
                for ($i = 1; $i <= $totalpages; ++$i) {
965
                    if ($i > 3 && $i < $totalpages) {
966
                        if (!$append) {
967
                            $topic_page_jump .= '...';
968
                            $append          = true;
969
                        }
970
                    } else {
971
                        $topic_page_jump .= '[<a href="' . XOOPS_URL . '/modules/newbb/viewtopic.php?topic_id=' . $myrow['topic_id'] . '&amp;start=' . (($i - 1) * $this->config['posts_per_page']) . '">' . $i . '</a>]';
972
                        // irmtfan remove here and move
973
                        //$topic_page_jump_icon = "<a href='" . XOOPS_URL . "/modules/newbb/viewtopic.php?topic_id=" . $myrow['topic_id'] . "&amp;start=" . (($i - 1) * $this->config['posts_per_page']) . "" . "'>" . newbbDisplayImage('document',_MD_NEWBB_GOTOLASTPOST) . '</a>';
974
                    }
975
                }
976
            }
977
            // BigKev73 - Added to make jump ICON, jump and scroll to the correct "last post"
978
            $topic_page_jump_icon = "<a href='" . XOOPS_URL . '/modules/newbb/viewtopic.php?topic_id=' . $myrow['topic_id'] . '&amp;post_id=' . $myrow['topic_last_post_id'] . '#forumpost=' . $myrow['topic_last_post_id'] . "'>" . \newbbDisplayImage('lastposticon', \_MD_NEWBB_GOTOLASTPOST) . '</a>';
979
980
            // irmtfan - move here for both topics with and without pages - change topic_id to post_id
981
            //$topic_page_jump_icon = "<a href='" . XOOPS_URL . '/modules/newbb/viewtopic.php?post_id=' . $myrow['topic_last_post_id'] . '' . "'>" . newbbDisplayImage('lastposticon', _MD_NEWBB_GOTOLASTPOST) . '</a>';
982
983
            // ------------------------------------------------------
984
            // => topic array
985
986
            $topic_title = \htmlspecialchars((string)$myrow['topic_title'], \ENT_QUOTES | \ENT_HTML5);
987
            // irmtfan use topic_title_excerpt for block topic title length
988
            $topic_title_excerpt = $topic_title;
989
            if (!empty($this->config['topic_title_excerpt'])) {
990
                $topic_title_excerpt = \xoops_substr($topic_title, 0, $this->config['topic_title_excerpt']);
991
            }
992
            // irmtfan hardcode class commented
993
            //if ($myrow['topic_digest']) {
994
            //   $topic_title = "<span class='digest'>" . $topic_title . "</span>";
995
            //}
996
997
            if (empty($this->config['post_excerpt'])) {
998
                $topic_excerpt = '';
999
            } elseif (($myrow['post_karma'] > 0 || $myrow['require_reply'] > 0) && !\newbbIsAdmin($myrow['forum_id'])) {
1000
                $topic_excerpt = '';
1001
            } else {
1002
                $topic_excerpt = \xoops_substr(\newbbHtml2text($myts->displayTarea($myrow['post_text'])), 0, $this->config['post_excerpt']);
1003
                $topic_excerpt = \str_replace('[', '&#91;', \htmlspecialchars((string) $topic_excerpt, \ENT_QUOTES | \ENT_HTML5));
1004
            }
1005
1006
            $topics[$myrow['topic_id']] = [
1007
                'topic_id'               => $myrow['topic_id'],
1008
                'topic_icon'             => $topic_icon,
1009
                'type_id'                => $myrow['type_id'],
1010
                'topic_title_excerpt'    => $topic_title_excerpt,
1011
                //irmtfan use topic_title_excerpt
1012
                //'topic_link'    => XOOPS_URL . '/modules/newbb/viewtopic.php?topic_id=' . $myrow['topic_id'], // . '&amp;forum=' . $myrow['forum_id'], // irmtfan comment
1013
                'topic_link'             => 'viewtopic.php?topic_id=' . $myrow['topic_id'],
1014
                // irmtfan remove hardcode link
1015
                'rating_img'             => $rating_img,
1016
                'votes'                  => $myrow['votes'],
1017
                //irmtfan added
1018
                'topic_page_jump'        => $topic_page_jump,
1019
                'topic_page_jump_icon'   => $topic_page_jump_icon,
1020
                'topic_replies'          => $myrow['topic_replies'],
1021
                'topic_poster_uid'       => $myrow['topic_poster'],
1022
                'topic_poster_name'      => !empty($myrow['poster_name']) ? \htmlspecialchars((string)$myrow['poster_name'], \ENT_QUOTES | \ENT_HTML5) : $anonymous,
1023
                'topic_views'            => $myrow['topic_views'],
1024
                'topic_time'             => \newbbFormatTimestamp((int)$myrow['topic_time']),
1025
                'topic_last_post_id'     => $myrow['topic_last_post_id'],
1026
                //irmtfan added
1027
                'topic_last_posttime'    => \newbbFormatTimestamp((int)$myrow['last_post_time']),
1028
                'topic_last_poster_uid'  => $myrow['uid'],
1029
                'topic_last_poster_name' => !empty($myrow['last_poster_name']) ? \htmlspecialchars((string)$myrow['last_poster_name'], \ENT_QUOTES | \ENT_HTML5) : $anonymous,
1030
                'topic_forum'            => $myrow['forum_id'],
1031
                'topic_excerpt'          => $topic_excerpt,
1032
                'sticky'                 => $myrow['topic_sticky'] ? \newbbDisplayImage('topic_sticky', \_MD_NEWBB_TOPICSTICKY) : '',
1033
                // irmtfan bug fixed
1034
                'lock'                   => $myrow['topic_status'] ? \newbbDisplayImage('topic_locked', \_MD_NEWBB_TOPICLOCK) : '',
1035
                //irmtfan added
1036
                'digest'                 => $myrow['topic_digest'] ? \newbbDisplayImage('topic_digest', \_MD_NEWBB_TOPICDIGEST) : '',
1037
                //irmtfan added
1038
                'poll'                   => $myrow['topic_haspoll'] ? \newbbDisplayImage('poll', \_MD_NEWBB_TOPICHASPOLL) : '',
1039
                //irmtfan added
1040
                'approve'                => $myrow['approved'],
1041
                //irmtfan added
1042
            ];
1043
1044
            /* users */
1045
            $posters[$myrow['topic_poster']] = 1;
1046
            $posters[$myrow['uid']]          = 1;
1047
            // reads
1048
            if (!empty($this->config['read_mode'])) {
1049
                $reads[$myrow['topic_id']] = (1 == $this->config['read_mode']) ? $myrow['last_post_time'] : $myrow['topic_last_post_id'];
1050
            }
1051
            // types
1052
            if (!empty($myrow['type_id'])) {
1053
                //$types[$myrow['type_id']] = 1;
1054
            }
1055
            // forums
1056
            $forums[$myrow['forum_id']] = 1;
1057
        }
1058
        $posters_name = \newbbGetUnameFromIds(\array_keys($posters), (bool)$this->config['show_realname'], true);
1059
        $topic_isRead = \newbbIsRead('topic', $reads);
1060
        /*
1061
        $type_list = [];
1062
        if (count($types) > 0) {
1063
            $typeHandler =  Newbb\Helper::getInstance()->getHandler('Type');
1064
            $type_list = $typeHandler->getAll(new \Criteria("type_id", "(".implode(", ", array_keys($types)).")", "IN"), null, false);
1065
        }
1066
        */
1067
        $type_list = $this->getTypes();
1068
        /** @var ForumHandler $forumHandler */
1069
        $forumHandler = Helper::getInstance()->getHandler('Forum');
1070
1071
        if (\count($forums) > 0) {
1072
            $forum_list = $forumHandler->getAll(new \Criteria('forum_id', '(' . \implode(', ', \array_keys($forums)) . ')', 'IN'), ['forum_name', 'hot_threshold'], false);
1073
        } else {
1074
            $forum_list = $forumHandler->getAll();
1075
        }
1076
1077
        foreach (\array_keys($topics) as $id) {
1078
            $topics[$id]['topic_read']       = empty($topic_isRead[$id]) ? 0 : 1; // add topic-read/topic-new smarty variable
1079
            $topics[$id]['topic_forum_link'] = '<a href="' . XOOPS_URL . '/modules/newbb/viewforum.php?forum=' . $topics[$id]['topic_forum'] . '">' . $forum_list[$topics[$id]['topic_forum']]['forum_name'] . '</a>';
1080
1081
            //irmtfan use topic_title_excerpt -- add else
1082
            if (!empty($topics[$id]['type_id']) && isset($type_list[$topics[$id]['type_id']])) {
1083
                $topics[$id]['topic_title'] = \getTopicTitle($topics[$id]['topic_title_excerpt'], $type_list[$topics[$id]['type_id']]['type_name'], $type_list[$topics[$id]['type_id']]['type_color']);
1084
            } else {
1085
                $topics[$id]['topic_title'] = $topics[$id]['topic_title_excerpt'];
1086
            }
1087
            $topics[$id]['topic_poster']      = !empty($posters_name[$topics[$id]['topic_poster_uid']]) ? $posters_name[$topics[$id]['topic_poster_uid']] : $topics[$id]['topic_poster_name'];
1088
            $topics[$id]['topic_last_poster'] = !empty($posters_name[$topics[$id]['topic_last_poster_uid']]) ? $posters_name[$topics[$id]['topic_last_poster_uid']] : $topics[$id]['topic_last_poster_name'];
1089
            // ------------------------------------------------------
1090
            // START irmtfan remove hardcodes from topic_folder smarty
1091
            // topic_folder: priority: newhot -> hot/new -> regular
1092
            //list($topic_status, $topic_digest, $topic_replies) = $topics[$id]["stats"]; irmtfan
1093
            // START irmtfan - add topic_folder_text for alt
1094
            //if ($topics[$id]["lock"] === 1) {
1095
            //    $topic_folder = 'topic_locked';
1096
            //    $topic_folder_text = _MD_NEWBB_TOPICLOCKED;
1097
            //} else {
1098
            //if ($topic_digest) {
1099
            //    $topic_folder = 'topic_digest';
1100
            //    $topic_folder_text = _MD_NEWBB_TOPICDIGEST;
1101
            if ($topics[$id]['topic_replies'] >= $forum_list[$topics[$id]['topic_forum']]['hot_threshold']) {
1102
                $topic_folder      = empty($topic_isRead[$id]) ? 'topic_hot_new' : 'topic_hot';
1103
                $topic_folder_text = empty($topic_isRead[$id]) ? \_MD_NEWBB_MORETHAN : \_MD_NEWBB_MORETHAN2;
1104
            } else {
1105
                $topic_folder      = empty($topic_isRead[$id]) ? 'topic_new' : 'topic';
1106
                $topic_folder_text = empty($topic_isRead[$id]) ? \_MD_NEWBB_NEWPOSTS : \_MD_NEWBB_NONEWPOSTS;
1107
            }
1108
            //}
1109
            // END irmtfan remove hardcodes from topic_folder smarty
1110
            $topics[$id]['topic_folder'] = \newbbDisplayImage($topic_folder, $topic_folder_text);
1111
            // END irmtfan - add topic_folder_text for alt
1112
1113
            unset($topics[$id]['topic_poster_name'], $topics[$id]['topic_last_poster_name']); // irmtfan remove $topics[$id]["stats"] because it is not exist now
1114
        }
1115
1116
        if (\count($topics) > 0) {
1117
            $sql    = ' SELECT DISTINCT topic_id FROM ' . $this->handler->db->prefix('newbb_posts') . " WHERE attachment != ''" . ' AND topic_id IN (' . \implode(',', \array_keys($topics)) . ')';
1118
            $result = $this->handler->db->query($sql);
1119
            if ($this->handler->db->isResultSet($result)) {
1120
                while ([$topic_id] = $this->handler->db->fetchRow($result)) {
1121
                    $topics[$topic_id]['attachment'] = '&nbsp;' . \newbbDisplayImage('attachment', \_MD_NEWBB_TOPICSHASATT);
1122
                }
1123
            }
1124
        }
1125
1126
        if (\is_object($xoopsTpl)) {
1127
            $xoopsTpl->assign_by_ref('sticky', $sticky);
1128
            $xoopsTpl->assign_by_ref('topics', $topics);
1129
1130
            return;
1131
        }
1132
1133
        return [$topics, $sticky];
1134
    }
1135
1136
    // START irmtfan to create an array from selected keys of an array
1137
    /**
1138
     * @param array       $array
1139
     * @param array|string|null   $keys
1140
     * @return array
1141
     */
1142
    public function getFromKeys(array $array, $keys = null): array
1143
    {
1144
        if (empty($keys)) {
1145
            return $array;
1146
        } // all keys
1147
        $keyarr = \is_string($keys) ? \explode(',', $keys) : $keys;
1148
        $keyarr = \array_intersect(\array_keys($array), $keyarr); // keys should be in array
1149
        $ret    = [];
1150
        foreach ($keyarr as $key) {
1151
            $ret[$key] = $array[$key];
1152
        }
1153
1154
        return $ret;
1155
    }
1156
    // END irmtfan to create an array from selected keys of an array
1157
}
1158