GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#2843)
by Brendan
04:11
created

FieldUpload   F

Complexity

Total Complexity 130

Size/Duplication

Total Lines 816
Duplicated Lines 0 %

Importance

Changes 14
Bugs 1 Features 0
Metric Value
c 14
b 1
f 0
dl 0
loc 816
rs 1.263
wmc 130

29 Methods

Rating   Name   Duplication   Size   Complexity  
A isSortable() 0 3 1
A canFilter() 0 3 1
A canPrePopulate() 0 3 1
A prepareTableValue() 0 16 4
D checkPostFieldData() 0 83 20
A getFilePath() 0 9 1
A commit() 0 18 4
A checkFields() 0 15 3
A getCurrentValues() 0 10 1
A prepareImportValue() 0 12 3
D appendFormattedElement() 0 28 10
C buildDSRetrievalSQL() 0 53 9
A entryDataCleanup() 0 11 2
A prepareTextValue() 0 6 2
A prepareAssociationsDrawerXMLElement() 0 7 1
B getMetaInfo() 0 16 5
D displayPublishPanel() 0 43 10
B validateFilename() 0 28 5
D processRawFieldData() 0 146 25
A getExampleFormMarkup() 0 6 1
C prepareExportValue() 0 28 7
A getExportModes() 0 6 1
A getImportModes() 0 5 1
A buildSortingSelectSQL() 0 3 1
B displaySettingsPanel() 0 42 6
B fetchFilterableOperators() 0 41 1
A createTable() 0 4 1
A buildSortingSQL() 0 14 2
A __construct() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like FieldUpload often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FieldUpload, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @package toolkit
5
 */
6
7
/**
8
 * A simple Upload field that essentially maps to HTML's `<input type='file '/>`.
9
 */
