StatsHandler   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 166
dl 0
loc 249
rs 9.92
c 0
b 0
f 0
wmc 31

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 8 2
A __construct() 0 7 1
B update() 0 57 7
F reset() 0 107 13
B getStats() 0 26 8
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
\defined('NEWBB_FUNCTIONS_INI') || require \dirname(__DIR__) . '/include/functions.ini.php';
14
15
\define('NEWBB_STATS_TYPE_TOPIC', 1);
16
\define('NEWBB_STATS_TYPE_POST', 2);
17
\define('NEWBB_STATS_TYPE_DIGEST', 3);
18
\define('NEWBB_STATS_TYPE_VIEW', 4);
19
20
\define('NEWBB_STATS_PERIOD_TOTAL', 1);
21
\define('NEWBB_STATS_PERIOD_DAY', 2);
22
\define('NEWBB_STATS_PERIOD_WEEK', 3);
23
\define('NEWBB_STATS_PERIOD_MONTH', 4);
24
25
/**
26
 * Stats for forum
27
 */
28
class StatsHandler
29
{
30
    /** @var \XoopsMySQLDatabase|null $db */
31
    public ?\XoopsMySQLDatabase $db;
32
    /** @var string $table */
33
    public string $table;
34
    /** @var array $param */
35
    public array $param = [
36
        'type'   => ['topic', 'post', 'digest', 'view'],
37
        'period' => ['total', 'day', 'week', 'month'],
38
    ];
39
40
    /**
41
     * @param \XoopsMySQLDatabase|null $db
42
     */
43
    public function __construct(\XoopsMySQLDatabase $db = null)
44
    {
45
        $this->db = $db;
46
        //if (!$db || !($db instanceof \XoopsMySQLDatabase)) {
47
//        $this->db = $GLOBALS['xoopsDB'];
48
        //}
49
        $this->table = $this->db->prefix('newbb_stats');
0 ignored issues
show
Bug introduced by
The method prefix() does not exist on null. ( Ignorable by Annotation )

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

49
        /** @scrutinizer ignore-call */ 
50
        $this->table = $this->db->prefix('newbb_stats');

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

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

Loading history...
50
    }
51
52
    /**
53
     * @param \XoopsMySQLDatabase|null $db
54
     * @return StatsHandler
55
     */
56
    public static function getInstance(\XoopsMySQLDatabase $db = null): StatsHandler
57
    {
58
        static $instance;
59
        if (null === $instance) {
60
            $instance = new static($db);
61
        }
62
63
        return $instance;
64
    }
65
66
    /**
67
     * @param int|string $id
68
     * @param string     $type
69
     * @param int        $increment
70
     * @return bool
71
     */
72
    public function update($id, string $type, int $increment = 1): bool
73
    {
74
        $id        = (int)$id;
75
        $increment = (int)$increment;
76
77
        if ($increment !== 0 || false === ($type = \array_search($type, $this->param['type'], true))) {
78
            return false;
79
        }
80
81
        $sql    = "    UPDATE {$this->table}"
82
                  . '    SET stats_value = CASE '
83
                  . "                    WHEN time_format = '' OR DATE_FORMAT(time_update, time_format) = DATE_FORMAT(NOW(), time_format)  THEN stats_value + '{$increment}' "
84
                  . "                    ELSE '{$increment}' "
85
                  . '                END, '
86
                  . '        time_update = NOW()'
87
                  . '    WHERE '
88
                  . "        (stats_id = '0' OR stats_id = '{$id}') "
89
                  . "        AND stats_type='{$type}' ";
90
        $result = $this->db->queryF($sql);
91
        $rows   = $this->db->getAffectedRows();
92
        if (0 == $rows) {
93
            $sql    = "    INSERT INTO {$this->table}"
94
                      . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) '
95
                      . '    VALUES '
96
                      . "        ('0', '{$increment}', '{$type}', '"
97
                      . \array_search('total', $this->param['period'], true)
98
                      . "', NOW(), ''), "
99
                      . "        ('0', '{$increment}', '{$type}', '"
100
                      . \array_search('day', $this->param['period'], true)
101
                      . "', NOW(), '%Y%j'), "
102
                      . "        ('0', '{$increment}', '{$type}', '"
103
                      . \array_search('week', $this->param['period'], true)
104
                      . "', NOW(), '%Y%u'), "
105
                      . "        ('0', '{$increment}', '{$type}', '"
106
                      . \array_search('month', $this->param['period'], true)
107
                      . "', NOW(), '%Y%m')";
108
            $result = $this->db->queryF($sql);
109
        }
110
        if ($rows < 2 * (is_countable($this->param['period']) ? \count($this->param['period']) : 0) && !empty($id)) {
111
            $sql    = "    INSERT INTO {$this->table}"
112
                      . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) '
113
                      . '    VALUES '
114
                      . "        ('{$id}', '{$increment}', '{$type}', '"
115
                      . \array_search('total', $this->param['period'], true)
116
                      . "', NOW(), ''), "
117
                      . "        ('{$id}', '{$increment}', '{$type}', '"
118
                      . \array_search('day', $this->param['period'], true)
119
                      . "', NOW(), '%Y%j'), "
120
                      . "        ('{$id}', '{$increment}', '{$type}', '"
121
                      . \array_search('week', $this->param['period'], true)
122
                      . "', NOW(), '%Y%u'), "
123
                      . "        ('{$id}', '{$increment}', '{$type}', '"
124
                      . \array_search('month', $this->param['period'], true)
125
                      . "', NOW(), '%Y%m')";
126
            $result = $this->db->queryF($sql);
127
        }
