Passed
Push — 2.x ( e7390d...51516d )
by Terry
02:00
created

ActionLogger::get()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 15
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\Firewall\Log;
14
15
use DateInterval;
16
use DatePeriod;
17
use DateTime;
18
use RecursiveDirectoryIterator;
19
use RecursiveIteratorIterator;
20
use RuntimeException;
21
22
use function date;
23
use function explode;
24
use function file_exists;
25
use function file_put_contents;
26
use function is_dir;
27
use function is_writable;
28
use function mkdir;
29
use function rmdir;
30
use function strtotime;
31
use function umask;
32
use function unlink;
33
34
/**
35
 * Action Logger only support storing log into files, 
36
 * I don't want to make it complex, that's it.
37
 */
38
final class ActionLogger
39
{
40
    /**
41
     * The directory that data files stored to.
42
     *
43
     * @var string
44
     */
45
    protected $directory = '/tmp/';
46
47
    /**
48
     * The file's extension name'.
49
     *
50
     * @var string
51
     */
52
    protected $extension = 'log';
53
54
    /**
55
     * The file name.
56
     *
57
     * @var string
58
     */
59
    protected $file = '';
60
61
    /**
62
     * The file path.
63
     *
64
     * @var string
65
     */
66
    protected $filePath = '';
67
68
    /**
69
     * Constructor.
70
     *
71
     * @param string $directory
72
     * @param string $Ymd
73
     */
74
    public function __construct(string $directory = '', $Ymd = '')
75
    {
76
        if ('' !== $directory) {
77
            $this->directory = $directory;
78
        }
79
80
        $this->checkDirectory();
81
82
        if ('' === $Ymd) {
83
            $Ymd = date('Ymd', time());
84
        }
85
86
        $this->file = $Ymd . '.' . $this->extension;
87
        $this->filePath = rtrim($this->directory, '/') . '/' . $this->file;
88
    }
89
90
    /**
91
     * Append data to the file.
92
     *
93
     * @param array $record The log data.
94
     *
95
     * @return void
96
     */
97
    public function add(array $record): void
98
    {
99
        if (!empty($record['session_id'])) {
100
            $record['session_id'] = substr($record['session_id'], 0, 4);
101
        }
102
103
        $data[0] = $record['ip']          ?? 'null';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
104
        $data[1] = $record['session_id']  ?? 'null';
105
        $data[2] = $record['action_code'] ?? 'null';
106
        $data[3] = $record['timesamp']    ?? 'null';
107
108
        file_put_contents($this->filePath, implode(',', $data) . "\n", FILE_APPEND | LOCK_EX);
109
    }
110
111
    /**
112
     * Get data from log file.
113
     *
114
     * @param string $fromYmd The string in Ymd Date format.
115
     * @param string $toYmd   The end date.
116
     * @return array
117
     */
118
    public function get(string $fromYmd = '', string $toYmd = ''): array
119
    {
120
        $results = [];
121
122
        if (empty($fromYmd)) {
123
            return [];
124
        }
125
126
        if ('' === $toYmd) {
127
            $results = $this->getDataFromSingleDate($fromYmd, $toYmd);
128
        } else {
129
            $results = $this->getDataFromRange($fromYmd, $toYmd);
130
        }
131
132
        return $results;
133
    }
134
135
    /**
136
     * Create a directory for storing data files.
137
     *
138
     * @return bool
139
     */
140
    protected function checkDirectory(): bool
141
    {
142
        $result = true;
143
144
        if (!is_dir($this->directory)) {
145
            $originalUmask = umask(0);
146
            $result = @mkdir($this->directory, 0777, true);
147
            umask($originalUmask);
148
        }
149
150
        // @codeCoverageIgnoreStart
151
        if (!is_writable($this->directory)) {
152
            throw new RuntimeException('The directory usded by ActionLogger must be writable. (' . $this->directory . ')');
153
        }
154
        // @codeCoverageIgnoreEnd
155
    
156
        return $result;
157
    }
158
159
    /**
160
     * Return current log's directory.
161
     *
162
     * @return string
163
     */
164
    public function getDirectory(): string
165
    {
166
        return $this->directory;
167
    }
168
169
    /**
170
     * Purge all logs and remove log directory.
171
     *
172
     * @return void
173
     */
174
    public function purgeLogs(): void
175
    {
176
        // Remove them recursively.
177
        
178
        if (file_exists($this->directory)) {
179
            $it = new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS);
180
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
181
182
            foreach ($files as $file) {
183
                if ($file->isDir()) {
184
                    // @codeCoverageIgnoreStart
185
                    rmdir($file->getRealPath());
186
                    // @codeCoverageIgnoreEnd
187
                } else {
188
                    unlink($file->getRealPath());
189
                }
190
            }
191
            unset($it, $files);
192
193
            if (is_dir($this->directory)) {
194
                rmdir($this->directory);
195
            }
196
        }
197
    }
198
199
    /**
200
     * Get current logger info.
201
     *
202
     * @return array
203
     */