10
class FieldUpload extends Field implements ExportableField, ImportableField
11
{
12
    protected static $imageMimeTypes = array(
13
        'image/gif',
14
        'image/jpg',
15
        'image/jpeg',
16
        'image/pjpeg',
17
        'image/png',
18
        'image/x-png'
19
    );
20
21
    public function __construct()
22
    {
23
        parent::__construct();
24
25
        $this->_name = __('File Upload');
0 ignored issues
show
Bug Best Practice introduced by
The property _name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
26
        $this->_required = true;
27
28
        $this->set('location', 'sidebar');
29
        $this->set('required', 'no');
30
    }
31
32
    /*-------------------------------------------------------------------------
33
        Definition:
34
    -------------------------------------------------------------------------*/
35
36
    public function canFilter()
37
    {
38
        return true;
39
    }
40
41
    public function canPrePopulate()
42
    {
43
        return true;
44
    }
45
46
    public function isSortable()
47
    {
48
        return true;
49
    }
50
51
    public function fetchFilterableOperators()
52
    {
53
        return array(
54
            array(
55
                'title' => 'is',
56
                'filter' => ' ',
57
                'help' => __('Find files that are an exact match for the given string.')
58
            ),
59
            array(
60
                'filter' => 'sql: NOT NULL',
61
                'title' => 'is not empty',
62
                'help' => __('Find entries where a file has been saved.')
63
            ),
64
            array(
65
                'filter' => 'sql: NULL',
66
                'title' => 'is empty',
67
                'help' => __('Find entries where no file has been saved.')
68
            ),
69
            array(
70
                'title' => 'contains',
71
                'filter' => 'regexp: ',
72
                'help' => __('Find files that match the given <a href="%s">MySQL regular expressions</a>.', array(
73
                    'https://dev.mysql.com/doc/mysql/en/regexp.html'
74
                ))
75
            ),
76
            array(
77
                'title' => 'does not contain',
78
                'filter' => 'not-regexp: ',
79
                'help' => __('Find files that do not match the given <a href="%s">MySQL regular expressions</a>.', array(
80
                    'https://dev.mysql.com/doc/mysql/en/regexp.html'
81
                ))
82
            ),
83
            array(
84
                'title' => 'file type is',
85
                'filter' => 'mimetype: ',
86
                'help' => __('Find files that match the given mimetype.')
87
            ),
88
            array(
89
                'title' => 'size is',
90
                'filter' => 'size: ',
91
                'help' => __('Find files that match the given size.')
92
            )
93
        );
94
    }
95
96
    /*-------------------------------------------------------------------------
97
        Setup:
98
    -------------------------------------------------------------------------*/
99
100
    public function createTable()
101
    {
102
        return Symphony::Database()->query(
103
            "CREATE TABLE IF NOT EXISTS `tbl_entries_data_" . $this->get('id') . "` (
0 ignored issues
show
Bug introduced by
Are you sure $this->get('id') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

103
            "CREATE TABLE IF NOT EXISTS `tbl_entries_data_" . /** @scrutinizer ignore-type */ $this->get('id') . "` (
Loading history...
104
              `id` int(11) unsigned NOT null auto_increment,
105
              `entry_id` int(11) unsigned NOT null,
106
              `file` varchar(255) default null,
107
              `size` int(11) unsigned null,
108
              `mimetype` varchar(100) default null,
109
              `meta` varchar(255) default null,
110
              PRIMARY KEY  (`id`),
111
              UNIQUE KEY `entry_id` (`entry_id`),
112
              KEY `file` (`file`),
113
              KEY `mimetype` (`mimetype`)
114
            ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;"
115
        );
116
    }
117
118
    /*-------------------------------------------------------------------------
119
        Utilities:
120
    -------------------------------------------------------------------------*/
121
122
    public function entryDataCleanup($entry_id, $data = null)
123
    {
124
        $file_location = $this->getFilePath($data['file']);
125
126
        if (is_file($file_location)) {
127
            General::deleteFile($file_location);
128
        }
129
130
        parent::entryDataCleanup($entry_id);
131
132
        return true;
133
    }
134
135
    public static function getMetaInfo($file, $type)
136
    {
137
        $meta = array();
138
139
        if (!file_exists($file) || !is_readable($file)) {
140
            return $meta;
141
        }
142
143
        $meta['creation'] = DateTimeObj::get('c', filemtime($file));
144
145
        if (General::in_iarray($type, fieldUpload::$imageMimeTypes) && $array = @getimagesize($file)) {
146
            $meta['width'] = $array[0];
147
            $meta['height'] = $array[1];
148
        }
149
150
        return $meta;
151
    }
152
153
    public function getFilePath($filename)
154
    {
155
        /**
156
         * Ensure the file exists in the `WORKSPACE` directory
157
         * @link http://getsymphony.com/discuss/issues/view/610/
158
         */
159
        $file = WORKSPACE . preg_replace(array('%/+%', '%(^|/)\.\./%', '%\/workspace\/%'), '/', $this->get('destination') . '/' . $filename);
0 ignored issues
show
Bug introduced by
The constant WORKSPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
Are you sure $this->get('destination') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
        $file = WORKSPACE . preg_replace(array('%/+%', '%(^|/)\.\./%', '%\/workspace\/%'), '/', /** @scrutinizer ignore-type */ $this->get('destination') . '/' . $filename);
Loading history...
160
161
        return $file;
162
    }
163
164
    /*-------------------------------------------------------------------------
165
        Settings:
166
    -------------------------------------------------------------------------*/
167
168
    public function displaySettingsPanel(XMLElement &$wrapper, $errors = null)
169
    {
170
        parent::displaySettingsPanel($wrapper, $errors);
171
172
        // Destination Folder
173
        $ignore = array(
174
            '/workspace/events',
175
            '/workspace/data-sources',
176
            '/workspace/text-formatters',
177
            '/workspace/pages',
178
            '/workspace/utilities'
179
        );
180
        $directories = General::listDirStructure(WORKSPACE, null, true, DOCROOT, $ignore);
0 ignored issues
show
Bug introduced by
The constant DOCROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant WORKSPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
181
182
        $label = Widget::Label(__('Destination Directory'));
183
184
        $options = array();
185
        $options[] = array('/workspace', false, '/workspace');
186
187
        if (!empty($directories) && is_array($directories)) {
188
            foreach ($directories as $d) {
189
                $d = '/' . trim($d, '/');
190
191
                if (!in_array($d, $ignore)) {
192
                    $options[] = array($d, ($this->get('destination') == $d), $d);
193
                }
194
            }
195
        }
196
197
        $label->appendChild(Widget::Select('fields['.$this->get('sortorder').'][destination]', $options));
0 ignored issues
show
Bug introduced by
Are you sure $this->get('sortorder') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

197
        $label->appendChild(Widget::Select('fields['./** @scrutinizer ignore-type */ $this->get('sortorder').'][destination]', $options));
Loading history...
198
199
        if (isset($errors['destination'])) {
200
            $wrapper->appendChild(Widget::Error($label, $errors['destination']));
201
        } else {
202
            $wrapper->appendChild($label);
203
        }
204
205
        // Validation rule
206
        $this->buildValidationSelect($wrapper, $this->get('validator'), 'fields['.$this->get('sortorder').'][validator]', 'upload', $errors);
0 ignored issues
show
Bug introduced by
It seems like $this->get('validator') can also be of type array; however, parameter $selected of Field::buildValidationSelect() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

206
        $this->buildValidationSelect($wrapper, /** @scrutinizer ignore-type */ $this->get('validator'), 'fields['.$this->get('sortorder').'][validator]', 'upload', $errors);
Loading history...
207
208
        // Requirements and table display
209
        $this->appendStatusFooter($wrapper);
210
    }
211
212
    public function checkFields(array &$errors, $checkForDuplicates = true)
213
    {
214
        if (is_dir(DOCROOT . $this->get('destination') . '/') === false) {
0 ignored issues
show
Bug introduced by
Are you sure $this->get('destination') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

214
        if (is_dir(DOCROOT . /** @scrutinizer ignore-type */ $this->get('destination') . '/') === false) {
Loading history...
Bug introduced by
The constant DOCROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
215
            $errors['destination'] = __('The destination directory, %s, does not exist.', array(
216
                '<code>' . $this->get('destination') . '</code>'
217
            ));
218
        } elseif (is_writable(DOCROOT . $this->get('destination') . '/') === false) {
219
            $errors['destination'] = __('The destination directory is not writable.')
220
                . ' '
221
                . __('Please check permissions on %s.', array(
222
                    '<code>' . $this->get('destination') . '</code>'
223
                ));
224
        }
225
226
        parent::checkFields($errors, $checkForDuplicates);
227
    }
228
229
    public function commit()
230
    {
231
        if (!parent::commit()) {
232
            return false;
233
        }
234
235
        $id = $this->get('id');
236
237
        if ($id === false) {
238
            return false;
239
        }
240
241
        $fields = array();
242
243
        $fields['destination'] = $this->get('destination');
244
        $fields['validator'] = ($fields['validator'] == 'custom' ? null : $this->get('validator'));
245
246
        return FieldManager::saveSettings($id, $fields);
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type array; however, parameter $field_id of FieldManager::saveSettings() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

246
        return FieldManager::saveSettings(/** @scrutinizer ignore-type */ $id, $fields);
Loading history...
247
    }
248
249
    /*-------------------------------------------------------------------------
250
        Publish:
251
    -------------------------------------------------------------------------*/
252
253
    public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)
254
    {
255
        if (is_dir(DOCROOT . $this->get('destination') . '/') === false) {
0 ignored issues
show
Bug introduced by
The constant DOCROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
Are you sure $this->get('destination') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

255
        if (is_dir(DOCROOT . /** @scrutinizer ignore-type */ $this->get('destination') . '/') === false) {
Loading history...
256
            $flagWithError = __('The destination directory, %s, does not exist.', array(
257
                '<code>' . $this->get('destination') . '</code>'
258
            ));
259
        } elseif ($flagWithError && is_writable(DOCROOT . $this->get('destination') . '/') === false) {
260
            $flagWithError = __('Destination folder is not writable.')
261
                . ' '
262
                . __('Please check permissions on %s.', array(
263
                    '<code>' . $this->get('destination') . '</code>'
264
                ));
265
        }
266
267
        $label = Widget::Label($this->get('label'));
0 ignored issues
show
Bug introduced by
It seems like $this->get('label') can also be of type array; however, parameter $name of Widget::Label() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

267
        $label = Widget::Label(/** @scrutinizer ignore-type */ $this->get('label'));
Loading history...
268
        $label->setAttribute('class', 'file');
269
270
        if ($this->get('required') !== 'yes') {
271
            $label->appendChild(new XMLElement('i', __('Optional')));
272
        }
273
274
        $span = new XMLElement('span', null, array('class' => 'frame'));
275
276
        if (isset($data['file'])) {
277
            $filename = $this->get('destination') . '/' . basename($data['file']);
278
            $file = $this->getFilePath($data['file']);
279
            if (file_exists($file) === false || !is_readable($file)) {
280
                $flagWithError = __('The file uploaded is no longer available. Please check that it exists, and is readable.');
281
            }
282
283
            $span->appendChild(new XMLElement('span', Widget::Anchor(preg_replace("![^a-z0-9]+!i", "$0&#8203;", $filename), URL . $filename)));
0 ignored issues
show
Bug introduced by
The constant URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
284
        } else {
285
            $filename = null;
286
        }
287
288
        $span->appendChild(Widget::Input('fields'.$fieldnamePrefix.'['.$this->get('element_name').']'.$fieldnamePostfix, $filename, ($filename ? 'hidden' : 'file')));
0 ignored issues
show
Bug introduced by
Are you sure $this->get('element_name') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

288
        $span->appendChild(Widget::Input('fields'.$fieldnamePrefix.'['./** @scrutinizer ignore-type */ $this->get('element_name').']'.$fieldnamePostfix, $filename, ($filename ? 'hidden' : 'file')));
Loading history...
289
290
        $label->appendChild($span);
291
292
        if ($flagWithError != null) {
293
            $wrapper->appendChild(Widget::Error($label, $flagWithError));
294
        } else {
295
            $wrapper->appendChild($label);
296
        }
297
    }
298
299
    public function validateFilename($file, &$message)
300
    {
301
        if ($this->get('validator') != null) {
302
            $rule = $this->get('validator');
303
304
            if (General::validateString($file, $rule) === false) {
305
                $message = __('File chosen in ‘%s’ does not match allowable file types for that field.', array(
306
                    $this->get('label')
307
                ));
308
309
                return self::__INVALID_FIELDS__;
310
            }
311
        }
312
        // If the developer did not specified any validator, check for the
313
        // blacklisted file types instead
314
        else {
315
            $blacklist = Symphony::Configuration()->get('upload_blacklist', 'admin');
316
317
            if (!empty($blacklist) && General::validateString($file, $blacklist)) {
318
                $message = __('File chosen in ‘%s’ is blacklisted for that field.', array(
319
                    $this->get('label')
320
                ));
321
322
                return self::__INVALID_FIELDS__;
323
            }
324
        }
325
326
        return self::__OK__;
327
    }
328
329
    public function checkPostFieldData($data, &$message, $entry_id = null)
330
    {
331
        /**
332
         * For information about PHPs upload error constants see:
333
         * @link http://php.net/manual/en/features.file-upload.errors.php
334
         */
335
        $message = null;
336
337
        if (
338
            empty($data)
339
            || (
340
                is_array($data)
341
                && isset($data['error'])
342
                && $data['error'] == UPLOAD_ERR_NO_FILE
343
            )
344
        ) {
345
            if ($this->get('required') === 'yes') {
346
                $message = __('‘%s’ is a required field.', array($this->get('label')));
347
348
                return self::__MISSING_FIELDS__;
349
            }
350
351
            return self::__OK__;
352
        }
353
354
        // Its not an array, so just retain the current data and return
355
        if (is_array($data) === false) {
356
            $file = $this->getFilePath(basename($data));
0 ignored issues
show
Bug introduced by
$data of type array is incompatible with the type string expected by parameter $path of basename(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

356
            $file = $this->getFilePath(basename(/** @scrutinizer ignore-type */ $data));
Loading history...
357
            if (file_exists($file) === false || !is_readable($file)) {
358
                $message = __('The file uploaded is no longer available. Please check that it exists, and is readable.');
359
360
                return self::__INVALID_FIELDS__;
361
            }
362
363
            // Ensure that the file still matches the validator and hasn't
364
            // changed since it was uploaded.
365
            return $this->validateFilename($file, $message);
366
        }
367
368
        if (is_dir(DOCROOT . $this->get('destination') . '/') === false) {
0 ignored issues
show
Bug introduced by
The constant DOCROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
Are you sure $this->get('destination') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

368
        if (is_dir(DOCROOT . /** @scrutinizer ignore-type */ $this->get('destination') . '/') === false) {
Loading history...
369
            $message = __('The destination directory, %s, does not exist.', array(
370
                '<code>' . $this->get('destination') . '</code>'
371
            ));
372
373
            return self::__ERROR__;
374
        } elseif (is_writable(DOCROOT . $this->get('destination') . '/') === false) {
375
            $message = __('Destination folder is not writable.')
376
                . ' '
377
                . __('Please check permissions on %s.', array(
378
                    '<code>' . $this->get('destination') . '</code>'
379
                ));
380
381
            return self::__ERROR__;
382
        }
383
384
        if ($data['error'] != UPLOAD_ERR_NO_FILE && $data['error'] != UPLOAD_ERR_OK) {
385
            switch ($data['error']) {
386
                case UPLOAD_ERR_INI_SIZE:
387
                    $message = __('File chosen in ‘%1$s’ exceeds the maximum allowed upload size of %2$s specified by your host.', array($this->get('label'), (is_numeric(ini_get('upload_max_filesize')) ? General::formatFilesize(ini_get('upload_max_filesize')) : ini_get('upload_max_filesize'))));
0 ignored issues
show
Bug introduced by
ini_get('upload_max_filesize') of type string is incompatible with the type integer expected by parameter $file_size of General::formatFilesize(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

387
                    $message = __('File chosen in ‘%1$s’ exceeds the maximum allowed upload size of %2$s specified by your host.', array($this->get('label'), (is_numeric(ini_get('upload_max_filesize')) ? General::formatFilesize(/** @scrutinizer ignore-type */ ini_get('upload_max_filesize')) : ini_get('upload_max_filesize'))));
Loading history...
388
                    break;
389
                case UPLOAD_ERR_FORM_SIZE:
390
                    $message = __('File chosen in ‘%1$s’ exceeds the maximum allowed upload size of %2$s, specified by Symphony.', array($this->get('label'), General::formatFilesize($_POST['MAX_FILE_SIZE'])));
391
                    break;
392
                case UPLOAD_ERR_PARTIAL:
393
                case UPLOAD_ERR_NO_TMP_DIR:
394
                    $message = __('File chosen in ‘%s’ was only partially uploaded due to an error.', array($this->get('label')));
395
                    break;
396
                case UPLOAD_ERR_CANT_WRITE:
397
                    $message = __('Uploading ‘%s’ failed. Could not write temporary file to disk.', array($this->get('label')));
398
                    break;
399
                case UPLOAD_ERR_EXTENSION:
400
                    $message = __('Uploading ‘%s’ failed. File upload stopped by extension.', array($this->get('label')));
401
                    break;
402
            }
403
404
            return self::__ERROR_CUSTOM__;
405
        }
406
407
        // Sanitize the filename
408
        $data['name'] = Lang::createFilename($data['name']);
409
410
        // Validate the filename
411
        return $this->validateFilename($data['name'], $message);
412
    }
413
414
    public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)
415
    {
416
        $status = self::__OK__;
417
418
        // No file given, save empty data:
419
        if ($data === null) {
420
            return array(
421
                'file' =>       null,
422
                'mimetype' =>   null,
423
                'size' =>       null,
424
                'meta' =>       null
425
            );
426
        }
427
428
        // Its not an array, so just retain the current data and return:
429
        if (is_array($data) === false) {
430
            $file = $this->getFilePath(basename($data));
0 ignored issues
show
Bug introduced by
$data of type array is incompatible with the type string expected by parameter $path of basename(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

430
            $file = $this->getFilePath(basename(/** @scrutinizer ignore-type */ $data));
Loading history...
431
432
            $result = array(
433
                'file' =>       $data,
434
                'mimetype' =>   null,
435
                'size' =>       null,
436
                'meta' =>       null
437
            );
438
439
            // Grab the existing entry data to preserve the MIME type and size information
440
            if (isset($entry_id)) {
441
                $row = $this->getCurrentValues($entry_id);
442
443
                if (empty($row) === false) {
444
                    $result = $row;
445
                }
446
            }
447
448
            // Found the file, add any missing meta information:
449
            if (file_exists($file) && is_readable($file)) {
450
                if (empty($result['mimetype'])) {
451
                    $result['mimetype'] = General::getMimeType($file);
0 ignored issues
show
Bug Best Practice introduced by
The method General::getMimeType() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

451
                    /** @scrutinizer ignore-call */ 
452
                    $result['mimetype'] = General::getMimeType($file);
Loading history...
452
                }
453
454
                if (empty($result['size'])) {
455
                    $result['size'] = filesize($file);
456
                }
457
458
                if (empty($result['meta'])) {
459
                    $result['meta'] = serialize(static::getMetaInfo($file, $result['mimetype']));
460
                }
461
462
                // The file was not found, or is unreadable:
463
            } else {
464
                $message = __('The file uploaded is no longer available. Please check that it exists, and is readable.');
465
                $status = self::__INVALID_FIELDS__;
466
            }
467
468
            return $result;
469
        }
470
471
        if ($simulate && is_null($entry_id)) {
472
            return $data;
473
        }
474
475
        // Check to see if the entry already has a file associated with it:
476
        if (is_null($entry_id) === false) {
477
            $row = $this->getCurrentValues($entry_id);
478
479
            $existing_file = isset($row['file']) ? $this->getFilePath($row['file']) : null;
480
481
            // File was removed:
482
            if (
483
                $data['error'] == UPLOAD_ERR_NO_FILE
484
                && !is_null($existing_file)
485
                && is_file($existing_file)
486
            ) {
487
                General::deleteFile($existing_file);
488
            }
489
        }
490
491
        // Do not continue on upload error:
492
        if ($data['error'] == UPLOAD_ERR_NO_FILE || $data['error'] != UPLOAD_ERR_OK) {
493
            return false;
494
        }
495
496
        // Where to upload the new file?
497
        $abs_path = DOCROOT . '/' . trim($this->get('destination'), '/');
0 ignored issues
show
Bug introduced by
The constant DOCROOT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
It seems like $this->get('destination') can also be of type array; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

497
        $abs_path = DOCROOT . '/' . trim(/** @scrutinizer ignore-type */ $this->get('destination'), '/');
Loading history...
498
        $rel_path = str_replace('/workspace', '', $this->get('destination'));
499
500
        // Sanitize the filename
501
        $data['name'] = Lang::createFilename($data['name']);
502
503
        // If a file already exists, then rename the file being uploaded by
504
        // adding `_1` to the filename. If `_1` already exists, the logic
505
        // will keep adding 1 until a filename is available (#672)
506
        if (file_exists($abs_path . '/' . $data['name'])) {
507
            $extension = General::getExtension($data['name']);
508
            $new_file = substr($abs_path . '/' . $data['name'], 0, -1 - strlen($extension));
0 ignored issues
show
Bug introduced by
$extension of type array is incompatible with the type string expected by parameter $string of strlen(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

508
            $new_file = substr($abs_path . '/' . $data['name'], 0, -1 - strlen(/** @scrutinizer ignore-type */ $extension));
Loading history...
509
            $count = 1;
510
511
            do {
512
                $renamed_file = $new_file . '_' . $count . '.' . $extension;
0 ignored issues
show
Bug introduced by
Are you sure $extension of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
                $renamed_file = $new_file . '_' . $count . '.' . /** @scrutinizer ignore-type */ $extension;
Loading history...
513
                $count++;
514
            } while (file_exists($renamed_file));
515
516
            // Extract the name filename from `$renamed_file`.
517
            $data['name'] = str_replace($abs_path . '/', '', $renamed_file);
518
        }
519
520
        $file = $this->getFilePath($data['name']);
521
522
        // Attempt to upload the file:
523
        $uploaded = General::uploadFile(
524
            $abs_path,
525
            $data['name'],
526
            $data['tmp_name'],
527
            Symphony::Configuration()->get('write_mode', 'file')
0 ignored issues
show
Bug introduced by
It seems like Symphony::Configuration(...t('write_mode', 'file') can also be of type array; however, parameter $perm of General::uploadFile() does only seem to accept integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

527
            /** @scrutinizer ignore-type */ Symphony::Configuration()->get('write_mode', 'file')
Loading history...
528
        );
529
530
        if ($uploaded === false) {
531
            $message = __(
532
                __('There was an error while trying to upload the file %1$s to the target directory %2$s.'),
533
                array(
534
                    '<code>' . $data['name'] . '</code>',
535
                    '<code>workspace/' . ltrim($rel_path, '/') . '</code>'
536
                )
537
            );
538
            $status = self::__ERROR_CUSTOM__;
539
540
            return false;
541
        }
542
543
        // File has been replaced:
544
        if (
545
            isset($existing_file)
546
            && $existing_file !== $file
547
            && is_file($existing_file)
548
        ) {
549
            General::deleteFile($existing_file);
550
        }
551
552
        // Get the mimetype, don't trust the browser. RE: #1609
553
        $data['type'] = General::getMimeType($file);
554
555
        return array(
556
            'file' =>       basename($file),
557
            'size' =>       $data['size'],
558
            'mimetype' =>   $data['type'],
559
            'meta' =>       serialize(static::getMetaInfo($file, $data['type']))
560
        );
561
    }
562
563
    protected function getCurrentValues($entry_id)
564
    {
565
        return Symphony::Database()->fetchRow(0, sprintf(
566
            "SELECT `file`, `mimetype`, `size`, `meta`
567
                FROM `tbl_entries_data_%d`
568
                WHERE `entry_id` = %d
569
                LIMIT 1
570
            ",
571
            $this->get('id'),
0 ignored issues
show
Bug introduced by
It seems like $this->get('id') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

571
            /** @scrutinizer ignore-type */ $this->get('id'),
Loading history...
572
            $entry_id
573
        ));
574
    }
575
576
    /*-------------------------------------------------------------------------
577
        Output:
578
    -------------------------------------------------------------------------*/
579
580
    public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
581
    {
582
        // It is possible an array of null data will be passed in. Check for this.
583
        if (!is_array($data) || !isset($data['file']) || is_null($data['file'])) {
584
            return;
585
        }
586
587
        $file = $this->getFilePath($data['file']);
588
        $filesize = (file_exists($file) && is_readable($file)) ? filesize($file) : null;
589
        $item = new XMLElement($this->get('element_name'));
0 ignored issues
show
Bug introduced by
It seems like $this->get('element_name') can also be of type array; however, parameter $name of XMLElement::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

589
        $item = new XMLElement(/** @scrutinizer ignore-type */ $this->get('element_name'));
Loading history...
590
        $item->setAttributeArray(array(
591
            'size' =>   !is_null($filesize) ? General::formatFilesize($filesize) : 'unknown',
592
            'bytes' =>  !is_null($filesize) ? $filesize : 'unknown',
593
            'path' =>   General::sanitize(
594
                str_replace(WORKSPACE, null, dirname($file))
0 ignored issues
show
Bug introduced by
The constant WORKSPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
595
            ),
596
            'type' =>   $data['mimetype']
597
        ));
598
599
        $item->appendChild(new XMLElement('filename', General::sanitize(basename($file))));
600
601
        $m = unserialize($data['meta']);
602
603
        if (is_array($m) && !empty($m)) {
604
            $item->appendChild(new XMLElement('meta', null, $m));
605
        }
606
607
        $wrapper->appendChild($item);
608
    }
609
610
    public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
611
    {
612
        if (isset($data['file']) === false || !$file = $data['file']) {
613
            return parent::prepareTableValue(null, $link, $entry_id);
614
        }
615
616
        if ($link) {
617
            $link->setValue(basename($file));
618
            $link->setAttribute('data-path', $this->get('destination'));
0 ignored issues
show
Bug introduced by
It seems like $this->get('destination') can also be of type array; however, parameter $value of XMLElement::setAttribute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

618
            $link->setAttribute('data-path', /** @scrutinizer ignore-type */ $this->get('destination'));
Loading history...
619
620
            return $link->generate();
621
        } else {
622
            $link = Widget::Anchor(basename($file), URL . $this->get('destination') . '/' . $file);
0 ignored issues
show
Bug introduced by
The constant URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
Are you sure $this->get('destination') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

622
            $link = Widget::Anchor(basename($file), URL . /** @scrutinizer ignore-type */ $this->get('destination') . '/' . $file);
Loading history...
623
            $link->setAttribute('data-path', $this->get('destination'));
624
625
            return $link->generate();
626
        }
627
    }
628
    
629
    public function prepareTextValue($data, $entry_id = null)
630
    {
631
        if (isset($data['file'])) {
632
            return $data['file'];
633
        }
634
        return null;
635
    }
636
637
    public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepopulate = '')
638
    {
639
        $li = parent::prepareAssociationsDrawerXMLElement($e, $parent_association);
640
        $a = $li->getChild(0);
641
        $a->setAttribute('data-path', $this->get('destination'));
0 ignored issues
show
Bug introduced by
It seems like $this->get('destination') can also be of type array; however, parameter $value of XMLElement::setAttribute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

641
        $a->setAttribute('data-path', /** @scrutinizer ignore-type */ $this->get('destination'));
Loading history...
642
643
        return $li;
644
    }
645
646
    /*-------------------------------------------------------------------------
647
        Import:
648
    -------------------------------------------------------------------------*/
649
650
    public function getImportModes()
651
    {
652
        return array(
653
            'getValue' =>       ImportableField::STRING_VALUE,
654
            'getPostdata' =>    ImportableField::ARRAY_VALUE
655
        );
656
    }
657
658
    public function prepareImportValue($data, $mode, $entry_id = null)
659
    {
660
        $message = $status = null;
661
        $modes = (object)$this->getImportModes();
662
663
        if ($mode === $modes->getValue) {
664
            return $data;
665
        } elseif ($mode === $modes->getPostdata) {
666
            return $this->processRawFieldData($data, $status, $message, true, $entry_id);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->processRaw...ssage, true, $entry_id) also could return the type false which is incompatible with the return type mandated by ImportableField::prepareImportValue() of string|array.
Loading history...
667
        }
668
669
        return null;
670
    }
671
672
    /*-------------------------------------------------------------------------
673
        Export:
674
    -------------------------------------------------------------------------*/
675
676
    /**
677
     * Return a list of supported export modes for use with `prepareExportValue`.
678
     *
679
     * @return array
680
     */
681
    public function getExportModes()
682
    {
683
        return array(
684
            'getFilename' =>    ExportableField::VALUE,
685
            'getObject' =>      ExportableField::OBJECT,
686
            'getPostdata' =>    ExportableField::POSTDATA
687
        );
688
    }
689
690
    /**
691
     * Give the field some data and ask it to return a value using one of many
692
     * possible modes.
693
     *
694
     * @param mixed $data
695
     * @param integer $mode
696
     * @param integer $entry_id
697
     * @return array|string|null
698
     */
699
    public function prepareExportValue($data, $mode, $entry_id = null)
700
    {
701
        $modes = (object)$this->getExportModes();
702
703
        $filepath = $this->getFilePath($data['file']);
704
705
        // No file, or the file that the entry is meant to have no
706
        // longer exists.
707
        if (!isset($data['file']) || !is_file($filepath)) {
708
            return null;
709
        }
710
711
        if ($mode === $modes->getFilename) {
712
            return $data['file'];
713
        }
714
715
        if ($mode === $modes->getObject) {
716
            $object = (object)$data;
717
718
            if (isset($object->meta)) {
719
                $object->meta = unserialize($object->meta);
720
            }
721
722
            return $object;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $object returns the type object which is incompatible with the documented return type null|string|array.
Loading history...
723
        }
724
725
        if ($mode === $modes->getPostdata) {
726
            return $data['file'];
727
        }
728
    }
729
730
    /*-------------------------------------------------------------------------
731
        Filtering:
732
    -------------------------------------------------------------------------*/
733
734
    public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false)
735
    {
736
        $field_id = $this->get('id');
737
738
        if (preg_match('/^mimetype:/', $data[0])) {
739
            $data[0] = str_replace('mimetype:', '', $data[0]);
740
            $column = 'mimetype';
741
        } elseif (preg_match('/^size:/', $data[0])) {
742
            $data[0] = str_replace('size:', '', $data[0]);
743
            $column = 'size';
744
        } else {
745
            $column = 'file';
746
        }
747
748
        if (self::isFilterRegex($data[0])) {
749
            $this->buildRegexSQL($data[0], array($column), $joins, $where);
750
        } elseif (self::isFilterSQL($data[0])) {
751
            $this->buildFilterSQL($data[0], array($column), $joins, $where);
752
        } elseif ($andOperation) {
753
            foreach ($data as $value) {
754
                $this->_key++;
755
                $value = $this->cleanValue($value);
756
                $joins .= "
757
                    LEFT JOIN
758
                        `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
759
                        ON (e.id = t{$field_id}_{$this->_key}.entry_id)
760
                ";
761
                $where .= "
762
                    AND t{$field_id}_{$this->_key}.{$column} = '{$value}'
763
                ";
764
            }
765
        } else {
766
            if (!is_array($data)) {
767
                $data = array($data);
768
            }
769
770
            foreach ($data as &$value) {
771
                $value = $this->cleanValue($value);
772
            }
773
774
            $this->_key++;
775
            $data = implode("', '", $data);
776
            $joins .= "
777
                LEFT JOIN
778
                    `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
779
                    ON (e.id = t{$field_id}_{$this->_key}.entry_id)
780
            ";
781
            $where .= "
782
                AND t{$field_id}_{$this->_key}.{$column} IN ('{$data}')
783
            ";
784
        }
785
786
        return true;
787
    }
788
789
    /*-------------------------------------------------------------------------
790
        Sorting:
791
    -------------------------------------------------------------------------*/
792
793
    public function buildSortingSQL(&$joins, &$where, &$sort, $order = 'ASC')
794
    {
795
        if ($this->isRandomOrder($order)) {
796
            $sort = 'ORDER BY RAND()';
797
        } else {
798
            $sort = sprintf(
799
                'ORDER BY (
800
                    SELECT %s
801
                    FROM tbl_entries_data_%d AS `ed`
802
                    WHERE entry_id = e.id
803
                ) %s',
804
                '`ed`.file',
805
                $this->get('id'),
0 ignored issues
show
Bug introduced by
It seems like $this->get('id') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

805
                /** @scrutinizer ignore-type */ $this->get('id'),
Loading history...
806
                $order
807
            );
808
        }
809
    }
810
811
    public function buildSortingSelectSQL($sort, $order = 'ASC')
812
    {
813
        return null;
814
    }
815
816
    /*-------------------------------------------------------------------------
817
        Events:
818
    -------------------------------------------------------------------------*/
819
820
    public function getExampleFormMarkup()
821
    {
822
        $label = Widget::Label($this->get('label'));
0 ignored issues
show
Bug introduced by
It seems like $this->get('label') can also be of type array; however, parameter $name of Widget::Label() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

822
        $label = Widget::Label(/** @scrutinizer ignore-type */ $this->get('label'));
Loading history...
823
        $label->appendChild(Widget::Input('fields['.$this->get('element_name').']', null, 'file'));
0 ignored issues
show
Bug introduced by
Are you sure $this->get('element_name') of type null|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

823
        $label->appendChild(Widget::Input('fields['./** @scrutinizer ignore-type */ $this->get('element_name').']', null, 'file'));
Loading history...
824
825
        return $label;
826
    }
827
}
828