Completed
Branch FET/files-data-handler (ebf186)
by
unknown
35:38 queued 27:06
created

FilesDataHandler   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 257
Duplicated Lines 12.84 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 33
loc 257
rs 10
c 0
b 0
f 0
wmc 27
lcom 1
cbo 3

10 Methods

Rating   Name   Duplication   Size   Complexity  
A isStrangeFilesArray() 22 33 4
A fixFilesDataArray() 0 20 4
A organizeFilesData() 0 12 3
A createFileObjects() 11 35 4
A inputNameFromParts() 0 15 4
A getFileObjectFromNameParts() 0 4 1
A __construct() 0 4 1
A getFileObjects() 0 5 1
A initialize() 0 23 4
A getFileObject() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace EventEspresso\core\services\request\files;
4
5
use EventEspresso\core\services\collections\Collection;
6
use EventEspresso\core\services\request\Request;
7
use InvalidArgumentException;
8
use UnexpectedValueException;
9
10
/**
11
 * Class FilesDataHandler
12
 *
13
 * Helper for dealing with PHP's $_FILES. Instead of working with a sometimes puzzling array, with file info
14
 * dispersed throughout, creates a single collection of FileSubmissionInterface objects. This collection is a
15
 * one-dimensional list, where identifiers are the HTML input names.
16
 * Eg, access all file info for a file input named "file1" using
17
 *
18
 * ```
19
 * $data_handler = LoaderFactory::getLoader()->load(' EventEspresso\core\services\request\files\FilesDataHandler');
20
 * $file = $data_handler->getFileObject('file1);
21
 * ```
22
 *
23
 * and for a file input named "my[great][file][input]", use the same code but change the last line to:
24
 *
25
 * ```
26
 * $file = $data_handler->getFileObject('my[great][file][input]');
27
 * ```
28
 *
29
 * In both cases, $file will be a FileSubmissionInterface object, containing the file's name, temporary filepath, size,
30
 * error code, and helpers to get its mime type and extension.
31
 *
32
 *
33
 *
34
 * @package     Event Espresso
35
 * @author         Mike Nelson
36
 * @since         $VID:$
37
 *
38
 */
