Completed
Branch FET/files-data-handler (2bc4ac)
by
unknown
26:50 queued 18:10
created

FilesDataHandler::parse2dFilesArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace EventEspresso\core\services\request\files;
4
5
use EventEspresso\core\services\collections\CollectionDetails;
6
use EventEspresso\core\services\collections\CollectionDetailsException;
7
use EventEspresso\core\services\collections\CollectionInterface;
8
use EventEspresso\core\services\collections\CollectionLoader;
9
use EventEspresso\core\services\collections\CollectionLoaderException;
10
use EventEspresso\core\services\request\Request;
11
use UnexpectedValueException;
12
13
/**
14
 * Class FilesDataHandler
15
 *
16
 * Helper for dealing with PHP's $_FILES. Instead of working with a sometimes puzzling array, with file info
17
 * dispersed throughout, creates a single array of FileSubmissionInterface objects. This array can be multi-dimensional,
18
 * but its shape follows that of $_POST and $_GET.
19
 * Eg, access all file info for a file input named "file1" using
20
 *
21
 * ```
22
 * $data_handler = LoaderFactory::getLoader()->load(' EventEspresso\core\services\request\files\FilesDataHandler');
23
 * $files = $data_handler->getFileObjects();
24
 * $file = $files['file1'];
25
 * ```
26
 *
27
 * and for a file input named "my[great][file][input]", use the same code but change the last line to:
28
 *
29
 * ```
30
 * $file = $files['my']['great']['file']['input'];
31
 * ```
32
 *
33
 * In both cases, $file will be a FileSubmissionInterface object, containing the file's name, temporary filepath, size,
34
 * error code, and helpers to get its mime type and extension.
35
 *
36
 *
37
 *
38
 * @package     Event Espresso
39
 * @author         Mike Nelson
40
 * @since         $VID:$
41
 *
42
 */
