Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
87 / 87 |
|
100.00% |
12 / 12 |
CRAP | |
100.00% |
1 / 1 |
File | |
100.00% |
87 / 87 |
|
100.00% |
12 / 12 |
51 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getFolder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPrefix | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
decode | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
send | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
getStates | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
7 | |||
getStateById | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getStateByModel | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
getStateByTimestamp | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
9 | |||
getStateByDate | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
12 | |||
getSnapshot | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (https://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@noladev.com> |
7 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
8 | * @license https://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Audit\Adapter; |
15 | |
16 | /** |
17 | * Auditor file class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Audit |
21 | * @author Nick Sagona, III <dev@noladev.com> |
22 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
23 | * @license https://www.popphp.org/license New BSD License |
24 | * @version 2.0.2 |
25 | */ |
26 | class 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 | } |