128
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type mysqli_result which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
129
    }
130
131
    /**
132
     *  Get stats of "Today"
133
     *
134
     * @param array $ids     ID of forum: > 0, forum; 0 - global; empty - all
135
     * @param array $types   type of stats items: 1 - topic; 2 - post; 3 - digest; 4 - click; empty - all
136
     * @param array $periods time period: 1 - all time; 2 - today; 3 - this week; 4 - this month; empty - all
137
     *
138
     * @return array[][]
139
     *
140
     * @psalm-return array<string, array<array>>
141
     */
142
    public function getStats(array $ids = [], array $types = [], array $periods = []): array
143
    {
144
        $ret = [];
145
146
        $_types = [];
147
        foreach ($types as $type) {
148
            $_types[] = \array_search($type, $this->param['type'], true);
149
        }
150
        $_periods = [];
151
        foreach ($periods as $period) {
152
            $_periods[] = \array_search($period, $this->param['period'], true);
153
        }
154
        $sql    = '    SELECT stats_id, stats_value, stats_type, stats_period ' . "    FROM {$this->table} " . '    WHERE ' . "        ( time_format = '' OR DATE_FORMAT(time_update, time_format) = DATE_FORMAT(NOW(), time_format) ) " . '        ' . ($ids === [] ? '' : 'AND stats_id IN (' . \implode(
155
                    ', ',
156
                    \array_map(
157
                        '\intval',
158
                        $ids
159
                    )
160
                ) . ')') . '        ' . ($_types === [] ? '' : 'AND stats_type IN (' . \implode(', ', $_types) . ')') . '        ' . ($_periods === [] ? '' : 'AND stats_period IN (' . \implode(', ', $_periods) . ')');
161
        $result = $this->db->query($sql);
162
        if ($this->db->isResultSet($result)) {
163
            while (false !== ($row = $this->db->fetchArray($result))) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of XoopsMySQLDatabase::fetchArray() does only seem to accept mysqli_result, 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

163
            while (false !== ($row = $this->db->fetchArray(/** @scrutinizer ignore-type */ $result))) {
Loading history...
164
                $ret[(string)$row['stats_id']][$this->param['type'][$row['stats_type']]][$this->param['period'][$row['stats_period']]] = $row['stats_value'];
165
            }
166
        }
167
        return $ret;
168
    }
169
170
    public function reset(): void
171
    {
172
        $this->db->queryF('TRUNCATE TABLE ' . $this->table);
173
        $now        = \time();
174
        $time_start = [
175
            'day'   => '%Y%j',
176
            'week'  => '%Y%u',
177
            'month' => '%Y%m',
178
        ];
179
        $counts     = [];
180
181
        $sql = '    SELECT forum_id' . '    FROM ' . $this->db->prefix('newbb_forums');
182
        $ret = $this->db->query($sql);
183
        if (!$this->db->isResultSet($ret)) {
184
            \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
185
        }
186
        while ([$forum_id] = $this->db->fetchRow($ret)) {
0 ignored issues
show
Bug introduced by
It seems like $ret can also be of type boolean; however, parameter $result of XoopsMySQLDatabase::fetchRow() does only seem to accept mysqli_result, 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

186
        while ([$forum_id] = $this->db->fetchRow(/** @scrutinizer ignore-type */ $ret)) {
Loading history...
187
            $sql    = '    SELECT COUNT(*), SUM(topic_views)' . '    FROM ' . $this->db->prefix('newbb_topics') . "    WHERE approved=1 AND forum_id = {$forum_id}";
188
            $result = $this->db->query($sql);
189
            if (!$this->db->isResultSet($result)) {
190
                \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
191
            }
192
            [$topics, $views] = $this->db->fetchRow($result);
193
            $this->update($forum_id, 'topic', (int)$topics);
194
            $this->update($forum_id, 'view', (int)$views);
195
196
            $sql    = '    SELECT COUNT(*)' . '    FROM ' . $this->db->prefix('newbb_topics') . "    WHERE approved=1 AND topic_digest >0 AND forum_id = {$forum_id}";
197
            $result = $this->db->query($sql);
198
            if (!$this->db->isResultSet($result)) {
199
                \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
200
            }
201
            [$digests] = $this->db->fetchRow($result);
202
            $this->update($forum_id, 'digest', (int)$digests);
203
204
            $sql    = '    SELECT COUNT(*)' . '    FROM ' . $this->db->prefix('newbb_posts') . "    WHERE approved=1 AND forum_id = {$forum_id}";
205
            $result = $this->db->query($sql);
206
            if (!$this->db->isResultSet($result)) {
207
                \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
208
            }
209
            [$posts] = $this->db->fetchRow($result);
210
            $this->update($forum_id, 'post', (int)$posts);
211
212
            foreach ($time_start as $period => $format) {
213
                $sql    = '    SELECT COUNT(*), SUM(topic_views)' . '    FROM ' . $this->db->prefix('newbb_topics') . "    WHERE approved=1 AND forum_id = {$forum_id}" . "        AND FROM_UNIXTIME(topic_time, '{$format}') >= FROM_UNIXTIME({$now}, '{$format}')";
214
                $result = $this->db->query($sql);
215
                if (!$this->db->isResultSet($result)) {
216
                    \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
217
                }
218
                [$topics, $views] = $this->db->fetchRow($result);
219
                $views = empty($views) ? 0 : $views; // null check
220
221
                $sql    = " INSERT INTO {$this->table}" . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) ' . '    VALUES ' . "        ('{$forum_id}', '{$topics}', '" . \array_search('topic', $this->param['type'], true) . "', '" . \array_search(
222
                        $period,
223
                        $this->param['period'],
224
                        true
225
                    ) . "', NOW(), '{$format}')";
226
                $result = $this->db->queryF($sql);
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
227
228
                $sql    = " INSERT INTO {$this->table}" . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) ' . '    VALUES ' . "        ('{$forum_id}', '{$views}', '" . \array_search('view', $this->param['type'], true) . "', '" . \array_search(
229
                        $period,
230
                        $this->param['period'],
231
                        true
232
                    ) . "', NOW(), '{$format}')";