204
    public function getCurrentLoggerInfo(): array
205
    {
206
        $data = [];
207
208
        if (file_exists($this->directory)) {
209
            $it = new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS);
210
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
211
212
            foreach ($files as $file) {
213
                if ($file->isFile()) {
214
                    $key = $file->getBasename('.log');
215
                    $size = $file->getSize();
216
217
                    // Data: datetime => file size.
218
                    $data[$key] = $size;
219
                } 
220
            }
221
        }
222
223
        return $data;
224
    }
225
226
/**
227
     * Get data from log file.
228
     *
229
     * @param string $fromYmd The string in Ymd Date format.
230
     * @param string $toYmd   The end date.
231
     *
232
     * @return array
233
     */
234
    private function getDataFromSingleDate(string $fromYmd = '', string $toYmd = ''): array
0 ignored issues
show
Unused Code introduced by
The parameter $toYmd is not used and could be removed. ( Ignorable by Annotation )

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

234
    private function getDataFromSingleDate(string $fromYmd = '', /** @scrutinizer ignore-unused */ string $toYmd = ''): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
    {
236
        $results = [];
237
238
        $fromYmd = date('Ymd', strtotime($fromYmd));
239
240
        $this->file = $fromYmd . '.' . $this->extension;
241
        $this->filePath = rtrim($this->directory, '/') . '/' . $this->file;
242
243
        if (file_exists($this->filePath)) {
244
245
            $logFile = fopen($this->filePath, 'r');
246
247
            while (!feof($logFile)) {
0 ignored issues
show
Bug introduced by
It seems like $logFile can also be of type false; however, parameter $handle of feof() does only seem to accept resource, 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

247
            while (!feof(/** @scrutinizer ignore-type */ $logFile)) {
Loading history...
248
                $line = fgets($logFile);
0 ignored issues
show
Bug introduced by
It seems like $logFile can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, 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
                $line = fgets(/** @scrutinizer ignore-type */ $logFile);
Loading history...
249
250
                if (!empty($line)) {
251
                    $data = explode(',', trim($line));
252
                }
253
    
254
                if (!empty($data[0])) {
255
                    $results[] = [
256
                        'ip'          => $data[0],
257
                        'session_id'  => $data[1],
258
                        'action_code' => $data[2],
259
                        'timesamp'    => $data[3],
260
                    ];
261
                }
262
                unset($line, $data);
263
            }
264
            fclose($logFile);
0 ignored issues
show
Bug introduced by
It seems like $logFile can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

264
            fclose(/** @scrutinizer ignore-type */ $logFile);
Loading history...
265
        }
266
267
        return $results;
268
    }
269
270
    /**
271
     * Get data from log files.
272
     *
273
     * @param string $fromYmd The string in Ymd Date format.
274
     * @param string $toYmd   The end date.
275
     *
276
     * @return array
277
     */
278
    private function getDataFromRange(string $fromYmd = '', string $toYmd = ''): array
279
    {
280
        $results = [];
281
282
        // for quering date range.
283
        $fromYmd = date('Ymd', strtotime($fromYmd));
284
        $toYmd = date('Ymd', strtotime($toYmd));
285
286
        $begin = new DateTime($fromYmd);
287
        $end = new DateTime($toYmd);
288
        $end = $end->modify('+1 day'); 
289
290
        $interval = new DateInterval('P1D');
291
        $daterange = new DatePeriod($begin, $interval, $end);
292
293
        $logFile = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $logFile is dead and can be removed.
Loading history...
294
295
        foreach ($daterange as $date) {
296
297
            $thisDayLogFile = $this->directory . '/' . $date->format('Ymd') . '.' . $this->extension;
298
299
            if (file_exists($thisDayLogFile)) {
300
301
                $logFile = fopen($thisDayLogFile, 'r');
302
303
                while (!feof($logFile)) {
0 ignored issues
show
Bug introduced by
It seems like $logFile can also be of type false; however, parameter $handle of feof() does only seem to accept resource, 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

303
                while (!feof(/** @scrutinizer ignore-type */ $logFile)) {
Loading history...
304
                    $line = fgets($logFile);
0 ignored issues
show
Bug introduced by
It seems like $logFile can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, 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

304
                    $line = fgets(/** @scrutinizer ignore-type */ $logFile);
Loading history...
305
306
                    if (!empty($line)) {
307
                        $data = explode(',', trim($line));
308
                    }
309
310
                    if (!empty($data[0])) {
311
                        $results[] = [
312
                            'ip'          => $data[0],
313
                            'session_id'  => $data[1],
314
                            'action_code' => $data[2],
315
                            'timesamp'    => $data[3],
316
                        ];
317
                    }
318
                    unset($line, $data);
319
                }
320
                fclose($logFile);
0 ignored issues
show
Bug introduced by
It seems like $logFile can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

320
                fclose(/** @scrutinizer ignore-type */ $logFile);
Loading history...
321
            }
322
        }
323
324
        return $results;
325
    }
326
}
327