1<?php
2
3// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
4namespace dokuwiki\plugin\statistics;
5
6/**
7 * Create the data for graph visualization
8 */
9class StatisticsGraph
10{
11    private \helper_plugin_statistics $hlp;
12    private string $from;
13    private string $to;
14    private int $width;
15    private int $height;
16
17    /**
18     * Initialize a new Graph
19     *
20     * @param \helper_plugin_statistics $hlp
21     * @param string $from From date
22     * @param string $to To date
23     * @param int $width width of the graph in pixels
24     * @param int $height height of the graph in pixels
25     */
26    public function __construct(\helper_plugin_statistics $hlp, $from, $to, $width, $height)
27    {
28        $this->hlp = $hlp;
29        $this->from = $from;
30        $this->to = $to;
31        $this->width = $width;
32        $this->height = $height;
33    }
34
35    /**
36     * Create a PieChart
37     *
38     * @param array $data associative array contianing label and values
39     */
40    protected function pieChart($data)
41    {
42        $data = [
43            'datasets' => [
44                [
45                    'data' => array_values($data),
46                ],
47            ],
48            'labels' => array_keys($data)
49
50        ];
51
52        $this->printGraph('countries', 'pie', $data);
53    }
54
55    /**
56     * Build a PieChart with only the top data shown and all other summarized
57     *
58     * @param string $query The function to call on the Query object to get the data
59     * @param ?string $key The key containing the label, or null for the first non-cnt key
60     * @param int $max How many discrete values to show before summarizing under "other"
61     */
62    public function sumUpPieChart($query, $key = null, $max = 4)
63    {
64        $result = $this->hlp->getQuery()->$query();
65        if (!$result) return;
66
67        if ($key === null) {
68            $keys = array_keys($result[0]);
69            $key = array_shift($keys);
70            if ($key === 'cnt') {
71                $key = array_shift($keys);
72            }
73        }
74
75        $data   = [];
76        $top    = 0;
77        foreach ($result as $row) {
78            if ($top < $max) {
79                $data[$row[$key]] = $row['cnt'];
80            } else {
81                $data['other'] += $row['cnt'];
82            }
83            $top++;
84        }
85        $this->pieChart($data);
86    }
87
88    /**
89     * Create a history graph for the given info type
90     *
91     * @param $info
92     */
93    protected function history($info)
94    {
95        $diff = abs(strtotime($this->from) - strtotime($this->to));
96        $days = floor($diff / (60 * 60 * 24));
97        if ($days > 365) {
98            $interval = 'months';
99        } elseif ($days > 56) {
100            $interval = 'weeks';
101        } else {
102            $interval = 'days';
103        }
104
105        $result = $this->hlp->getQuery()->history($info, $interval);
106
107        $data = [];
108        $times = [];
109        foreach ($result as $row) {
110            $data[] = $row['cnt'];
111            if ($interval == 'months') {
112                $times[] = substr($row['time'], 0, 4) . '-' . substr($row['time'], 4, 2);
113            } elseif ($interval == 'weeks') {
114                $times[] = $row['EXTRACT(YEAR FROM dt)'] . '-' . $row['time']; // FIXME
115            } else {
116                $times[] = substr($row['time'], -5); // FIXME
117            }
118        }
119
120        $data = [
121            'datasets' => [
122                [
123                    'label' => $this->hlp->getLang('graph_' . $info),
124                    'data' => $data,
125                ],
126            ],
127            'labels' => $times
128        ];
129
130        $this->printGraph("history_$info", 'line', $data);
131    }
132    #region Graphbuilding functions
133
134    public function countries()
135    {
136        $this->sumUpPieChart('countries', 'country');
137    }
138
139    public function searchengines()
140    {
141        $this->sumUpPieChart('searchengines', 'engine', 3);
142    }
143
144    public function browsers()
145    {
146        $this->sumUpPieChart('browsers', 'browser');
147    }
148
149    public function os()
150    {
151        $this->sumUpPieChart('os', 'os');
152    }
153
154    public function topdomain()
155    {
156        $this->sumUpPieChart('topdomain', 'domain');
157    }
158
159    public function topuser()
160    {
161        $this->sumUpPieChart('topuser', 'user');
162    }
163
164    public function topeditor()
165    {
166        $this->sumUpPieChart('topeditor', 'user');
167    }
168
169    public function topgroup()
170    {
171        $this->sumUpPieChart('topgroup', 'group');
172    }
173
174    public function topgroupedit()
175    {
176        $this->sumUpPieChart('topgroupedit', 'group');
177    }
178
179    public function viewport()
180    {
181        $result = $this->hlp->getQuery()->viewport();
182        $data = [];
183
184        foreach ($result as $row) {
185            $data[] = [
186                'x' => $row['res_x'],
187                'y' => $row['res_y'],
188                'r' => floor($row['cnt'] / 10),
189            ];
190        }
191
192        $data = [
193            'datasets' => [
194                [
195                    'label' => $this->hlp->getLang('viewport'),
196                    'data' => $data
197                ]
198            ],
199        ];
200
201        $this->printGraph('viewport', 'bubble', $data);
202    }
203
204    public function resolution()
205    {
206        $result = $this->hlp->getQuery()->resolution();
207        $data = [];
208
209        foreach ($result as $row) {
210            $data[] = [
211                'x' => $row['res_x'],
212                'y' => $row['res_y'],
213                'r' => floor($row['cnt'] / 10),
214            ];
215        }
216
217        $data = [
218            'datasets' => [
219                [
220                    'label' => $this->hlp->getLang('resolution'),
221                    'data' => $data
222                ]
223            ],
224        ];
225
226        $this->printGraph('resolution', 'bubble', $data);
227    }
228
229
230    public function history_page_count()
231    {
232        $this->history('page_count');
233    }
234
235    public function history_page_size()
236    {
237        $this->history('page_size');
238    }
239
240    public function history_media_count()
241    {
242        $this->history('media_count');
243    }
244
245    public function history_media_size()
246    {
247        $this->history('media_size');
248    }
249
250    public function dashboardviews()
251    {
252        $hours  = ($this->from == $this->to);
253        $result = $this->hlp->getQuery()->dashboardviews($hours);
254        $data1  = [];
255        $data2  = [];
256        $data3  = [];
257        $times  = [];
258
259        foreach ($result as $time => $row) {
260            $data1[] = (int) $row['pageviews'];
261            $data2[] = (int) $row['sessions'];
262            $data3[] = (int) $row['visitors'];
263            $times[] = $time . ($hours ? 'h' : '');
264        }
265
266        $data = [
267            'datasets' => [
268                [
269                    'label' => $this->hlp->getLang('graph_views'),
270                    'data' => $data1,
271                ],
272                [
273                    'label' => $this->hlp->getLang('graph_sessions'),
274                    'data' => $data2,
275                ],
276                [
277                    'label' => $this->hlp->getLang('graph_visitors'),
278                    'data' => $data3,
279                ],
280            ],
281            'labels' => $times
282        ];
283
284        $this->printGraph('dashboardviews', 'line', $data);
285    }
286
287    public function dashboardwiki($js = false)
288    {
289        $hours  = ($this->from == $this->to);
290        $result = $this->hlp->getQuery()->dashboardwiki($hours);
291        $data1  = [];
292        $data2  = [];
293        $data3  = [];
294        $times  = [];
295
296        foreach ($result as $time => $row) {
297            $data1[] = (int) ($row['E'] ?? 0);
298            $data2[] = (int) ($row['C'] ?? 0);
299            $data3[] = (int) ($row['D'] ?? 0);
300            $times[] = $time . ($hours ? 'h' : '');
301        }
302        $data = [
303            'datasets' => [
304                [
305                    'label' => $this->hlp->getLang('graph_edits'),
306                    'data' => $data1,
307                ],
308                [
309                    'label' => $this->hlp->getLang('graph_creates'),
310                    'data' => $data2,
311                ],
312                [
313                    'label' => $this->hlp->getLang('graph_deletions'),
314                    'data' => $data3,
315                ],
316            ],
317            'labels' => $times
318        ];
319
320        $this->printGraph('dashboardwiki', 'line', $data);
321    }
322
323    /**
324     * @param string $name
325     * @param string $type
326     * @param array $data
327     * @return void
328     */
329    protected function printGraph(string $name, string $type, array $data)
330    {
331        $json = htmlspecialchars(json_encode($data), ENT_QUOTES, 'UTF-8');
332        $tpl = '
333        <chart-component
334            width="%d"
335            height="%d"
336            name="%s"
337            type="%s"
338            data="%s"></chart-component>
339        ';
340
341        echo sprintf($tpl, $this->width, $this->height, $name, $type, $json);
342    }
343
344    #endregion Graphbuilding functions
345}
346