39
class FilesDataHandler
40
{
41
    /**
42
     * @var Request
43
     */
44
    protected $request;
45
46
    /**
47
     * @var CollectionInterface | FileSubmissionInterface[]
48
     */
49
    protected $file_objects;
50
51
    /**
52
     * @var bool
53
     */
54
    protected $initialized = false;
55
56
    /**
57
     * FilesDataHandler constructor.
58
     * @param Request $request
59
     */
60
    public function __construct(Request $request)
61
    {
62
        $this->request = $request;
63
    }
64
65
    /**
66
     * @since $VID:$
67
     * @return CollectionInterface | FileSubmissionInterface[]
68
     * @throws UnexpectedValueException
69
     * @throws InvalidArgumentException
70
     */
71
    protected function getFileObjects()
72
    {
73
        $this->initialize();
74
        return $this->file_objects;
75
    }
76
77
    /**
78
     * Sets up the file objects from the request's $_FILES data.
79
     * @since $VID:$
80
     * @throws UnexpectedValueException
81
     * @throws InvalidArgumentException
82
     * @throws \EventEspresso\core\exceptions\InvalidInterfaceException
83
     */
84
    protected function initialize()
85
    {
86
        if ($this->initialized) {
87
            return;
88
        }
89
        $this->file_objects = new Collection(
90
            // collection interface
91
            'EventEspresso\core\services\request\files\FileSubmissionInterface',
92
            // collection name
93
            'submitted_files'
94
        );
95
        $files_raw_data = $this->request->filesParams();
96
        if (empty($files_raw_data)) {
97
            return;
98
        }
99
        if ($this->isStrangeFilesArray($files_raw_data)) {
100
            $data = $this->fixFilesDataArray($files_raw_data);
101
        } else {
102
            $data = $files_raw_data;
103
        }
104
        $this->createFileObjects($data);
105
        $this->initialized = true;
106
    }
107
108
    /**
109
     * Detects if $_FILES is a weird multi-dimensional array that needs fixing or not.
110
     * @since $VID:$
111
     * @param $files_data
112
     * @return bool
113
     * @throws UnexpectedValueException
114
     */
115
    protected function isStrangeFilesArray($files_data)
116
    {
117 View Code Duplication
        if (!is_array($files_data)) {
118
            throw new UnexpectedValueException(
119
                sprintf(
120
                    esc_html__(
121
                        'Unexpected PHP $_FILES data format. "%1$s" was expected to be an array.',
122
                        'event_espresso'
123
                    ),
124
                    (string) $files_data
125
                )
126
            );
127
        }
128
        $first_value = reset($files_data);
129 View Code Duplication
        if (!is_array($first_value)) {
130
            throw new UnexpectedValueException(
131
                sprintf(
132
                    esc_html__(
133
                        'Unexpected PHP $_FILES data format. "%1$s" was expected to be an array.',
134
                        'event_espresso'
135
                    ),
136
                    (string) $first_value
137
                )
138
            );
139
        }
140
        $first_sub_array_item = reset($first_value);
141
        if (is_array($first_sub_array_item)) {
142
            // not just a 2d array
143
            return true;
144
        }
145
        // yep, just 2d array
146
        return false;
147
    }
148
149
    /**
150
     * Takes into account that $_FILES does a weird thing when you have hierarchical form names (eg `<input type="file"
151
     * name="my[hierarchical][form]">`): it leaves the top-level form part alone, but replaces the SECOND part with
152
     * "name", "size", "tmp_name", etc. So that file's data is located at "my[name][hierarchical][form]",
153
     * "my[size][hierarchical][form]", "my[tmp_name][hierarchical][form]", etc. It's really weird.
154
     * @since $VID:$
155
     * @param $files_data
156
     * @return array
157
     */
158
    protected function fixFilesDataArray($files_data)
159
    {
160
        $sane_files_array = [];
161
        foreach ($files_data as $top_key => $top_key_value) {
162
            foreach ($top_key_value as $lower_key => $lower_key_value) {
163
                foreach ($lower_key_value as $lowest_key => $lowest_key_value) {
164
                    $next_data = [
165
                        $top_key => [
166
                            $lowest_key => $this->organizeFilesData($lowest_key_value, $lower_key, $lowest_key)
167
                        ]
168
                    ];
169
                    $sane_files_array = array_merge_recursive(
170
                        $sane_files_array,
171
                        $next_data
172
                    );
173
                }
174
            }
175
        }
176
        return $sane_files_array;
177
    }
178
179
    /**
180
     * Recursively explores the array until it finds a leaf node, and tacks `$type` as a final index in front of it.
181
     * @since $VID:$
182
     * @param $data either 'name', 'tmp_name', 'size', or 'error'
183
     * @param $type
184
     * @return array
185
     */
186
    protected function organizeFilesData($data, $type)
187
    {
188
        $organized_data = [];
189
        foreach ($data as $key => $val) {
190
            if (is_array($val)) {
191
                $organized_data[ $key ] = $this->organizeFilesData($val, $type);
192
            } else {
193
                $organized_data[ $key ][ $type ] = $val;
194
            }
195
        }
196
        return $organized_data;
197
    }
198
199
    /**
200
     * Takes the organized $_FILES array (where all file info is located at the same spot as you'd expect an input
201
     * to be in $_GET or $_POST, with all the file's data located side-by-side in an array) and creates a
202
     * multi-dimensional array of FileSubmissionInterface objects. Stores it in `$this->file_objects`.
203
     * @since $VID:$
204
     * @param array $organized_files $_FILES but organized like $_POST
205
     * @param array $name_parts_so_far for multidimensional HTML form names,
206
     * @throws UnexpectedValueException
207
     * @throws InvalidArgumentException
208
     */
209
    protected function createFileObjects($organized_files, $name_parts_so_far = [])
210
    {
211 View Code Duplication
        if (!is_array($organized_files)) {
212
            throw new UnexpectedValueException(
213
                sprintf(
214
                    esc_html__(
215
                        'Unexpected PHP $organized_files data format. "%1$s" was expected to be an array.',
216
                        'event_espresso'
217
                    ),
218
                    (string) $organized_files
219
                )
220
            );
221
        }
222
        foreach ($organized_files as $key => $value) {
223
            array_push(
224
                $name_parts_so_far,
225
                $key
226
            );
227
            if (isset($value['name'], $value['tmp_name'], $value['size'])) {
228
                $html_name = $this->inputNameFromParts($name_parts_so_far);
229
                $this->file_objects->add(
230
                    new FileSubmission(
231
                        $html_name,
232
                        $value['name'],
233
                        $value['tmp_name'],
234
                        $value['size'],
235
                        $value['error']
236
                    ),
237
                    $html_name
238
                );
239
            } else {
240
                $this->createFileObjects($value, $name_parts_so_far);
241
            }
242
        }
243
    }
244
245
    /**
246
     * Takes the input name parts, like `['my', 'great', 'file', 'input1']`
247
     * and returns the HTML name for it, "my[great][file][input1]"
248
     * @since $VID:$
249
     * @param $parts
250
     * @throws UnexpectedValueException
251
     */
252
    protected function inputNameFromParts($parts)
253
    {
254
        if (!is_array($parts)) {
255
            throw new UnexpectedValueException(esc_html__('Name parts should be an array.', 'event_espresso'));
256
        }
257
        $generated_string = '';
258
        foreach ($parts as $part) {
259
            if ($generated_string === '') {
260
                $generated_string = (string) $part;
261
            } else {
262
                $generated_string .= '[' . (string) $part . ']';
263
            }
264
        }
265
        return $generated_string;
266
    }
267
268
    /**
269
     * Gets the input by the indicated $name_parts.
270
     * Eg if you're looking for an input named "my[great][file][input1]", $name_parts
271
     * should be `['my', 'great', 'file', 'input1']`.
272
     * Alternatively, you could use `FileDataHandler::getFileObject('my[great][file][input1]');`
273
     * @since $VID:$
274
     * @param $name_parts
275
     * @throws UnexpectedValueException
276
     * @return FileSubmissionInterface
277
     */
278
    public function getFileObjectFromNameParts($name_parts)
279
    {
280
        return $this->getFileObjects()->get($this->inputNameFromParts($name_parts));
281
    }
282
283
    /**
284
     * Gets the FileSubmissionInterface corresponding to the HTML name provided.
285
     * @since $VID:$
286
     * @param $html_name
287
     * @return mixed
288
     * @throws InvalidArgumentException
289
     * @throws UnexpectedValueException
290
     */
291
    public function getFileObject($html_name)
292
    {
293
        return $this->getFileObjects()->get($html_name);
294
    }
295
}
296
// End of file FilesDataHandler.php
297
// Location: EventEspresso\core\services\request\files/FilesDataHandler.php
298