ActionLogger::getDataFromRange()   B
last analyzed

Complexity

Conditions 7
Paths 8

Size

Total Lines 44
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 7

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 44
ccs 26
cts 26
cp 1
rs 8.5706
cc 7
nc 8
nop 2
crap 7
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
use function date;
32
use function explode;
33
use function file_exists;
34
use function file_put_contents;
35
use function is_dir;
36
use function is_writable;
37
use function mkdir;
38
use function rmdir;
39
use function strtotime;
40
use function umask;
41
use function unlink;
42
43
/**
44
 * Action Logger only support storing log into files,
45
 * I don't want to make it complex, that's it.
46
 */
47
final class ActionLogger
48
{
49
    /**
50
     * The directory that data files stored to.
51
     *
52
     * @var string
53
     */
54
    protected $directory = '/tmp/';
55
56
    /**
57
     * The file's extension name'.
58
     *
59
     * @var string
60
     */
61
    protected $extension = 'log';
62
63
    /**
64
     * The file name.
65
     *
66
     * @var string
67
     */
68
    protected $file = '';
69
70
    /**
71
     * The file path.
72
     *
73
     * @var string
74
     */
75
    protected $filePath = '';
76
77
    /**
78
     * Constructor.
79
     *
80
     * @param string $directory The dirctory in where the logs were placed.
81
     * @param string $Ymd       The date string.
82
     */
83 98
    public function __construct(string $directory = '', string $Ymd = '')
84
    {
85 98
        if ('' !== $directory) {
86 98
            $this->directory = $directory;
87
        }
88
89 98
        $this->checkDirectory();
90
91 98
        if ('' === $Ymd) {
92 97
            $Ymd = date('Ymd', time());
93
        }
94
95 98
        $this->file = $Ymd . '.' . $this->extension;
96 98
        $this->filePath = rtrim($this->directory, '/') . '/' . $this->file;
97
    }
98
99
    /**
100
     * Append data to the file.
101
     *
102
     * @param array $record The log data.
103
     *
104
     * @return void
105
     */
106 20
    public function add(array $record): void
107
    {
108 20
        if (!empty($record['session_id'])) {
109 20
            $record['session_id'] = substr($record['session_id'], 0, 4);
110
        }
111
112 20
        $data = [];
113
114 20
        $data[0] = $record['ip'] ?? 'null';
115 20
        $data[1] = $record['session_id'] ?? 'null';
116 20
        $data[2] = $record['action_code'] ?? 'null';
117 20
        $data[3] = $record['timestamp'] ?? 'null';
118
119 20
        file_put_contents($this->filePath, implode(',', $data) . "\n", FILE_APPEND | LOCK_EX);
120
    }
121
122
    /**
123
     * Get data from log file.
124
     *
125
     * @param string $fromYmd The string in Ymd Date format.
126
     * @param string $toYmd   The end date.
127
     *
128
     * @return array
129
     */
130 10
    public function get(string $fromYmd = '', string $toYmd = ''): array
131
    {
132 10
        $results = [];
133
134 10
        if ('' === $toYmd) {
135 4
            $results = $this->getDataFromSingleDate($fromYmd);
136
        } else {
137 8
            $results = $this->getDataFromRange($fromYmd, $toYmd);
138
        }
139
140 10
        return $results;
141
    }
142
143
    /**
144
     * Create a directory for storing data files.
145
     *
146
     * @return bool
147
     */
148 98
    protected function checkDirectory(): bool
149
    {
150 98
        $result = true;
151
152 98
        if (!is_dir($this->directory)) {
153 12
            $originalUmask = umask(0);
154 12
            $result = mkdir($this->directory, 0777, true);
155 12
            umask($originalUmask);
156
        }
157
158
        // @codeCoverageIgnoreStart
159
        if (!is_writable($this->directory)) {
160
            throw new RuntimeException(
161
                'The directory usded by ActionLogger must be writable. (' . $this->directory . ')'
162
            );
163
        }
164 98
        // @codeCoverageIgnoreEnd
165
    
166
        return $result;
167
    }
168
169
    /**
170
     * Return current log's directory.
171
     *
172 68
     * @return string
173
     */
174 68
    public function getDirectory(): string
175
    {
176
        return $this->directory;
177
    }
178
179
    /**
180
     * Purge all logs and remove log directory.
181
     *
182 10
     * @return void
183
     */
184
    public function purgeLogs(): void
185
    {
186 10
        // Remove them recursively.
187 10
        
188 10
        if (file_exists($this->directory)) {
189
            $it = new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS);
190 10
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
191 8
192
            foreach ($files as $file) {
193
                if ($file->isDir()) {
194
                    // @codeCoverageIgnoreStart
195
                    rmdir($file->getRealPath());
196 8
                    // @codeCoverageIgnoreEnd
197
                } else {
198
                    unlink($file->getRealPath());
199 10
                }
200
            }
201 10
            unset($it, $files);
202 10
203
            if (is_dir($this->directory)) {
204
                rmdir($this->directory);
205
            }
206
        }
207
    }