43
class FilesDataHandler
44
{
45
    /**
46
     * @var Request
47
     */
48
    protected $request;
49
50
    /**
51
     * @var array
52
     */
53
    protected $file_objects;
54
55
    /**
56
     * @var bool
57
     */
58
    protected $initialized = false;
59
60
    /**
61
     * FilesDataHandler constructor.
62
     * @param Request $request
63
     */
64
    public function __construct(Request $request)
65
    {
66
        $this->request = $request;
67
    }
68
69
    /**
70
     * @since $VID:$
71
     * @return array with a similar structure to $_POST and $_GET (ie, it can be multi-dimensional) but the "leaf"
72
     * nodes are all of type FileSubmissionInterface
73
     * @throws CollectionDetailsException
74
     * @throws CollectionLoaderException
75
     * @throws UnexpectedValueException
76
     */
77
    public function getFileObjects()
78
    {
79
        if (!$this->initialized) {
80
            $this->initialize();
81
        }
82
        return $this->file_objects;
83
    }
84
85
    /**
86
     * Sets up the file objects from the request's $_FILES data.
87
     * @since $VID:$
88
     * @throws UnexpectedValueException
89
     */
90
    protected function initialize()
91
    {
92
        $files_raw_data = $this->request->filesParams();
93
        if (empty($files_raw_data)) {
94
            return;
95
        }
96
        if ($this->isStrangeFilesArray($files_raw_data)) {
97
            $data = $this->fixFilesDataArray($files_raw_data);
98
        } else {
99
            $data = $files_raw_data;
100
        }
101
        $this->file_objects = $this->createFileObjects($data);
102
        $this->initialized = true;
103
    }
104
105
    /**
106
     * Detects if $_FILES is a weird multi-dimensional array that needs fixing or not.
107
     * @since $VID:$
108
     * @param $files_data
109
     * @return bool
110
     * @throws UnexpectedValueException
111
     */
112
    protected function isStrangeFilesArray($files_data)
113
    {
114 View Code Duplication
        if (!is_array($files_data)) {
115
            throw new UnexpectedValueException(
116
                sprintf(
117
                    esc_html__(
118
                        'Unexpected PHP $_FILES data format. "%1$s" was expected to be an array.',
119
                        'event_espresso'
120
                    ),
121
                    (string) $files_data
122
                )
123
            );
124
        }
125
        $first_value = reset($files_data);
126 View Code Duplication
        if (!is_array($first_value)) {
127
            throw new UnexpectedValueException(
128
                sprintf(
129
                    esc_html__(
130
                        'Unexpected PHP $_FILES data format. "%1$s" was expected to be an array.',
131
                        'event_espresso'
132
                    ),
133
                    (string) $first_value
134
                )
135
            );
136
        }
137
        $first_sub_array_item = reset($first_value);
138
        if (is_array($first_sub_array_item)) {
139
            // not just a 2d array
140
            return true;
141
        }
142
        // yep, just 2d array
143
        return false;
144
    }
145
146
    /**
147
     * Takes into account that $_FILES does a weird thing when you have hierarchical form names (eg `<input type="file"
148
     * name="my[hierarchical][form]">`): it leaves the top-level form part alone, but replaces the SECOND part with
149
     * "name", "size", "tmp_name", etc. So that file's data is located at "my[name][hierarchical][form]",
150
     * "my[size][hierarchical][form]", "my[tmp_name][hierarchical][form]", etc. It's really weird.
151
     * @since $VID:$
152
     * @param $files_data
153
     * @return array
154
     */
155
    protected function fixFilesDataArray($files_data)
156
    {
157
        $sane_files_array = [];
158
        foreach ($files_data as $top_key => $top_key_avlue) {
159
            foreach ($top_key_avlue as $lower_key => $lower_key_value) {
160
                foreach ($lower_key_value as $lowest_key => $lowest_key_value) {
161
                    $next_data = [
162
                        $top_key => [
163
                            $lowest_key => $this->organizeFilesData($lowest_key_value, $lower_key, $lowest_key)
164
                        ]
165
                    ];
166
                    $sane_files_array = array_merge_recursive(
167
                        $sane_files_array,
168
                        $next_data
169
                    );
170
                }
171
            }
172
        }
173
        return $sane_files_array;
174
    }
175
176
    /**
177
     * Recursively explores the array until it finds a leaf node, and tacks `$type` as a final index in front of it.
178
     * @since $VID:$
179
     * @param $data either 'name', 'tmp_name', 'size', or 'error'
180
     * @param $type
181
     * @return array
182
     */
183
    protected function organizeFilesData($data, $type)
184
    {
185
        $organized_data = [];
186
        foreach ($data as $key => $val) {
187
            if (is_array($val)) {
188
                $organized_data[ $key ] = $this->organizeFilesData($val, $type);
189
            } else {
190
                $organized_data[ $key ][ $type ] = $val;
191
            }
192
        }
193
        return $organized_data;
194
    }
195
196
    /**
197
     * Takes the organized $_FILES array (where all file info is located at the same spot as you'd expect an input
198
     * to be in $_GET or $_POST, with all the file's data located side-by-side in an array) and creates a
199
     * multi-dimensional array of FileSubmissionInterface objects.
200
     * @since $VID:$
201
     * @param $organized_files
202
     * @return array
203
     * @throws UnexpectedValueException
204
     */
205
    protected function createFileObjects($organized_files)
206
    {
207 View Code Duplication
        if (!is_array($organized_files)) {
208
            throw new UnexpectedValueException(
209
                sprintf(
210
                    esc_html__(
211
                        'Unexpected PHP $organized_files data format. "%1$s" was expected to be an array.',
212
                        'event_espresso'
213
                    ),
214
                    (string) $organized_files
215
                )
216
            );
217
        }
218
        $objs = [];
219
        foreach ($organized_files as $key => $value) {
220
            if (isset($value['name'], $value['tmp_name'], $value['size'])) {
221
                $objs[ $key ] = new FileSubmission(
222
                    $value['name'],
223
                    $value['tmp_name'],
224
                    $value['size'],
225
                    $value['error']
226
                );
227
            } else {
228
                $objs[ $key ] = $this->createFileObjects($value);
229
            }
230
        }
231
        return $objs;
232
    }
233
}
234
// End of file FilesDataHandler.php
235
// Location: EventEspresso\core\services\request\files/FilesDataHandler.php
236