Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
60 / 60
100.00% covered (success)
100.00%
23 / 23
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractAuthClient
100.00% covered (success)
100.00%
60 / 60
100.00% covered (success)
100.00%
23 / 23
43
100.00% covered (success)
100.00%
1 / 1
 setAuthApiUrl
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getAuthApiUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAuthApiUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAccountNumber
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getAccountNumber
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAccountNumber
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTokenData
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasTokenData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loadTokenData
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 loadTokenDataFromFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 saveTokenDataToFile
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 hasTokenDataFile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAuthToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getAuthToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 fetchAuthToken
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 hasTokenType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTokenType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasExpiration
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExpiration
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isExpired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 willExpireIn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 authenticate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 refresh
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
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 <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Shipping\Auth;
16
17use Pop\Shipping\Client\AbstractShippingClient;
18
19/**
20 * Pop shipping auth abstract client class
21 *
22 * @category   Pop
23 * @package    Pop\Shipping
24 * @author     Nick Sagona, III <dev@noladev.com>
25 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    4.0.0
28 */
29abstract class AbstractAuthClient extends AbstractShippingClient implements AuthClientInterface
30{
31
32    /**
33     * Auth API URL
34     * @var ?string
35     */
36    protected ?string $authApiUrl = null;
37
38    /**
39     * Account number
40     * @var ?string
41     */
42    protected ?string $accountNumber = null;
43
44    /**
45     * Auth token
46     * @var ?string
47     */
48    protected ?string $authToken = null;
49
50    /**
51     * Auth token type
52     * @var ?string
53     */
54    protected ?string $tokenType = null;
55
56    /**
57     * Auth token expiration timestamp
58     * @var ?int
59     */
60    protected ?int $expiration = null;
61
62    /**
63     * Auth token data
64     * @var array
65     */
66    protected array $tokenData = [];
67
68    /**
69     * Set auth API URL
70     *
71     * @param  string $authApiUrl
72     * @return AbstractAuthClient
73     */
74    public function setAuthApiUrl(string $authApiUrl): AbstractAuthClient
75    {
76        $this->authApiUrl = $authApiUrl;
77        return $this;
78    }
79
80    /**
81     * Get auth API URL
82     *
83     * @return ?string
84     */
85    public function getAuthApiUrl(): ?string
86    {
87        return $this->authApiUrl;
88    }
89
90    /**
91     * Has auth API URL
92     *
93     * @return bool
94     */
95    public function hasAuthApiUrl(): bool
96    {
97        return !empty($this->authApiUrl);
98    }
99
100    /**
101     * Set account number
102     *
103     * @param  string $accountNumber
104     * @return AbstractAuthClient
105     */
106    public function setAccountNumber(string $accountNumber): AbstractAuthClient
107    {
108        $this->accountNumber = $accountNumber;
109        return $this;
110    }
111
112    /**
113     * Get account number
114     *
115     * @return ?string
116     */
117    public function getAccountNumber(): ?string
118    {
119        return $this->accountNumber;
120    }
121
122    /**
123     * Has account number
124     *
125     * @return bool
126     */
127    public function hasAccountNumber(): bool
128    {
129        return !empty($this->accountNumber);
130    }
131
132    /**
133     * Get token data
134     *
135     * @param  ?string $key
136     * @return mixed
137     */
138    public function getTokenData(?string $key = null): mixed
139    {
140        if ($key !== null) {
141            return $this->tokenData[$key] ?? null;
142        } else {
143            return $this->tokenData;
144        }
145    }
146
147    /**
148     * Has token data
149     *
150     * @return bool
151     */
152    public function hasTokenData(): bool
153    {
154        return (!empty($this->tokenData));
155    }
156
157    /**
158     * Load token data
159     *
160     * @param  array $tokenData
161     * @return AbstractAuthClient
162     */
163    public function loadTokenData(array $tokenData): AbstractAuthClient
164    {
165        $this->tokenData = $tokenData;
166
167        if (!empty($this->tokenData['access_token'])) {
168            $this->authToken = $this->tokenData['access_token'];
169        }
170        if (!empty($this->tokenData['token_type'])) {
171            $this->tokenType = $this->tokenData['token_type'];
172        }
173        if (!empty($this->tokenData['expires_in'])) {
174            $this->expiration = time() + $this->tokenData['expires_in'];
175        } else if (!empty($this->tokenData['expiration'])) {
176            $this->expiration = $this->tokenData['expiration'];
177        }
178
179        return $this;
180    }
181
182    /**
183     * Load token data from file
184     *
185     * @param  string $tokenFile
186     * @return AbstractAuthClient
187     */
188    public function loadTokenDataFromFile(string $tokenFile): AbstractAuthClient
189    {
190        if (file_exists($tokenFile)) {
191            $this->loadTokenData(json_decode(file_get_contents($tokenFile), true));
192        }
193        return $this;
194    }
195
196    /**
197     * Save token data to file
198     *
199     * @param  string $tokenFile
200     * @param  ?array $tokenData
201     * @return AbstractAuthClient
202     */
203    public function saveTokenDataToFile(string $tokenFile, ?array $tokenData = null): AbstractAuthClient
204    {
205        if ($tokenData !== null) {
206            $this->loadTokenData($tokenData);
207        }
208        file_put_contents($tokenFile, json_encode([
209            'access_token' => $this->tokenData['access_token'] ?? null,
210            'token_type'   => $this->tokenData['token_type'] ?? null,
211            'expiration'   => $this->expiration
212        ], JSON_PRETTY_PRINT));
213
214        return $this;
215    }
216
217    /**
218     * Has token data file
219     *
220     * @param  string $tokenFile
221     * @return bool
222     */
223    public function hasTokenDataFile(string $tokenFile): bool
224    {
225        return (file_exists($tokenFile));
226    }
227
228    /**
229     * Has valid auth token
230     *
231     * @return bool
232     */
233    public function hasAuthToken(): bool
234    {
235        return (($this->authToken !== null) && (!$this->isExpired()));
236    }
237
238    /**
239     * Get auth token
240     *
241     * @return ?string
242     */
243    public function getAuthToken(): ?string
244    {
245        return ($this->hasAuthToken()) ? $this->authToken : null;
246    }
247
248    /**
249     * Fetch auth token, either the current valid one, or get a new/refreshed auth token
250     *
251     * @param  ?string $tokenFile
252     * @param  int     $buffer     Buffer in seconds to check the expiration
253     * @return ?string
254     */
255    public function fetchAuthToken(?string $tokenFile = null, int $buffer = 10): ?string
256    {
257        if ((!$this->hasAuthToken()) && ($tokenFile !== null)) {
258            $this->loadTokenDataFromFile($tokenFile);
259        }
260        if ((!$this->hasAuthToken()) || ($this->willExpireIn() <= $buffer)) {
261            $this->authenticate($tokenFile);
262        }
263
264        return $this->getAuthToken();
265    }
266
267    /**
268     * Has auth token type
269     *
270     * @return bool
271     */
272    public function hasTokenType(): bool
273    {
274        return ($this->tokenType !== null);
275    }
276
277    /**
278     * Get auth token type
279     *
280     * @return ?string
281     */
282    public function getTokenType(): ?string
283    {
284        return $this->tokenType;
285    }
286
287    /**
288     * Has auth token expiration
289     *
290     * @return bool
291     */
292    public function hasExpiration(): bool
293    {
294        return ($this->expiration !== null);
295    }
296
297    /**
298     * Get auth token expiration
299     *
300     * @return ?int
301     */
302    public function getExpiration(): ?int
303    {
304        return $this->expiration;
305    }
306
307    /**
308     * Determine if the auth token has expired
309     *
310     * @return bool
311     */
312    public function isExpired(): bool
313    {
314        return (($this->hasExpiration()) && (time() > $this->expiration));
315    }
316
317    /**
318     * Determine when the token will expire in seconds
319     *
320     * @return int
321     */
322    public function willExpireIn(): int
323    {
324        return (($this->hasExpiration()) && (!$this->isExpired())) ? ($this->expiration - time()) : 0;
325    }
326
327    /**
328     * Authenticate and get auth token
329     *
330     * @param  ?string $tokenFile
331     * @throws Exception
332     * @return AbstractAuthClient
333     */
334    public function authenticate(?string $tokenFile = null): AbstractAuthClient
335    {
336        if (!$this->hasClient()) {
337            throw new Exception('Error: The auth client does not have an HTTP client.');
338        }
339
340        $response = $this->client->send($this->authApiUrl);
341
342        if ($response->isSuccess()) {
343            $this->loadTokenData($response->getParsedResponse());
344
345            if (($this->hasTokenData()) && ($tokenFile !== null)) {
346                $this->saveTokenDataToFile($tokenFile);
347            }
348        }
349        return $this;
350    }
351
352    /**
353     * Refresh auth token
354     *
355     * @param  ?string $tokenFile
356     * @return AbstractAuthClient
357     */
358    public function refresh(?string $tokenFile = null): AbstractAuthClient
359    {
360        $this->authToken  = null;
361        $this->expiration = null;
362        $this->tokenData  = [];
363
364        $this->authenticate($tokenFile);
365
366        return $this;
367    }
368
369}