Passed
Push — 2.x ( fd069b...31976b )
by Terry
02:06
created

ActionLogger::getDataFromSingleDate()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 19
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 34
rs 9.3222
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 = '', string $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 = [];
114
115
        $data[0] = $record['ip']          ?? 'null';
116
        $data[1] = $record['session_id']  ?? 'null';
117
        $data[2] = $record['action_code'] ?? 'null';
118
        $data[3] = $record['timesamp']    ?? 'null';
119
120
        file_put_contents($this->filePath, implode(',', $data) . "\n", FILE_APPEND | LOCK_EX);
121
    }
122
123
    /**
124
     * Get data from log file.
125
     *
126
     * @param string $fromYmd The string in Ymd Date format.
127
     * @param string $toYmd   The end date.
128
     *
129
     * @return array
130
     */
131
    public function get(string $fromYmd = '', string $toYmd = ''): array
132
    {
133
        $results = [];
134
135
        if ('' === $toYmd) {
136
            $results = $this->getDataFromSingleDate($fromYmd);
137
        } else {
138
            $results = $this->getDataFromRange($fromYmd, $toYmd);
139
        }
140
141
        return $results;
142
    }
143
144
    /**
145
     * Create a directory for storing data files.
146
     *
147
     * @return bool
148
     */
149
    protected function checkDirectory(): bool
150
    {
151
        $result = true;
152
153
        if (!is_dir($this->directory)) {
154
            $originalUmask = umask(0);
155
            $result = mkdir($this->directory, 0777, true);
156
            umask($originalUmask);
157
        }
158
159
        // @codeCoverageIgnoreStart
160
        if (!is_writable($this->directory)) {
161
            throw new RuntimeException('The directory usded by ActionLogger must be writable. (' . $this->directory . ')');
162
        }
163
        // @codeCoverageIgnoreEnd
164
    
165
        return $result;
166
    }
167
168
    /**
169
     * Return current log's directory.
170
     *
171
     * @return string
172
     */
173
    public function getDirectory(): string
174
    {
175
        return $this->directory;
176
    }
177
178
    /**
179
     * Purge all logs and remove log directory.
180
     *
181
     * @return void
182
     */
183
    public function purgeLogs(): void
184
    {
185
        // Remove them recursively.
186
        
187
        if (file_exists($this->directory)) {
188
            $it = new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS);
189
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
190
191
            foreach ($files as $file) {
192
                if ($file->isDir()) {
193
                    // @codeCoverageIgnoreStart
194
                    rmdir($file->getRealPath());
195
                    // @codeCoverageIgnoreEnd
196
                } else {
197
                    unlink($file->getRealPath());
198
                }
199
            }
200
            unset($it, $files);
201
202
            if (is_dir($this->directory)) {
203
                rmdir($this->directory);
204
            }
205
        }
206
    }
207
208
    /**
209
     * Get current logger info.
210
     *
211
     * @return array
212
     */
213
    public function getCurrentLoggerInfo(): array
214
    {
215
        $data = [];
216
217
        if (file_exists($this->directory)) {
218
            $it = new RecursiveDirectoryIterator($this->directory, RecursiveDirectoryIterator::SKIP_DOTS);
219
            $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
220
221
            foreach ($files as $file) {
222
                if ($file->isFile()) {
223
                    $key = $file->getBasename('.log');
224
                    $size = $file->getSize();
225
226
                    // Data: datetime => file size.
227
                    $data[$key] = $size;
228
                } 
229
            }
230
        }
231
232
        return $data;
233
    }
234
235
    /**
236
     * Get data from log file.
237
     *
238
     * @param string $fromYmd The string in Ymd Date format.
239
     *
240
     * @return array
241
     */
242
    private function getDataFromSingleDate(string $fromYmd = ''): array
243
    {
244
        $results = [];
245
246
        $fromYmd = date('Ymd', strtotime($fromYmd));
247
248
        $this->file = $fromYmd . '.' . $this->extension;
249
        $this->filePath = rtrim($this->directory, '/') . '/' . $this->file;
250
251
        if (file_exists($this->filePath)) {
252
253
            $logFile = fopen($this->filePath, 'r');
254
255
            if (is_resource($logFile)) {
256
                while (!feof($logFile)) {
257
                    $line = fgets($logFile);
258
    
259
                    if (!empty($line)) {
260
                        $data = explode(',', trim($line));
261
                        $results[] = [
262
                            'ip'          => $data[0],
263
                            'session_id'  => $data[1],
264
                            'action_code' => $data[2],
265
                            'timesamp'    => $data[3],
266
                        ];
267
                    }
268
269
                    unset($line, $data);
270
                }
271
                fclose($logFile);
272
            }
273
        }
274
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
     */
286
    private function getDataFromRange(string $fromYmd = '', string $toYmd = ''): array
287
    {
288
        $results = [];
289
290
        // for quering date range.
291
        $fromYmd = date('Ymd', strtotime($fromYmd));
292
        $toYmd = date('Ymd', strtotime($toYmd));
293
294
        $begin = new DateTime($fromYmd);
295
        $end = new DateTime($toYmd);
296
        $end = $end->modify('+1 day'); 
297
298
        $interval = new DateInterval('P1D');
299
        $daterange = new DatePeriod($begin, $interval, $end);
300
301
        foreach ($daterange as $date) {
302
303
            $thisDayLogFile = $this->directory . '/' . $date->format('Ymd') . '.' . $this->extension;
304
305
            if (file_exists($thisDayLogFile)) {
306
307
                $logFile = fopen($thisDayLogFile, 'r');
308
309
                if (is_resource($logFile)) {
310
                    while (!feof($logFile)) {
311
                        $line = fgets($logFile);
312
    
313
                        if (!empty($line)) {
314
                            $data = explode(',', trim($line));
315
                        }
316
    
317
                        if (!empty($data[0])) {
318
                            $results[] = [
319
                                'ip'          => $data[0],
320
                                'session_id'  => $data[1],
321
                                'action_code' => $data[2],
322
                                'timesamp'    => $data[3],
323
                            ];
324
                        }
325
                        unset($line, $data);
326
                    }
327
                    fclose($logFile);
328
                }
329
            }
330
        }
331
332
        return $results;
333
    }
334
}
335