Lines 84.21% 112 / 133
Methods 22.22% 2 / 9
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 getTrackingFilesStatistics 81.48% 22 / 27 0.00% 0 / 1 9.51
 getFirstTrackingDate 93.33% 14 / 15 0.00% 0 / 1 6.01
 getLastTrackingDate 94.11% 16 / 17 0.00% 0 / 1 8.01
 getAllTrackingDates 90.90% 10 / 11 0.00% 0 / 1 7.04
 deleteTrackingFiles 60.00% 12 / 20 0.00% 0 / 1 18.74
 clearAllVisits 55.55% 5 / 9 0.00% 0 / 1 5.40
 renderMonthSelector 92.30% 12 / 13 0.00% 0 / 1 3.00
 renderDaySelector 100.00% 20 / 20 100.00% 1 / 1 4
29readonly class StatisticsHelper
30{
31    public function __construct(
32        private Session $session,
33        private Visits $visits,
34        private Date $date,
35    ) {
36    }
37
38    public function getTrackingFilesStatistics(): stdClass
39    {
40        $numberOfDays = 0;
41        $first = PHP_INT_MAX;
42        $last = 0;
43        $dir = opendir((string) PMF_ROOT_DIR . '/content/core/data');
44        if ($dir === false) {
45            $result = new stdClass();
46            $result->numberOfDays = 0;
47            $result->firstDate = $first;
48            $result->lastDate = $last;
49            return $result;
50        }
51
52        while (true) {
53            $dat = readdir($dir);
54            if ($dat === false) {
55                break;
56            }
57
58            if ($dat !== '.' && $dat !== '..') {
59                ++$numberOfDays;
60            }
61
62            if ($this->date->getTrackingFileDateStart($dat) > $last) {
63                $last = $this->date->getTrackingFileDateStart($dat);
64            }
65
66            if (
67                $this->date->getTrackingFileDateStart($dat) < $first
68                && $this->date->getTrackingFileDateStart($dat) > 0
69            ) {
70                $first = $this->date->getTrackingFileDateStart($dat);
71            }
72        }
73
74        closedir($dir);
75
76        $result = new stdClass();
77        $result->numberOfDays = $numberOfDays;
78        $result->firstDate = $first;
79        $result->lastDate = $last;
80
81        return $result;
82    }
83
84    public function getFirstTrackingDate(int $firstDate): string
85    {
86        $request = Request::createFromGlobals();
87        $requestTime = $request->server->get('REQUEST_TIME');
88        $date = 0;
89
90        $trackingFile =
91            (string) PMF_ROOT_DIR . '/content/core/data/tracking' . date(format: 'dmY', timestamp: $firstDate);
92        if (is_file($trackingFile)) {
93            $fp = fopen(filename: $trackingFile, mode: 'r');
94            if ($fp === false) {
95                return Translation::getString('ad_sess_noentry');
96            }
97
98            while (($data = fgetcsv($fp, length: 1024, separator: ';', enclosure: '"', escape: '\\')) !== false) {
99                $field = $data[7] ?? null;
100                $date = is_string($field) && 10 === strlen($field) ? $field : $requestTime;
101            }
102
103            fclose($fp);
104            return $this->date->format(date(format: 'Y-m-d H:i', timestamp: (int) $date));
105        }
106
107        return Translation::getString('ad_sess_noentry');
108    }
109
110    public function getLastTrackingDate(int $lastDate): string
111    {
112        $request = Request::createFromGlobals();
113        $requestTime = $request->server->get('REQUEST_TIME');
114
115        $trackingFile =
116            (string) PMF_ROOT_DIR . '/content/core/data/tracking' . date(format: 'dmY', timestamp: $lastDate);
117        if (is_file($trackingFile)) {
118            $fp = fopen(filename: $trackingFile, mode: 'r');
119            if ($fp === false) {
120                return Translation::getString('ad_sess_noentry');
121            }
122
123            $date = null;
124            while (($data = fgetcsv($fp, length: 1024, separator: ';', enclosure: '"', escape: '\\')) !== false) {
125                $field = $data[7] ?? null;
126                $date = is_string($field) && 10 === strlen($field) ? $field : $requestTime;
127            }
128
129            fclose($fp);
130
131            if ($date === null || $date === 0) {
132                $date = $request->server->get('REQUEST_TIME');
133            }
134
135            return $this->date->format(date(format: 'Y-m-d H:i', timestamp: (int) $date));
136        }
137
138        return Translation::getString('ad_sess_noentry');
139    }
140
141    /**
142     * Returns all tracking dates.
143     *
144     * @return int[]
145     */
146    public function getAllTrackingDates(): array
147    {
148        $dir = opendir((string) PMF_ROOT_DIR . '/content/core/data');
149        $trackingDates = [];
150        if ($dir === false) {
151            return $trackingDates;
152        }
153
154        while (false !== ($dat = readdir($dir))) {
155            if (!($dat !== '.' && $dat !== '..' && strlen($dat) === 16 && !is_dir($dat))) {
156                continue;
157            }
158
159            $trackingDates[] = $this->date->getTrackingFileDateStart($dat);
160        }
161
162        closedir($dir);
163        sort($trackingDates);
164
165        return $trackingDates;
166    }
167
168    public function deleteTrackingFiles(string $month): bool
169    {
170        $dir = opendir((string) PMF_ROOT_DIR . '/content/core/data');
171        $first = PHP_INT_MAX;
172        $last = 0;
173        if ($dir === false) {
174            return false;
175        }
176
177        while (true) {
178            $trackingFile = readdir($dir);
179            if ($trackingFile === false) {
180                break;
181            }
182
183            // The filename format is: trackingDDMMYYYY
184            // e.g.: tracking02042006
185            if (!($trackingFile !== '.' && $trackingFile !== '..' && 10 === strpos($trackingFile, $month))) {
186                continue;
187            }
188
189            $candidateFirst = $this->date->getTrackingFileDateStart($trackingFile);
190            $candidateLast = $this->date->getTrackingFileDateEnd($trackingFile);
191            if ($candidateLast > 0 && $candidateLast > $last) {
192                $last = $candidateLast;
193            }
194
195            if ($candidateFirst > 0 && $candidateFirst < $first) {
196                $first = $candidateFirst;
197            }
198
199            unlink(PMF_CONTENT_DIR . '/core/data/' . $trackingFile);
200        }
201
202        closedir($dir);
203
204        return $this->session->deleteSessions($first, $last);
205    }
206
207    public function clearAllVisits(): bool
208    {
209        $this->visits->resetAll();
210
211        $files = glob(PMF_CONTENT_DIR . '/core/data/*');
212        if ($files === false) {
213            $files = [];
214        }
215
216        foreach ($files as $file) {
217            if (!is_file($file)) {
218                continue;
219            }
220
221            unlink($file);
222        }
223
224        return $this->session->deleteAllSessions();
225    }
226
227    public function renderMonthSelector(): string
228    {
229        $oldValue = (int) mktime(hour: 0, minute: 0, second: 0, month: 1, day: 1, year: 1970);
230        $renderedHtml = sprintf('<option value="" selected>%s</option>', Translation::getString('ad_stat_choose'));
231
232        $trackingDates = $this->getAllTrackingDates();
233        foreach ($trackingDates as $trackingDate) {
234            if (date(format: 'Y-m', timestamp: $oldValue) === date(format: 'Y-m', timestamp: (int) $trackingDate)) {
235                continue;
236            }
237
238            $renderedHtml .= sprintf(
239                '<option value="%s">%s</option>',
240                date(format: 'mY', timestamp: (int) $trackingDate),
241                date(format: 'Y-m', timestamp: (int) $trackingDate),
242            );
243            $oldValue = $trackingDate;
244        }
245
246        return $renderedHtml;
247    }
248
249    public function renderDaySelector(): string
250    {
251        $request = Request::createFromGlobals();
252        $trackingDates = $this->getAllTrackingDates();
253        $renderedHtml = '';
254
255        if ($trackingDates === []) {
256            return sprintf(
257                '%s<option value="" selected>%s</option>',
258                $renderedHtml,
259                Translation::getString('ad_stat_choose'),
260            );
261        }
262
263        foreach ($trackingDates as $trackingDate) {
264            $renderedHtml .= sprintf('<option value="%d"', $trackingDate);
265            if (
266                date(format: 'Y-m-d', timestamp: (int) $trackingDate) === date(
267                    format: 'Y-m-d',
268                    timestamp: (int) $request->server->get('REQUEST_TIME'),
269                )
270            ) {
271                $renderedHtml .= ' selected';
272            }
273
274            $renderedHtml .= '>';
275            $renderedHtml .= $this->date->format(date(format: 'Y-m-d H:i', timestamp: (int) $trackingDate));
276            $renderedHtml .= "</option>\n";
277        }
278
279        return $renderedHtml;
280    }
281}