Passed
Push — 2.x ( 984b77...9cccdb )
by Terry
01:57
created

ActionLogger::checkDirectory()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

241
    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...
242
    {
243
        $results = [];
244
245
        $fromYmd = date('Ymd', strtotime($fromYmd));
246
247
        $this->file = $fromYmd . '.' . $this->extension;
248
        $this->filePath = rtrim($this->directory, '/') . '/' . $this->file;
249
250
        if (file_exists($this->filePath)) {
251
252
            $logFile = fopen($this->filePath, 'r');
253
254
            if (is_resource($logFile)) {
255
                while (!feof($logFile)) {
256
                    $line = fgets($logFile);
257
    
258
                    if (!empty($line)) {
259
                        $data = explode(',', trim($line));
260
                    }
261
        
262
                    if (!empty($data[0])) {
263
                        $results[] = [
264
                            'ip'          => $data[0],
265
                            'session_id'  => $data[1],
266
                            'action_code' => $data[2],
267
                            'timesamp'    => $data[3],
268
                        ];
269
                    }
270
                    unset($line, $data);
271
                }
272
                fclose($logFile);
273
            }
274
        }
275
276
        return $results;
277
    }
278
279
    /**
280
     * Get data from log files.
281
     *
282
     * @param string $fromYmd The string in Ymd Date format.
283
     * @param string $toYmd   The end date.
284
     *
285
     * @return array
286
     */
287
    private function getDataFromRange(string $fromYmd = '', string $toYmd = ''): array
288
    {
289
        $results = [];
290
291
        // for quering date range.
292
        $fromYmd = date('Ymd', strtotime($fromYmd));
293
        $toYmd = date('Ymd', strtotime($toYmd));
294
295
        $begin = new DateTime($fromYmd);
296
        $end = new DateTime($toYmd);
297
        $end = $end->modify('+1 day'); 
298
299
        $interval = new DateInterval('P1D');
300
        $daterange = new DatePeriod($begin, $interval, $end);
301
302
        $logFile = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $logFile is dead and can be removed.
Loading history...
303
304
        foreach ($daterange as $date) {
305
306
            $thisDayLogFile = $this->directory . '/' . $date->format('Ymd') . '.' . $this->extension;
307
308
            if (file_exists($thisDayLogFile)) {
309
310
                $logFile = fopen($thisDayLogFile, 'r');
311
312
                if (is_resource($logFile)) {
313
                    while (!feof($logFile)) {
314
                        $line = fgets($logFile);
315
    
316
                        if (!empty($line)) {
317
                            $data = explode(',', trim($line));
318
                        }
319
    
320
                        if (!empty($data[0])) {
321
                            $results[] = [
322
                                'ip'          => $data[0],
323
                                'session_id'  => $data[1],
324
                                'action_code' => $data[2],
325
                                'timesamp'    => $data[3],
326
                            ];
327
                        }
328
                        unset($line, $data);
329
                    }
330
                    fclose($logFile);
331
                }
332
            }
333
        }
334
335
        return $results;
336
    }
337
}
338