1<?php
2
3declare(strict_types=1);
4
5namespace Laminas\Diactoros;
6
7use Psr\Http\Message\UploadedFileInterface;
8
9use function is_array;
10
11/**
12 * Normalize uploaded files
13 *
14 * Transforms each value into an UploadedFile instance, and ensures that nested
15 * arrays are normalized.
16 *
17 * @return UploadedFileInterface[]
18 * @throws Exception\InvalidArgumentException for unrecognized values
19 */
20function normalizeUploadedFiles(array $files) : array
21{
22    /**
23     * Traverse a nested tree of uploaded file specifications.
24     *
25     * @param string[]|array[] $tmpNameTree
26     * @param int[]|array[] $sizeTree
27     * @param int[]|array[] $errorTree
28     * @param string[]|array[]|null $nameTree
29     * @param string[]|array[]|null $typeTree
30     * @return UploadedFile[]|array[]
31     */
32    $recursiveNormalize = function (
33        array $tmpNameTree,
34        array $sizeTree,
35        array $errorTree,
36        array $nameTree = null,
37        array $typeTree = null
38    ) use (&$recursiveNormalize) : array {
39        $normalized = [];
40        foreach ($tmpNameTree as $key => $value) {
41            if (is_array($value)) {
42                // Traverse
43                $normalized[$key] = $recursiveNormalize(
44                    $tmpNameTree[$key],
45                    $sizeTree[$key],
46                    $errorTree[$key],
47                    $nameTree[$key] ?? null,
48                    $typeTree[$key] ?? null
49                );
50                continue;
51            }
52            $normalized[$key] = createUploadedFile([
53                'tmp_name' => $tmpNameTree[$key],
54                'size' => $sizeTree[$key],
55                'error' => $errorTree[$key],
56                'name' => $nameTree[$key] ?? null,
57                'type' => $typeTree[$key] ?? null,
58            ]);
59        }
60        return $normalized;
61    };
62
63    /**
64     * Normalize an array of file specifications.
65     *
66     * Loops through all nested files (as determined by receiving an array to the
67     * `tmp_name` key of a `$_FILES` specification) and returns a normalized array
68     * of UploadedFile instances.
69     *
70     * This function normalizes a `$_FILES` array representing a nested set of
71     * uploaded files as produced by the php-fpm SAPI, CGI SAPI, or mod_php
72     * SAPI.
73     *
74     * @param array $files
75     * @return UploadedFile[]
76     */
77    $normalizeUploadedFileSpecification = function (array $files = []) use (&$recursiveNormalize) : array {
78        if (! isset($files['tmp_name']) || ! is_array($files['tmp_name'])
79            || ! isset($files['size']) || ! is_array($files['size'])
80            || ! isset($files['error']) || ! is_array($files['error'])
81        ) {
82            throw new Exception\InvalidArgumentException(sprintf(
83                '$files provided to %s MUST contain each of the keys "tmp_name",'
84                . ' "size", and "error", with each represented as an array;'
85                . ' one or more were missing or non-array values',
86                __FUNCTION__
87            ));
88        }
89
90        return $recursiveNormalize(
91            $files['tmp_name'],
92            $files['size'],
93            $files['error'],
94            $files['name'] ?? null,
95            $files['type'] ?? null
96        );
97    };
98
99    $normalized = [];
100    foreach ($files as $key => $value) {
101        if ($value instanceof UploadedFileInterface) {
102            $normalized[$key] = $value;
103            continue;
104        }
105
106        if (is_array($value) && isset($value['tmp_name']) && is_array($value['tmp_name'])) {
107            $normalized[$key] = $normalizeUploadedFileSpecification($value);
108            continue;
109        }
110
111        if (is_array($value) && isset($value['tmp_name'])) {
112            $normalized[$key] = createUploadedFile($value);
113            continue;
114        }
115
116        if (is_array($value)) {
117            $normalized[$key] = normalizeUploadedFiles($value);
118            continue;
119        }
120
121        throw new Exception\InvalidArgumentException('Invalid value in files specification');
122    }
123    return $normalized;
124}
125