Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
87 / 87
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
File
100.00% covered (success)
100.00%
87 / 87
100.00% covered (success)
100.00%
12 / 12
51
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getFolder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 decode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 send
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 getStates
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 getStateById
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getStateByModel
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getStateByTimestamp
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
9
 getStateByDate
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
12
 getSnapshot
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Audit\Adapter;
15
16/**
17 * Auditor file class
18 *
19 * @category   Pop
20 * @package    Pop\Audit
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    2.0.0
25 */
26class File extends AbstractAdapter
27{
28
29    /**
30     * Folder to store the audit results
31     * @var ?string
32     */
33    protected ?string $folder = null;
34
35
36    /**
37     * File prefix
38     * @var string
39     */
40    protected string $prefix = 'pop-audit-';
41
42    /**
43     * Constructor
44     *
45     * Instantiate the file adapter object
46     *
47     * @param  string $folder
48     * @param  string $prefix
49     * @throws Exception
50     */
51    public function __construct(string $folder, string $prefix = 'pop-audit-')
52    {
53        if (!file_exists($folder)) {
54            throw new Exception('That folder does not exist.');
55        }
56        $this->folder = $folder;
57        $this->prefix = $prefix;
58    }
59
60    /**
61     * Get the folder
62     *
63     * @return string
64     */
65    public function getFolder(): string
66    {
67        return $this->folder;
68    }
69
70    /**
71     * Get the prefix
72     *
73     * @return string
74     */
75    public function getPrefix(): string
76    {
77        return $this->prefix;
78    }
79
80    /**
81     * Get ID from filename
82     *
83     * @param  string $filename
84     * @return string
85     */
86    public function getId(string $filename): string
87    {
88        $filename = substr($filename, 0, strrpos($filename, '.'));
89        $filename = substr($filename, 0, strrpos($filename, '-'));
90        return substr($filename, (strrpos($filename, '-') + 1));
91    }
92
93    /**
94     * Decode the audit file
95     *
96     * @param  string $filename
97     * @throws Exception
98     * @return array|false|null
99     */
100    public function decode(string $filename): array|false|null
101    {
102        if (!file_exists($this->folder . DIRECTORY_SEPARATOR . $filename)) {
103            throw new Exception('That audit file does not exist.');
104        }
105
106        return json_decode(file_get_contents($this->folder . DIRECTORY_SEPARATOR . $filename), true);
107    }
108
109    /**
110     * Send the results of the audit
111     *
112     * @throws Exception
113     * @return string
114     */
115    public function send(): string
116    {
117        if ($this->action === null) {
118            throw new Exception('The model state differences have not been resolved.');
119        }
120        if (($this->model === null) || ($this->modelId === null)) {
121            throw new Exception('The model has not been set.');
122        }
123
124        $id       = md5($this->model . '-' . $this->modelId) . '-' .uniqid() . '-' . time();
125        $filename = $this->prefix . $id . '.log';
126        file_put_contents(
127            $this->folder . DIRECTORY_SEPARATOR . $filename,
128            json_encode($this->prepareData(false), JSON_PRETTY_PRINT)
129        );
130
131        return $filename;
132    }
133
134    /**
135     * Get model states
136     *
137     * @param  string $sort
138     * @param  ?int   $limit
139     * @param  ?int   $offset
140     * @return array
141     */
142    public function getStates(string $sort = 'DESC', ?int $limit = null, ?int $offset = null): array
143    {
144        $files     = scandir($this->folder);
145        $fileNames = [];
146        $results   = [];
147
148        foreach ($files as $file) {
149            if (($file != '.') && ($file != '..')) {
150                $mtime = filemtime($this->folder . DIRECTORY_SEPARATOR . $file);
151                $fileNames[$mtime] = $file;
152            }
153        }
154
155        if ($sort == 'ASC') {
156            ksort($fileNames, SORT_NUMERIC);
157        } else {
158            krsort($fileNames, SORT_NUMERIC);
159        }
160
161        if ($limit !== null) {
162            $fileNames = array_slice($fileNames, (int)$offset, (int)$limit);
163        }
164
165        foreach ($fileNames as $fileName) {
166            $results[$fileName] = $this->decode($fileName);
167        }
168
169        return $results;
170    }
171
172    /**
173     * Get model state by ID
174     *
175     * @param  int|string $id
176     * @return array
177     */
178    public function getStateById(int|string $id): array
179    {
180        $files   = scandir($this->folder);
181        $results = [];
182
183        foreach ($files as $file) {
184            if (str_contains($file, $id)) {
185                $results[$file] = $this->decode($file);
186                break;
187            }
188        }
189
190        return $results;
191    }
192
193    /**
194     * Get model state by model
195     *
196     * @param  string          $model
197     * @param  int|string|null $modelId
198     * @throws Exception
199     * @return array
200     */
201    public function getStateByModel(string $model, int|string|null $modelId = null): array
202    {
203        if ($modelId === null) {
204            throw new Exception('You must pass a model ID.');
205        }
206
207        $files   = scandir($this->folder);
208        $id      = md5($model . '-' . $modelId);
209        $results = [];
210
211        foreach ($files as $file) {
212            if (str_contains($file, $id)) {
213                $results[$file] = $this->decode($file);
214            }
215        }
216
217        return $results;
218    }
219
220    /**
221     * Get model state by timestamp
222     *
223     * @param  string  $from
224     * @param  ?string $backTo
225     * @return array
226     */
227    public function getStateByTimestamp(string $from, ?string $backTo = null): array
228    {
229        $files   = scandir($this->folder);
230        $results = [];
231
232        foreach ($files as $file) {
233            if (($file != '.') && ($file != '..')) {
234                $mtime = filemtime($this->folder . DIRECTORY_SEPARATOR . $file);
235                if ((($backTo !== null) && ($mtime <= $from) && ($mtime >= $backTo)) ||
236                    (($backTo === null) && ($mtime <= $from))) {
237                    $results[$file] = $this->decode($file);
238                }
239            }
240        }
241
242        return $results;
243    }
244
245    /**
246     * Get model state by date
247     *
248     * @param  string  $from
249     * @param  ?string $backTo
250     * @return array
251     */
252    public function getStateByDate(string $from, ?string $backTo = null): array
253    {
254        $results = [];
255
256        if (!str_contains($from, ' ')) {
257            $from .= ' 23:59:59';
258        }
259
260        $from = strtotime($from);
261
262        if ($backTo !== null) {
263            if (!str_contains($backTo, ' ')) {
264                $backTo .= ' 00:00:00';
265            }
266            $backTo = strtotime($backTo);
267        }
268
269        $files = scandir($this->folder);
270        foreach ($files as $file) {
271            if (($file != '.') && ($file != '..')) {
272                $mtime = filemtime($this->folder . DIRECTORY_SEPARATOR . $file);
273                if ((($backTo !== null) && ($mtime <= $from) && ($mtime >= $backTo)) ||
274                    (($backTo === null) && ($mtime <= $from))) {
275                    $results[$file] = $this->decode($file);
276                }
277            }
278        }
279
280        return $results;
281    }
282
283    /**
284     * Get model snapshot by ID
285     *
286     * @param  int|string $id
287     * @param  bool       $post
288     * @return array
289     */
290     public function getSnapshot(int|string $id, bool $post = false): array
291     {
292         $result   = $this->getStateById($id);
293         $result   = reset($result);
294         $snapshot = [];
295
296         if (!($post) && !empty($result['old'])) {
297             $snapshot = $result['old'];
298         } else if (($post) && !empty($result['new'])) {
299             $snapshot = $result['new'];
300         }
301
302         return $snapshot;
303     }
304
305}