Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
75 / 75
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
1 / 1
Http
100.00% covered (success)
100.00%
75 / 75
100.00% covered (success)
100.00%
17 / 17
41
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setSendClient
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFetchClient
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSendClient
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFetchClient
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFetchedResult
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 decodeState
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 fetchResults
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 fetchByFilter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasFetchClient
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 send
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getStates
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getStateById
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 getStateByModel
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getStateByTimestamp
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getStateByDate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getSnapshot
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Audit\Adapter;
16
17use Pop\Http\Client;
18
19/**
20 * Auditor HTTP class
21 *
22 * @category   Pop
23 * @package    Pop\Audit
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    3.0.0
28 */
29class Http extends AbstractAdapter
30{
31
32    /**
33     * Client to send the audit results
34     * @var ?Client
35     */
36    protected ?Client $sendClient = null;
37
38    /**
39     * Client to fetch the audit results
40     * @var ?Client
41     */
42    protected ?Client $fetchClient = null;
43
44    /**
45     * Constructor
46     *
47     * Instantiate the HTTP adapter object
48     *
49     * @param Client  $sendClient
50     * @param ?Client $fetchClient
51     */
52    public function __construct(Client $sendClient, ?Client $fetchClient = null)
53    {
54        $this->setSendClient($sendClient);
55        if ($fetchClient !== null) {
56            $this->setFetchClient($fetchClient);
57        }
58    }
59
60    /**
61     * Set the send stream
62     *
63     * @param  Client $sendClient
64     * @return Http
65     */
66    public function setSendClient(Client $sendClient): Http
67    {
68        $this->sendClient = $sendClient;
69        return $this;
70    }
71
72    /**
73     * Set the fetch stream
74     *
75     * @param  Client $fetchClient
76     * @return Http
77     */
78    public function setFetchClient(Client $fetchClient): Http
79    {
80        $this->fetchClient = $fetchClient;
81        return $this;
82    }
83
84    /**
85     * Get the send stream
86     *
87     * @return Client
88     */
89    public function getSendClient(): Client
90    {
91        return $this->sendClient;
92    }
93
94    /**
95     * Get the fetch stream
96     *
97     * @return Client
98     */
99    public function getFetchClient(): Client
100    {
101        return $this->fetchClient;
102    }
103
104    /**
105     * Get the fetched result
106     *
107     * @return mixed
108     */
109    public function getFetchedResult(): mixed
110    {
111        $resultResponse = null;
112
113        if ($this->fetchClient->hasResponse()) {
114            $response = $this->fetchClient->getResponse();
115            if ($response->hasBody()) {
116                $resultResponse = $response->getParsedResponse();
117            }
118        }
119
120        return $resultResponse;
121    }
122
123    /**
124     * Decode the JSON-encoded old/new state fields of a result row
125     *
126     * @param  array $result
127     * @return array
128     */
129    protected function decodeState(array $result): array
130    {
131        foreach (['old', 'new'] as $field) {
132            if (!empty($result[$field]) && is_string($result[$field])) {
133                $decoded = json_decode($result[$field], true);
134                if (json_last_error() === JSON_ERROR_NONE) {
135                    $result[$field] = $decoded;
136                }
137            }
138        }
139
140        return $result;
141    }
142
143    /**
144     * Send the fetch request and return the fetched result as an array
145     *
146     * @return array
147     */
148    protected function fetchResults(): array
149    {
150        $this->fetchClient->send();
151        $results = $this->getFetchedResult();
152
153        return (is_array($results)) ? $results : [];
154    }
155
156    /**
157     * Send the fetch request with a filter payload and return the fetched result as an array
158     *
159     * @param  array $filter
160     * @return array
161     */
162    protected function fetchByFilter(array $filter): array
163    {
164        $this->fetchClient->setData(['filter' => $filter]);
165        return $this->fetchResults();
166    }
167
168    /**
169     * Determine if the adapter has a fetch stream
170     *
171     * @return bool
172     */
173    public function hasFetchClient(): bool
174    {
175        return ($this->fetchClient !== null);
176    }
177
178    /**
179     * Send the results of the audit
180     *
181     * @throws Exception|Client\Exception|Client\Handler\Exception|\Pop\Http\Exception
182     * @return Client\Response
183     */
184    public function send(): Client\Response
185    {
186        if ($this->action === null) {
187            throw new Exception('The model state differences have not been resolved.');
188        }
189        if (($this->model === null) || ($this->modelId === null)) {
190            throw new Exception('The model has not been set.');
191        }
192
193        $this->sendClient->setData($this->prepareData());
194        return $this->sendClient->send();
195    }
196
197    /**
198     * Get model states
199     *
200     * @param  array $fields
201     * @return array
202     */
203    public function getStates(array $fields = []): array
204    {
205        if (!empty($fields)) {
206            $this->fetchClient->setData($fields);
207        }
208
209        return $this->fetchResults();
210    }
211
212    /**
213     * Get model state by ID
214     *
215     * @param  int|string $id
216     * @param  bool       $asQuery
217     * @return array
218     */
219    public function getStateById(int|string $id, bool $asQuery = false): array
220    {
221        $origUrl = $this->fetchClient->getRequest()->getUri()->getFullUri();
222
223        if ($asQuery) {
224            $this->fetchClient->addData('id', $id);
225        } else {
226            $this->fetchClient->getRequest()->getUri()->setUri($origUrl . '/' . $id);
227        }
228
229        $this->fetchClient->send();
230        $parsedResult = $this->getFetchedResult();
231
232        $result = (is_array($parsedResult)) ? $parsedResult : [$parsedResult];
233        $result = $this->decodeState($result);
234
235        $this->fetchClient->getRequest()->getUri()->setUri($origUrl);
236
237        return $result;
238    }
239
240    /**
241     * Get model state by model
242     *
243     * @param  string          $model
244     * @param  int|string|null $modelId
245     * @return array
246     */
247    public function getStateByModel(string $model, int|string|null $modelId = null): array
248    {
249        $filter = ['model = ' . $model];
250
251        if ($modelId !== null) {
252            $filter[] = 'model_id = ' . $modelId;
253        }
254
255        return $this->fetchByFilter($filter);
256    }
257
258    /**
259     * Get model state by timestamp
260     *
261     * @param  int  $from
262     * @param  ?int $backTo
263     * @return array
264     */
265    public function getStateByTimestamp(int $from, ?int $backTo = null): array
266    {
267        $filter = ['timestamp <= ' . date('Y-m-d H:i:s', $from)];
268
269        if ($backTo !== null) {
270            $filter[] = 'timestamp >= ' . date('Y-m-d H:i:s', $backTo);
271        }
272
273        return $this->fetchByFilter($filter);
274    }
275
276    /**
277     * Get model state by date
278     *
279     * @param  string  $from
280     * @param  ?string $backTo
281     * @return array
282     */
283    public function getStateByDate(string $from, ?string $backTo = null): array
284    {
285        if (!str_contains($from, ' ')) {
286            $from .= ' 23:59:59';
287        }
288
289        $filter = ['timestamp <= ' . $from];
290
291        if ($backTo !== null) {
292            if (!str_contains($backTo, ' ')) {
293                $backTo .= ' 00:00:00';
294            }
295            $filter[] = 'timestamp >= ' . $backTo;
296        }
297
298        return $this->fetchByFilter($filter);
299    }
300
301    /**
302     * Get model snapshot by ID
303     *
304     * @param  int|string $id
305     * @param  bool       $post
306     * @return array
307     */
308    public function getSnapshot(int|string $id, bool $post = false): array
309    {
310        $url = $this->fetchClient->getRequest()->getUri()->getFullUri();
311        $this->fetchClient->getRequest()->getUri()->setUri($url . '/' . $id);
312        $this->fetchClient->send();
313
314        $parsedResult = $this->getFetchedResult();
315
316        $result = (is_array($parsedResult)) ? $parsedResult : [$parsedResult];
317        $result = $this->decodeState($result);
318
319        $snapshot = [];
320
321        if (!($post) && !empty($result['old'])) {
322            $snapshot = $result['old'];
323        } else if (($post) && !empty($result['new'])) {
324            $snapshot = $result['new'];
325        }
326
327        $this->fetchClient->getRequest()->getUri()->setUri($url);
328
329        return $snapshot;
330    }
331
332}