Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
99.22% |
128 / 129 |
|
96.55% |
28 / 29 |
CRAP | |
0.00% |
0 / 1 |
Data | |
99.22% |
128 / 129 |
|
96.55% |
28 / 29 |
85 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
setData | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
addData | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
getData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
hasData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
removeData | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
removeAllData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
setRequest | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRequest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasRequest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDataContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDataContentLength | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
hasDataContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isPrepared | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
prepare | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
11 | |||
reset | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
prepareUrlEncoded | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
prepareJson | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
13 | |||
prepareXml | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
9 | |||
prepareMultipart | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
getRawData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasRawData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getRawDataLength | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
toArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMimeTypes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasMimeType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMimeType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMimeTypeFromFilename | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getDefaultMimeType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
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 | */ |
14 | namespace Pop\Http\Client; |
15 | |
16 | use Pop\Http\HttpFilterableTrait; |
17 | use Pop\Mime\Message; |
18 | use Pop\Mime\Part; |
19 | |
20 | /** |
21 | * Client request data class |
22 | * |
23 | * @category Pop |
24 | * @package Pop\Http |
25 | * @author Nick Sagona, III <dev@nolainteractive.com> |
26 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
27 | * @license http://www.popphp.org/license New BSD License |
28 | * @version 5.2.0 |
29 | */ |
30 | class Data |
31 | { |
32 | |
33 | use HttpFilterableTrait; |
34 | |
35 | /** |
36 | * Raw data constant |
37 | */ |
38 | const POP_CLIENT_REQUEST_RAW_DATA = 'POP_CLIENT_REQUEST_RAW_DATA'; |
39 | |
40 | /** |
41 | * Data fields (form fields and files) |
42 | * $data = [ |
43 | * 'username' => 'admin' |
44 | * 'file1' => [ |
45 | * 'filename' => __DIR__ . '/path/to/file.txt', |
46 | * 'contentType' => 'text/plain' |
47 | * ], |
48 | * 'file2' => [ |
49 | * 'filename' => 'test.pdf', |
50 | * 'contentType' => 'application/pdf', |
51 | * 'contents' => file_get_contents(__DIR__ . '/path/to/test.pdf' |
52 | * ] |
53 | * ] |
54 | * @var array |
55 | */ |
56 | protected array $data = []; |
57 | |
58 | /** |
59 | * Data parent request |
60 | * @var ?Request |
61 | */ |
62 | protected ?Request $request = null; |
63 | |
64 | /** |
65 | * Data content |
66 | * @var ?string |
67 | */ |
68 | protected ?string $dataContent = null; |
69 | |
70 | /** |
71 | * Data content prepared flag |
72 | * @var bool |
73 | */ |
74 | protected bool $prepared = false; |
75 | |
76 | /** |
77 | * Common mime types |
78 | * @var array |
79 | */ |
80 | protected static array $mimeTypes = [ |
81 | 'bmp' => 'image/x-ms-bmp', |
82 | 'bz2' => 'application/bzip2', |
83 | 'csv' => 'text/csv', |
84 | 'doc' => 'application/msword', |
85 | 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
86 | 'gif' => 'image/gif', |
87 | 'gz' => 'application/gzip', |
88 | 'jpe' => 'image/jpeg', |
89 | 'jpg' => 'image/jpeg', |
90 | 'jpeg' => 'image/jpeg', |
91 | 'json' => 'application/json', |
92 | 'log' => 'text/plain', |
93 | 'pdf' => 'application/pdf', |
94 | 'png' => 'image/png', |
95 | 'ppt' => 'application/msword', |
96 | 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', |
97 | 'psd' => 'image/x-photoshop', |
98 | 'svg' => 'image/svg+xml', |
99 | 'tar' => 'application/x-tar', |
100 | 'tbz' => 'application/bzip2', |
101 | 'tbz2' => 'application/bzip2', |
102 | 'tgz' => 'application/gzip', |
103 | 'tif' => 'image/tiff', |
104 | 'tiff' => 'image/tiff', |
105 | 'tsv' => 'text/tsv', |
106 | 'txt' => 'text/plain', |
107 | 'xls' => 'application/msword', |
108 | 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', |
109 | 'xml' => 'application/xml', |
110 | 'zip' => 'application/zip' |
111 | ]; |
112 | |
113 | /** |
114 | * Default mime type |
115 | * @var string |
116 | */ |
117 | protected static string $defaultMimeType = 'application/octet-stream'; |
118 | |
119 | /** |
120 | * Constructor |
121 | * |
122 | * Instantiate the request data object |
123 | * |
124 | * @param array|string $data |
125 | * @param mixed $filters |
126 | * @param ?string $type |
127 | */ |
128 | public function __construct(array|string $data = [], mixed $filters = null, ?Request $request = null) |
129 | { |
130 | if ($filters !== null) { |
131 | if (is_array($filters)) { |
132 | $this->addFilters($filters); |
133 | } else { |
134 | $this->addFilter($filters); |
135 | } |
136 | } |
137 | |
138 | if ($request !== null) { |
139 | $this->setRequest($request); |
140 | } |
141 | |
142 | if (!empty($data)) { |
143 | $this->setData($data); |
144 | } |
145 | } |
146 | |
147 | /** |
148 | * Set data |
149 | * |
150 | * @param array|string $data |
151 | * @return Data |
152 | */ |
153 | public function setData(array|string $data): Data |
154 | { |
155 | if (is_string($data)) { |
156 | $this->data = [self::POP_CLIENT_REQUEST_RAW_DATA => $data]; |
157 | } else if (is_array($data) && (count($data) == 1) && isset($data[0])) { |
158 | $this->data = [self::POP_CLIENT_REQUEST_RAW_DATA => $data[0]]; |
159 | } else { |
160 | $this->data = $data; |
161 | $this->prepare(); |
162 | } |
163 | |
164 | return $this; |
165 | } |
166 | |
167 | /** |
168 | * Add data |
169 | * |
170 | * @param array|string $data |
171 | * @param mixed $value |
172 | * @return Data |
173 | */ |
174 | public function addData(array|string $data, mixed $value = null): Data |
175 | { |
176 | if (is_string($data) && ($value !== null)) { |
177 | $this->data[$data] = $value; |
178 | } else if (is_array($data)) { |
179 | $this->data = array_merge($this->data, $data); |
180 | } |
181 | |
182 | $this->prepare(); |
183 | |
184 | return $this; |
185 | } |
186 | |
187 | /** |
188 | * Get data |
189 | * |
190 | * @param ?string $key |
191 | * @return mixed |
192 | */ |
193 | public function getData(?string $key = null): mixed |
194 | { |
195 | if ($key !== null) { |
196 | return $this->data[$key] ?? null; |
197 | } else { |
198 | return $this->data; |
199 | } |
200 | } |
201 | |
202 | /** |
203 | * Has data |
204 | * |
205 | * @param ?string $key |
206 | * @return bool |
207 | */ |
208 | public function hasData(?string $key = null): bool |
209 | { |
210 | if ($key !== null) { |
211 | return (isset($this->data[$key])); |
212 | } else { |
213 | return !empty($this->data); |
214 | } |
215 | } |
216 | |
217 | /** |
218 | * Remove data |
219 | * |
220 | * @param string $key |
221 | * @return Data |
222 | */ |
223 | public function removeData(string $key): Data |
224 | { |
225 | if (isset($this->data[$key])) { |
226 | unset($this->data[$key]); |
227 | } |
228 | |
229 | $this->prepare(); |
230 | |
231 | return $this; |
232 | } |
233 | |
234 | /** |
235 | * Remove all data |
236 | * |
237 | * @return Data |
238 | */ |
239 | public function removeAllData(): Data |
240 | { |
241 | $this->data = []; |
242 | $this->dataContent = null; |
243 | |
244 | return $this; |
245 | } |
246 | |
247 | /** |
248 | * Set data parent request |
249 | * |
250 | * @param Request $request |
251 | * @return Data |
252 | */ |
253 | public function setRequest(Request $request): Data |
254 | { |
255 | $this->request = $request; |
256 | return $this; |
257 | } |
258 | |
259 | /** |
260 | * Get data parent request |
261 | * |
262 | * @return ?Request |
263 | */ |
264 | public function getRequest(): ?Request |
265 | { |
266 | return $this->request; |
267 | } |
268 | |
269 | /** |
270 | * Has data parent request |
271 | * |
272 | * @return bool |
273 | */ |
274 | public function hasRequest(): bool |
275 | { |
276 | return !empty($this->request); |
277 | } |
278 | |
279 | /** |
280 | * Get prepared data content |
281 | * |
282 | * @return ?string |
283 | */ |
284 | public function getDataContent(): ?string |
285 | { |
286 | return $this->dataContent; |
287 | } |
288 | |
289 | /** |
290 | * Get data content length |
291 | * |
292 | * @param bool $mb |
293 | * @return int |
294 | */ |
295 | public function getDataContentLength(bool $mb = false): int |
296 | { |
297 | return ($mb) ? mb_strlen($this->dataContent) : strlen($this->dataContent); |
298 | } |
299 | |
300 | /** |
301 | * Check if the data content has been prepared |
302 | * |
303 | * @return bool |
304 | */ |
305 | public function hasDataContent(): bool |
306 | { |
307 | return !empty($this->dataContent); |
308 | } |
309 | |
310 | /** |
311 | * Check if the data content has been prepared (alias to hasDataContent) |
312 | * |
313 | * @return bool |
314 | */ |
315 | public function isPrepared(): bool |
316 | { |
317 | return $this->prepared; |
318 | } |
319 | |
320 | /** |
321 | * Prepare data |
322 | * |
323 | * @param bool $mb |
324 | * @return Data |
325 | */ |
326 | public function prepare(bool $mb = false): Data |
327 | { |
328 | switch ($this->request?->getRequestType()) { |
329 | case Request::URLENCODED: |
330 | $this->prepareUrlEncoded(); |
331 | break; |
332 | case Request::JSON: |
333 | $this->prepareJson(); |
334 | break; |
335 | case Request::XML: |
336 | $this->prepareXml(); |
337 | break; |
338 | case Request::MULTIPART: |
339 | $this->prepareMultipart(); |
340 | break; |
341 | default: |
342 | if ($this->hasRawData()) { |
343 | $this->dataContent = $this->getRawData(); |
344 | } else if ($this->hasData()) { |
345 | $this->prepareUrlEncoded(); |
346 | } |
347 | } |
348 | |
349 | if (!empty($this->dataContent) && ($this->hasRequest())) { |
350 | if ($this->request->hasHeader('Content-Length')) { |
351 | $this->request->removeHeader('Content-Length'); |
352 | } |
353 | $this->request->addHeader('Content-Length', strlen($this->dataContent)); |
354 | } |
355 | |
356 | $this->prepared = true; |
357 | |
358 | return $this; |
359 | } |
360 | |
361 | /** |
362 | * Reset data |
363 | * |
364 | * @return Data |
365 | */ |
366 | public function reset(): Data |
367 | { |
368 | $this->dataContent = null; |
369 | $this->prepared = false; |
370 | return $this; |
371 | } |
372 | |
373 | /** |
374 | * Method to prepare URL-encoded data content |
375 | * |
376 | * @return Data |
377 | */ |
378 | public function prepareUrlEncoded(): Data |
379 | { |
380 | if (!array_key_exists(self::POP_CLIENT_REQUEST_RAW_DATA, $this->data) && !empty($this->data)) { |
381 | $data = $this->data; |
382 | if ($this->hasFilters()) { |
383 | $data = $this->filter($data); |
384 | } |
385 | $this->dataContent = http_build_query($data); |
386 | } else { |
387 | $this->dataContent = null; |
388 | } |
389 | |
390 | if ($this->hasRequest()) { |
391 | if ($this->request->hasHeader('Content-Type')) { |
392 | $this->request->removeHeader('Content-Type'); |
393 | } |
394 | $this->request->addHeader('Content-Type', Request::URLENCODED); |
395 | } |
396 | |
397 | return $this; |
398 | } |
399 | |
400 | /** |
401 | * Method to prepare JSON data content |
402 | * |
403 | * @return Data |
404 | */ |
405 | public function prepareJson(): Data |
406 | { |
407 | if ($this->hasRawData()) { |
408 | $jsonContent = $this->getRawData(); |
409 | } else { |
410 | $jsonData = $this->data; |
411 | $jsonContent = []; |
412 | |
413 | // Check for JSON files |
414 | foreach ($jsonData as $jsonDatum) { |
415 | if (isset($jsonDatum['filename']) && isset($jsonDatum['contentType']) && |
416 | str_contains(strtolower($jsonDatum['contentType']), 'json') && file_exists($jsonDatum['filename'])) { |
417 | $jsonContent = array_merge($jsonContent, json_decode(file_get_contents($jsonDatum['filename']), true)); |
418 | } |
419 | } |
420 | |
421 | // Else, use JSON data |
422 | if (empty($jsonContent)) { |
423 | $jsonContent = $jsonData; |
424 | } |
425 | } |
426 | |
427 | if ($this->hasRequest()) { |
428 | if ($this->request->hasHeader('Content-Type')) { |
429 | $this->request->removeHeader('Content-Type'); |
430 | } |
431 | $this->request->addHeader('Content-Type', Request::JSON); |
432 | } |
433 | |
434 | // Only encode if the data isn't already encoded |
435 | if (!((is_string($jsonContent) && (json_decode($jsonContent) !== false)) && (json_last_error() == JSON_ERROR_NONE))) { |
436 | $this->dataContent = json_encode($jsonContent, JSON_PRETTY_PRINT); |
437 | } else { |
438 | $this->dataContent = $jsonContent; |
439 | } |
440 | |
441 | return $this; |
442 | } |
443 | |
444 | /** |
445 | * Method to prepare XML data content |
446 | * |
447 | * @return Data |
448 | */ |
449 | public function prepareXml(): Data |
450 | { |
451 | if ($this->hasRawData()) { |
452 | $xmlContent = $this->getRawData(); |
453 | } else { |
454 | $xmlData = $this->data; |
455 | $xmlContent = ''; |
456 | |
457 | // Check for XML files |
458 | foreach ($xmlData as $xmlDatum) { |
459 | $xmlContent .= (isset($xmlDatum['filename']) && isset($xmlDatum['contentType']) && |
460 | str_contains(strtolower($xmlDatum['contentType']), 'xml') && file_exists($xmlDatum['filename'])) ? |
461 | file_get_contents($xmlDatum['filename']) : $xmlDatum; |
462 | } |
463 | } |
464 | |
465 | if ($this->hasRequest()) { |
466 | if ($this->request->hasHeader('Content-Type')) { |
467 | $this->request->removeHeader('Content-Type'); |
468 | } |
469 | $this->request->addHeader('Content-Type', Request::XML); |
470 | } |
471 | |
472 | $this->dataContent = $xmlContent; |
473 | |
474 | return $this; |
475 | } |
476 | |
477 | /** |
478 | * Method to prepare multi-part data content |
479 | * |
480 | * @return Data |
481 | */ |
482 | public function prepareMultipart(): Data |
483 | { |
484 | $formMessage = Message::createForm($this->data); |
485 | $this->dataContent = $formMessage->renderRaw(); |
486 | |
487 | if ($this->hasRequest()) { |
488 | if ($this->request->hasHeader('Content-Type')) { |
489 | $this->request->removeHeader('Content-Type'); |
490 | } |
491 | $this->request->addHeader($formMessage->getHeader('Content-Type')); |
492 | } |
493 | |
494 | return $this; |
495 | } |
496 | |
497 | /** |
498 | * Get raw data |
499 | * |
500 | * @return ?string |
501 | */ |
502 | public function getRawData(): ?string |
503 | { |
504 | return $this->data[self::POP_CLIENT_REQUEST_RAW_DATA] ?? null; |
505 | } |
506 | |
507 | /** |
508 | * Has raw data |
509 | * |
510 | * @return bool |
511 | */ |
512 | public function hasRawData(): bool |
513 | { |
514 | return (count($this->data) == 1) && isset($this->data[self::POP_CLIENT_REQUEST_RAW_DATA]); |
515 | } |
516 | |
517 | /** |
518 | * Get raw data length |
519 | * |
520 | * @param bool $mb |
521 | * @return int |
522 | */ |
523 | public function getRawDataLength(bool $mb = false): int |
524 | { |
525 | return ($mb) ? mb_strlen((string)$this->getRawData()) : strlen((string)$this->getRawData()); |
526 | } |
527 | |
528 | /** |
529 | * Get data array |
530 | * |
531 | * @return array |
532 | */ |
533 | public function toArray(): array |
534 | { |
535 | return $this->data; |
536 | } |
537 | |
538 | /** |
539 | * Get common mime types |
540 | * |
541 | * @return array |
542 | */ |
543 | public static function getMimeTypes(): array |
544 | { |
545 | return static::$mimeTypes; |
546 | } |
547 | |
548 | /** |
549 | * Has mime type |
550 | * |
551 | * @param string $ext |
552 | * @return bool |
553 | */ |
554 | public static function hasMimeType(string $ext): bool |
555 | { |
556 | return isset(static::$mimeTypes[$ext]); |
557 | } |
558 | |
559 | /** |
560 | * Get mime type |
561 | * |
562 | * @param string $ext |
563 | * @return ?string |
564 | */ |
565 | public static function getMimeType(string $ext): ?string |
566 | { |
567 | return static::$mimeTypes[$ext] ?? null; |
568 | } |
569 | |
570 | /** |
571 | * Get mime type |
572 | * |
573 | * @param string $filename |
574 | * @return string |
575 | */ |
576 | public static function getMimeTypeFromFilename(string $filename): string |
577 | { |
578 | $info = pathinfo($filename); |
579 | |
580 | return (isset($info['extension']) && isset(self::$mimeTypes[$info['extension']])) ? |
581 | self::$mimeTypes[$info['extension']] : self::$defaultMimeType; |
582 | } |
583 | |
584 | /** |
585 | * Get default mime type |
586 | * |
587 | * @return string |
588 | */ |
589 | public static function getDefaultMimeType(): string |
590 | { |
591 | return static::$defaultMimeType; |
592 | } |
593 | |
594 | } |