208
209
    /**
210
     * Get current logger info.
211
     *
212 7
     * @return array
213
     */
214 7
    public function getCurrentLoggerInfo(): array
215
    {
216 7
        $data = [];
217 6
218 6
        if (file_exists($this->directory)) {
219
            $it = new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS);
220 6
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
221 4
222 4
            foreach ($files as $file) {
223 4
                if ($file->isFile()) {
224
                    $key = $file->getBasename('.log');
225
                    $size = $file->getSize();
226 4
227
                    // Data: datetime => file size.
228
                    $data[$key] = $size;
229
                }
230
            }
231 7
        }
232
233
        return $data;
234
    }
235
236
    /**
237
     * Get data from log file.
238
     *
239
     * @param string $fromYmd The string in Ymd Date format.
240
     *
241 4
     * @return array
242
     */
243 4
    private function getDataFromSingleDate(string $fromYmd = ''): array
244
    {
245 4
        $results = [];
246
247 4
        $fromYmd = date('Ymd', strtotime($fromYmd));
248 4
249
        $this->file = $fromYmd . '.' . $this->extension;
250 4
        $this->filePath = rtrim($this->directory, '/') . '/' . $this->file;
251
252 3
        if (file_exists($this->filePath)) {
253
            $logFile = fopen($this->filePath, 'r');
254 3
255 3
            if (is_resource($logFile)) {
256 3
                while (!feof($logFile)) {
257
                    $line = fgets($logFile);
258 3
    
259 3
                    if (!empty($line)) {
260 3
                        $data = explode(',', trim($line));
261 3
                        $results[] = [
262 3
                            'ip'          => $data[0],
263 3
                            'session_id'  => $data[1],
264 3
                            'action_code' => $data[2],
265 3
                            'timestamp'    => $data[3],
266
                        ];
267
                    }
268 3
269
                    unset($line, $data);
270 3
                }
271
                fclose($logFile);
272
            }
273
        }
274 4
275
        return $results;
276
    }
277
278
    /**
279
     * Get data from log files.
280
     *
281
     * @param string $fromYmd The string in Ymd Date format.
282
     * @param string $toYmd   The end date.
283
     *
284
     * @return array
285 8
     */
286
    private function getDataFromRange(string $fromYmd = '', string $toYmd = ''): array
287 8
    {
288
        $results = [];
289
290 8
        // for quering date range.
291 8
        $fromYmd = date('Ymd', strtotime($fromYmd));
292
        $toYmd = date('Ymd', strtotime($toYmd));
293 8
294 8
        $begin = new DateTime($fromYmd);
295 8
        $end = new DateTime($toYmd);
296
        $end = $end->modify('+1 day');
297 8
        $interval = new DateInterval('P1D');
298 8
        $daterange = new DatePeriod($begin, $interval, $end);
299
300 8
        foreach ($daterange as $date) {
301
            $thisDayLogFile = $this->directory . '/' . $date->format('Ymd') . '.' . $this->extension;
302 8
303
            if (file_exists($thisDayLogFile)) {
304 8
                $logFile = fopen($thisDayLogFile, 'r');
305
306 7
                if (is_resource($logFile)) {
307
                    while (!feof($logFile)) {
308 7
                        $line = fgets($logFile);
309 7
    
310 7
                        if (!empty($line)) {
311
                            $data = explode(',', trim($line));
312 7
                        }
313 7
    
314
                        if (!empty($data[0])) {
315
                            $results[] = [
316 7
                                'ip'          => $data[0],
317 7
                                'session_id'  => $data[1],
318 7
                                'action_code' => $data[2],
319 7
                                'timestamp'    => $data[3],
320 7
                            ];
321 7
                        }
322 7
                        unset($line, $data);
323
                    }
324 7
                    fclose($logFile);
325
                }
326 8
            }
327
        }
328
329
        return $results;
330
    }
331
}
332