233
                $result = $this->db->queryF($sql);
234
                @$counts['topic'][$period] += $topics;
235
                @$counts['view'][$period] += $views;
236
237
                $sql    = '    SELECT COUNT(*)' . '    FROM ' . $this->db->prefix('newbb_topics') . "    WHERE approved=1 AND topic_digest >0 AND forum_id = {$forum_id}" . "        AND FROM_UNIXTIME(digest_time, '{$format}') >= FROM_UNIXTIME({$now}, '{$format}')";
238
                $result = $this->db->query($sql);
239
                if (!$this->db->isResultSet($result)) {
240
                    \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
241
                }
242
                [$digests] = $this->db->fetchRow($result);
243
                $sql    = " INSERT INTO {$this->table}" . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) ' . '    VALUES ' . "        ('{$forum_id}', '{$digests}', '" . \array_search('digest', $this->param['type'], true) . "', '" . \array_search(
244
                        $period,
245
                        $this->param['period'],
246
                        true
247
                    ) . "', NOW(), '{$format}')";
248
                $result = $this->db->queryF($sql);
249
                @$counts['digest'][$period] += $digests;
250
251
                $sql    = ' SELECT COUNT(*)' . '    FROM ' . $this->db->prefix('newbb_posts') . "    WHERE approved=1 AND forum_id = {$forum_id}" . "        AND FROM_UNIXTIME(post_time, '{$format}') >= FROM_UNIXTIME({$now}, '{$format}')";
252
                $result = $this->db->query($sql);
253
                if (!$this->db->isResultSet($result)) {
254
                    \trigger_error("Query Failed! SQL: $sql- Error: " . $this->db->error(), E_USER_ERROR);
255
                }
256
                [$posts] = $this->db->fetchRow($result);
257
                $sql    = " INSERT INTO {$this->table}" . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) ' . '    VALUES ' . "        ('{$forum_id}', '{$posts}', '" . \array_search('post', $this->param['type'], true) . "', '" . \array_search(
258
                        $period,
259
                        $this->param['period'],
260
                        true
261
                    ) . "', NOW(), '{$format}')";
262
                $result = $this->db->queryF($sql);
263
                @$counts['post'][$period] += $posts;
264
            }
265
        }
266
267
        $sql    = "    DELETE FROM {$this->table}" . "    WHERE stats_id = '0' AND stats_period <> " . \array_search('total', $this->param['period'], true);
268
        $result = $this->db->queryF($sql);
269
        foreach ($time_start as $period => $format) {
270
            foreach (\array_keys($counts) as $type) {
271
                $sql    = " INSERT INTO {$this->table}" . '        (`stats_id`, `stats_value`, `stats_type`, `stats_period`, `time_update`, `time_format`) ' . '    VALUES ' . "        ('0', '{$counts[$type][$period]}', '" . \array_search($type, $this->param['type'], true) . "', '" . \array_search(
272
                        $period,
273
                        $this->param['period'],
274
                        true
275
                    ) . "', NOW(), '{$format}')";
276
                $result = $this->db->queryF($sql);
277
            }
278
        }
279
    }
280
}
281