Completed
Pull Request — master (#5)
by Michael
02:20
created

UploadHandler   D

Complexity

Total Complexity 195

Size/Duplication

Total Lines 914
Duplicated Lines 4.27 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 39
loc 914
rs 4.4444
c 0
b 0
f 0
wmc 195
lcom 1
cbo 1

41 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 84 3
B initialize() 0 22 8
B get_full_url() 0 9 8
A get_user_id() 0 5 1
A get_user_path() 0 7 2
A get_upload_path() 0 6 3
A get_query_separator() 0 4 2
A get_download_url() 0 12 4
A set_file_delete_properties() 0 11 3
A fix_integer_overflow() 0 7 2
A get_file_size() 0 7 2
A is_valid_file_object() 0 5 2
A get_file_object() 0 19 5
A get_file_objects() 0 8 2
A count_file_objects() 0 4 1
C create_scaled_image() 5 58 17
A get_error_message() 0 4 2
A get_config_bytes() 0 16 4
D validate() 20 54 23
A upcount_name_callback() 0 6 3
A upcount_name() 0 4 1
A get_unique_filename() 0 15 4
A trim_file_name() 0 16 4
A get_file_name() 0 4 1
A handle_form_data() 0 4 1
B orient_image() 0 32 7
D handle_file_upload() 16 71 18
A getFileExtension() 0 5 1
A readfile() 0 4 1
A body() 0 4 1
A header() 0 4 1
B generate_response() 0 20 10
A get_version_param() 0 4 2
A get_file_name_param() 0 4 2
A get_file_type() 0 14 5
A download() 0 27 5
A send_content_type_header() 0 9 3
A send_access_control_headers() 0 7 2
A head() 0 12 2
A get() 0 17 4
D post() 0 34 19

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like UploadHandler 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 UploadHandler, and based on these observations, apply Extract Interface, too.

1
<?php namespace XoopsModules\Smallworld;
2
3
/*
4
 * jQuery File Upload Plugin PHP Class 6.1.2
5
 * https://github.com/blueimp/jQuery-File-Upload
6
 *
7
 * Copyright 2010, Sebastian Tschan
8
 * https://blueimp.net
9
 *
10
 * Licensed under the MIT license:
11
 * http://www.opensource.org/licenses/MIT
12
 */
13
14
/**
15
 * Class UploadHandler
16
 */
17
class UploadHandler
18
{
19
    public    $xoopsUser;
20
    protected $options;
21
22
    // PHP File Upload error message codes:
23
    // http://php.net/manual/en/features.file-upload.errors.php
24
    protected $error_messages = [
25
        1                     => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
26
        2                     => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
27
        3                     => 'The uploaded file was only partially uploaded',
28
        4                     => 'No file was uploaded',
29
        6                     => 'Missing a temporary folder',
30
        7                     => 'Failed to write file to disk',
31
        8                     => 'A PHP extension stopped the file upload',
32
        'post_max_size'       => 'The uploaded file exceeds the post_max_size directive in php.ini',
33
        'max_file_size'       => 'File is too big',
34
        'min_file_size'       => 'File is too small',
35
        'accept_file_types'   => 'Filetype not allowed',
36
        'max_number_of_files' => 'Maximum number of files exceeded',
37
        'max_width'           => 'Image exceeds maximum width',
38
        'min_width'           => 'Image requires a minimum width',
39
        'max_height'          => 'Image exceeds maximum height',
40
        'min_height'          => 'Image requires a minimum height',
41
    ];
42
43
    /**
44
     * UploadHandler constructor.
45
     * @param null $options
46
     * @param bool $initialize
47
     */
48
    public function __construct($options = null, $initialize = true)
49
    {
50
        global $xoopsUser;
51
        $userID        = $xoopsUser->getVar('uid');
52
        $this->options = [
53
            'script_url'                       => $this->get_full_url() . '/imgupload.php',
54
            'upload_dir'                       => XOOPS_ROOT_PATH . '/uploads/albums_smallworld/' . $userID . '/',
55
            'upload_url'                       => XOOPS_URL . '/uploads/albums_smallworld/' . $userID . '/',
56
            'user_dirs'                        => false,
57
            'mkdir_mode'                       => 0755,
58
            'param_name'                       => 'files',
59
            // Set the following option to 'POST', if your server does not support
60
            // DELETE requests. This is a parameter sent to the client:
61
            'delete_type'                      => 'DELETE',
62
            'access_control_allow_origin'      => '*',
63
            'access_control_allow_credentials' => false,
64
            'access_control_allow_methods'     => [
65
                'OPTIONS',
66
                'HEAD',
67
                'GET',
68
                'POST',
69
                'PUT',
70
                'PATCH',
71
                'DELETE',
72
            ],
73
            'access_control_allow_headers'     => [
74
                'Content-Type',
75
                'Content-Range',
76
                'Content-Disposition',
77
            ],
78
            // Enable to provide file downloads via GET requests to the PHP script:
79
            'download_via_php'                 => false,
80
            // Defines which files can be displayed inline when downloaded:
81
            'inline_file_types'                => '/\.(gif|jpe?g|png|JPE?G)$/i',
82
            // Defines which files (based on their names) are accepted for upload:
83
            'accept_file_types'                => '/.+$/i',
84
            // The php.ini settings upload_max_filesize and post_max_size
85
            // take precedence over the following max_file_size setting:
86
            'max_file_size'                    => null,
87
            'min_file_size'                    => 1,
88
            // The maximum number of files for the upload directory:
89
            'max_number_of_files'              => null,
90
            // Image resolution restrictions:
91
            'max_width'                        => null,
92
            'max_height'                       => null,
93
            'min_width'                        => 1,
94
            'min_height'                       => 1,
95
            // Set the following option to false to enable resumable uploads:
96
            'discard_aborted_uploads'          => true,
97
            // Set to true to rotate images based on EXIF meta data, if available:
98
            'orient_image'                     => false,
99
            'image_versions'                   => [
100
                // Uncomment the following version to restrict the size of
101
                // uploaded images:
102
                /*
103
                '' => array(
104
                    'max_width' => 1920,
105
                    'max_height' => 1200,
106
                    'jpeg_quality' => 95
107
                ),
108
                */
109
                // Uncomment the following to create medium sized images:
110
                /*
111
                'medium' => array(
112
                    'max_width' => 800,
113
                    'max_height' => 600,
114
                    'jpeg_quality' => 80
115
                ),
116
                */
117
                'thumbnails' => [
118
                    'upload_dir' => XOOPS_ROOT_PATH . '/uploads/albums_smallworld' . '/' . $userID . '/thumbnails/',
119
                    'upload_url' => XOOPS_URL . '/uploads/albums_smallworld' . '/' . $userID . '/thumbnails/',
120
                    'max_width'  => 80,
121
                    'max_height' => 80,
122
                ],
123
            ],
124
        ];
125
        if ($options) {
126
            $this->options = array_merge($this->options, $options);
127
        }
128
        if ($initialize) {
129
            $this->initialize();
130
        }
131
    }
132
133
    protected function initialize()
134
    {
135
        switch ($_SERVER['REQUEST_METHOD']) {
136
            case 'OPTIONS':
137
            case 'HEAD':
138
                $this->head();
139
                break;
140
            case 'GET':
141
                $this->get();
142
                break;
143
            case 'PATCH':
144
            case 'PUT':
145
            case 'POST':
146
                $this->post();
147
                break;
148
            case 'DELETE':
149
                $this->delete();
150
                break;
151
            default:
152
                $this->header('HTTP/1.1 405 Method Not Allowed');
153
        }
154
    }
155
156
    /**
157
     * @return string
158
     */
159
    protected function get_full_url()
160
    {
161
        $https = !empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS'];
162
        return ($https ? 'https://' : 'http://')
163
               . (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] . '@' : '')
164
               . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'] . ($https && 443 === $_SERVER['SERVER_PORT'] || 80 === $_SERVER['SERVER_PORT'] ? '' : ':'
165
                                                                                                                                                                                        . $_SERVER['SERVER_PORT'])))
166
               . substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
167
    }
168
169
    /**
170
     * @return string
171
     */
172
    protected function get_user_id()
173
    {
174
        @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
175
        return session_id();
176
    }
177
178
    /**
179
     * @return string
180
     */
181
    protected function get_user_path()
182
    {
183
        if ($this->options['user_dirs']) {
184
            return $this->get_user_id() . '/';
185
        }
186
        return '';
187
    }
188
189
    /**
190
     * @param null|string $file_name
191
     * @param null        $version
192
     * @return string
193
     */
194
    protected function get_upload_path($file_name = null, $version = null)
195
    {
196
        $file_name    = $file_name ?: '';
197
        $version_path = empty($version) ? '' : $version . '/';
198
        return $this->options['upload_dir'] . $this->get_user_path() . $version_path . $file_name;
199
    }
200
201
    /**
202
     * @param $url
203
     * @return string
204
     */
205
    protected function get_query_separator($url)
206
    {
207
        return false === strpos($url, '?') ? '?' : '&';
208
    }
209
210
    /**
211
     * @param      $file_name
212
     * @param null $version
213
     * @return string
214
     */
215
    protected function get_download_url($file_name, $version = null)
216
    {
217
        if ($this->options['download_via_php']) {
218
            $url = $this->options['script_url'] . $this->get_query_separator($this->options['script_url']) . 'file=' . rawurlencode($file_name);
219
            if ($version) {
220
                $url .= '&version=' . rawurlencode($version);
221
            }
222
            return $url . '&download=1';
223
        }
224
        $version_path = empty($version) ? '' : rawurlencode($version) . '/';
225
        return $this->options['upload_url'] . $this->get_user_path() . $version_path . rawurlencode($file_name);
226
    }
227
228
    /**
229
     * @param $file
230
     */
231
    protected function set_file_delete_properties($file)
232
    {
233
        $file->delete_url  = $this->options['script_url'] . $this->get_query_separator($this->options['script_url']) . 'file=' . rawurlencode($file->name);
234
        $file->delete_type = $this->options['delete_type'];
235
        if ('DELETE' !== $file->delete_type) {
236
            $file->delete_url .= '&_method=DELETE';
237
        }
238
        if ($this->options['access_control_allow_credentials']) {
239
            $file->delete_with_credentials = true;
240
        }
241
    }
242
243
    // Fix for overflowing signed 32 bit integers,
244
    // works for sizes up to 2^32-1 bytes (4 GiB - 1):
245
    /**
246
     * @param $size
247
     * @return float|int
248
     */
249
    protected function fix_integer_overflow($size)
250
    {
251
        if ($size < 0) {
252
            $size += 2.0 * (PHP_INT_MAX + 1);
253
        }
254
        return $size;
255
    }
256
257
    /**
258
     * @param      $file_path
259
     * @param bool $clear_stat_cache
260
     * @return float|int
261
     */
262
    protected function get_file_size($file_path, $clear_stat_cache = false)
263
    {
264
        if ($clear_stat_cache) {
265
            clearstatcache(true, $file_path);
266
        }
267
        return $this->fix_integer_overflow(filesize($file_path));
268
    }
269
270
    /**
271
     * @param $file_name
272
     * @return bool
273
     */
274
    protected function is_valid_file_object($file_name)
275
    {
276
        $file_path = $this->get_upload_path($file_name);
277
        return is_file($file_path) && '.' !== $file_name[0];
278
    }
279
280
    /**
281
     * @param $file_name
282
     * @return null|\stdClass
283
     */
284
    protected function get_file_object($file_name)
285
    {
286
        if ($this->is_valid_file_object($file_name)) {
287
            $file       = new \stdClass();
288
            $file->name = $file_name;
289
            $file->size = $this->get_file_size($this->get_upload_path($file_name));
290
            $file->url  = $this->get_download_url($file->name);
291
            foreach ($this->options['image_versions'] as $version => $options) {
0 ignored issues
show
Bug introduced by
The expression $this->options['image_versions'] of type string|integer|array<int...ight\":\"integer\"}>"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
292
                if (!empty($version)) {
293
                    if (is_file($this->get_upload_path($file_name, $version))) {
294
                        $file->{$version . '_url'} = $this->get_download_url($file->name, $version);
295
                    }
296
                }
297
            }
298
            $this->set_file_delete_properties($file);
299
            return $file;
300
        }
301
        return null;
302
    }
303
304
    /**
305
     * @param string $iteration_method
306
     * @return array
307
     */
308
    protected function get_file_objects($iteration_method = 'get_file_object')
309
    {
310
        $upload_dir = $this->get_upload_path();
311
        if (!is_dir($upload_dir)) {
312
            return [];
313
        }
314
        return array_values(array_filter(array_map([$this, $iteration_method], scandir($upload_dir, SCANDIR_SORT_NONE))));
315
    }
316
317
    /**
318
     * @return int
319
     */
320
    protected function count_file_objects()
321
    {
322
        return count($this->get_file_objects('is_valid_file_object'));
323
    }
324
325
    /**
326
     * @param $file_name
327
     * @param $version
328
     * @param $options
329
     * @return bool
330
     */
331
    protected function create_scaled_image($file_name, $version, $options)
332
    {
333
        $file_path = $this->get_upload_path($file_name);
334
        if (!empty($version)) {
335
            $version_dir = $this->get_upload_path(null, $version);
336 View Code Duplication
            if (!is_dir($version_dir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
337
                if (!mkdir($version_dir, $this->options['mkdir_mode'], true) && !is_dir($version_dir)) {
338
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $version_dir));
339
                }
340
            }
341
            $new_file_path = $version_dir . '/' . $file_name;
342
        } else {
343
            $new_file_path = $file_path;
344
        }
345
        list($img_width, $img_height) = @getimagesize($file_path);
346
        if (!$img_width || !$img_height) {
347
            return false;
348
        }
349
        $scale = min($options['max_width'] / $img_width, $options['max_height'] / $img_height);
350
        if ($scale >= 1) {
351
            if ($file_path !== $new_file_path) {
352
                return copy($file_path, $new_file_path);
353
            }
354
            return true;
355
        }
356
        $new_width  = $img_width * $scale;
357
        $new_height = $img_height * $scale;
358
        $new_img    = @imagecreatetruecolor($new_width, $new_height);
359
        switch (strtolower(substr(strrchr($file_name, '.'), 1))) {
360
            case 'jpg':
361
            case 'jpeg':
362
                $src_img       = @imagecreatefromjpeg($file_path);
363
                $write_image   = 'imagejpeg';
364
                $image_quality = isset($options['jpeg_quality']) ? $options['jpeg_quality'] : 75;
365
                break;
366
            case 'gif':
367
                @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
368
                $src_img       = @imagecreatefromgif($file_path);
369
                $write_image   = 'imagegif';
370
                $image_quality = null;
371
                break;
372
            case 'png':
373
                @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
374
                @imagealphablending($new_img, false);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
375
                @imagesavealpha($new_img, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
376
                $src_img       = @imagecreatefrompng($file_path);
377
                $write_image   = 'imagepng';
378
                $image_quality = isset($options['png_quality']) ? $options['png_quality'] : 9;
379
                break;
380
            default:
381
                $src_img = null;
382
        }
383
        $success = $src_img && @imagecopyresampled($new_img, $src_img, 0, 0, 0, 0, $new_width, $new_height, $img_width, $img_height) && $write_image($new_img, $new_file_path, $image_quality);
0 ignored issues
show
Bug introduced by
The variable $write_image does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $image_quality does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
384
        // Free up memory (imagedestroy does not delete files):
385
        @imagedestroy($src_img);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
386
        @imagedestroy($new_img);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
387
        return $success;
388
    }
389
390
    /**
391
     * @param $error
392
     * @return mixed
393
     */
394
    protected function get_error_message($error)
395
    {
396
        return array_key_exists($error, $this->error_messages) ? $this->error_messages[$error] : $error;
397
    }
398
399
    /**
400
     * @param $val
401
     * @return float|int
402
     */
403
    public function get_config_bytes($val)
404
    {
405
        $val  = trim($val);
406
        $last = strtolower($val[strlen($val) - 1]);
407
        switch ($last) {
408
            case 'g':
409
                $val *= 1024;
410
            // no break
411
            case 'm':
412
                $val *= 1024;
413
            // no break
414
            case 'k':
415
                $val *= 1024;
416
        }
417
        return $this->fix_integer_overflow($val);
418
    }
419
420
    /**
421
     * @param $uploaded_file
422
     * @param $file
423
     * @param $error
424
     * @param $index
425
     * @return bool
426
     */
427
    protected function validate($uploaded_file, $file, $error, $index)
0 ignored issues
show
Unused Code introduced by
The parameter $index is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
428
    {
429
        if ($error) {
430
            $file->error = $this->get_error_message($error);
431
            return false;
432
        }
433
        $content_length = $this->fix_integer_overflow((int)$_SERVER['CONTENT_LENGTH']);
434
        $post_max_size  = $this->get_config_bytes(ini_get('post_max_size'));
435
        if ($post_max_size && ($content_length > $post_max_size)) {
436
            $file->error = $this->get_error_message('post_max_size');
437
            return false;
438
        }
439
        if (!preg_match($this->options['accept_file_types'], $file->name)) {
440
            $file->error = $this->get_error_message('accept_file_types');
441
            return false;
442
        }
443
        if ($uploaded_file && is_uploaded_file($uploaded_file)) {
444
            $file_size = $this->get_file_size($uploaded_file);
445
        } else {
446
            $file_size = $content_length;
447
        }
448
        if ($this->options['max_file_size'] && ($file_size > $this->options['max_file_size'] || $file->size > $this->options['max_file_size'])) {
449
            $file->error = $this->get_error_message('max_file_size');
450
            return false;
451
        }
452
        if ($this->options['min_file_size'] && $file_size < $this->options['min_file_size']) {
453
            $file->error = $this->get_error_message('min_file_size');
454
            return false;
455
        }
456
        if (is_int($this->options['max_number_of_files']) && ($this->count_file_objects() >= $this->options['max_number_of_files'])) {
457
            $file->error = $this->get_error_message('max_number_of_files');
458
            return false;
459
        }
460
        list($img_width, $img_height) = @getimagesize($uploaded_file);
461
        if (is_int($img_width)) {
462 View Code Duplication
            if ($this->options['max_width'] && $img_width > $this->options['max_width']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
463
                $file->error = $this->get_error_message('max_width');
464
                return false;
465
            }
466 View Code Duplication
            if ($this->options['max_height'] && $img_height > $this->options['max_height']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
467
                $file->error = $this->get_error_message('max_height');
468
                return false;
469
            }
470 View Code Duplication
            if ($this->options['min_width'] && $img_width < $this->options['min_width']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
471
                $file->error = $this->get_error_message('min_width');
472
                return false;
473
            }
474 View Code Duplication
            if ($this->options['min_height'] && $img_height < $this->options['min_height']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
475
                $file->error = $this->get_error_message('min_height');
476
                return false;
477
            }
478
        }
479
        return true;
480
    }
481
482
    /**
483
     * @param $matches
484
     * @return string
485
     */
486
    protected function upcount_name_callback($matches)
487
    {
488
        $index = isset($matches[1]) ? (int)$matches[1] + 1 : 1;
489
        $ext   = isset($matches[2]) ? $matches[2] : '';
490
        return ' (' . $index . ')' . $ext;
491
    }
492
493
    /**
494
     * @param $name
495
     * @return null|string|string[]
496
     */
497
    protected function upcount_name($name)
498
    {
499
        return preg_replace_callback('/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', [$this, 'upcount_name_callback'], $name, 1);
500
    }
501
502
    /**
503
     * @param $name
504
     * @param $type
505
     * @param $index
506
     * @param $content_range
507
     * @return null|string|string[]
508
     */
509
    protected function get_unique_filename($name, $type, $index, $content_range)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $index is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
510
    {
511
        while (is_dir($this->get_upload_path($name))) {
512
            $name = $this->upcount_name($name);
513
        }
514
        // Keep an existing filename if this is part of a chunked upload:
515
        $uploaded_bytes = $this->fix_integer_overflow((int)$content_range[1]);
516
        while (is_file($this->get_upload_path($name))) {
517
            if ($uploaded_bytes === $this->get_file_size($this->get_upload_path($name))) {
518
                break;
519
            }
520
            $name = $this->upcount_name($name);
521
        }
522
        return $name;
523
    }
524
525
    /**
526
     * @param $name
527
     * @param $type
528
     * @param $index
529
     * @param $content_range
530
     * @return mixed|string
531
     */
532
    protected function trim_file_name($name, $type, $index, $content_range)
0 ignored issues
show
Unused Code introduced by
The parameter $index is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $content_range is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
533
    {
534
        // Remove path information and dots around the filename, to prevent uploading
535
        // into different directories or replacing hidden system files.
536
        // Also remove control characters and spaces (\x00..\x20) around the filename:
537
        $name = trim(basename(stripslashes($name)), ".\x00..\x20");
538
        // Use a timestamp for empty filenames:
539
        if (!$name) {
540
            $name = str_replace('.', '-', microtime(true));
541
        }
542
        // Add missing file extension for known image types:
543
        if (false === strpos($name, '.') && preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
544
            $name .= '.' . $matches[1];
545
        }
546
        return $name;
547
    }
548
549
    /**
550
     * @param $name
551
     * @param $type
552
     * @param $index
553
     * @param $content_range
554
     * @return null|string|string[]
555
     */
556
    protected function get_file_name($name, $type, $index, $content_range)
557
    {
558
        return $this->get_unique_filename($this->trim_file_name($name, $type, $index, $content_range), $type, $index, $content_range);
559
    }
560
561
    /**
562
     * @param $file
563
     * @param $index
564
     */
565
    protected function handle_form_data($file, $index)
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $index is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
566
    {
567
        // Handle form data, e.g. $_REQUEST['description'][$index]
568
    }
569
570
    /**
571
     * @param $file_path
572
     * @return bool
573
     */
574
    protected function orient_image($file_path)
575
    {
576
        if (!function_exists('exif_read_data')) {
577
            return false;
578
        }
579
        $exif = @exif_read_data($file_path);
580
        if (false === $exif) {
581
            return false;
582
        }
583
        $orientation = (int)(@$exif['Orientation']);
584
        if (!in_array($orientation, [3, 6, 8])) {
585
            return false;
586
        }
587
        $image = @imagecreatefromjpeg($file_path);
588
        switch ($orientation) {
589
            case 3:
590
                $image = @imagerotate($image, 180, 0);
591
                break;
592
            case 6:
593
                $image = @imagerotate($image, 270, 0);
594
                break;
595
            case 8:
596
                $image = @imagerotate($image, 90, 0);
597
                break;
598
            default:
599
                return false;
600
        }
601
        $success = imagejpeg($image, $file_path);
602
        // Free up memory (imagedestroy does not delete files):
603
        @imagedestroy($image);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
604
        return $success;
605
    }
606
607
    /**
608
     * @param      $uploaded_file
609
     * @param      $name
610
     * @param      $size
611
     * @param      $type
612
     * @param      $error
613
     * @param null $index
614
     * @param null $content_range
615
     * @return \XoopsModules\Smallworld|\stdClass
616
     */
617
    protected function handle_file_upload(
618
        $uploaded_file,
619
        $name,
620
        $size,
621
        $type,
622
        $error,
623
        $index = null,
624
        $content_range = null
625
    ) {
626
        global $xoopsUser, $SwDatabase;
627
        $file = new \stdClass();
628
629
        $file->name = $this->get_file_name($name, $type, $index, $content_range);
630
        $file->size = $this->fix_integer_overflow((int)$size);
631
        $file->type = $type;
632
633
        // Save to database for later use
634
        $db     = new SwDatabase();
635
        $userid = $xoopsUser->getVar('uid');
636
637
        // Generate new name for file
638
        $file->name = basename(stripslashes($name));
639
        $file->name = time() . mt_rand(0, 99999) . '.' . $this->getFileExtension($name);
640
        $img        = XOOPS_URL . '/uploads/albums_smallworld/' . $userid . '/' . $file->name;
641
        $db->saveImage("'', '" . $userid . "', '" . $file->name . "', '" . addslashes($img) . "', '" . time() . "', ''");
642
643
        if ($this->validate($uploaded_file, $file, $error, $index)) {
644
            $this->handle_form_data($file, $index);
645
            $upload_dir = $this->get_upload_path();
646 View Code Duplication
            if (!is_dir($upload_dir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
647
                if (!mkdir($upload_dir, $this->options['mkdir_mode'], true) && !is_dir($upload_dir)) {
648
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $upload_dir));
649
                }
650
            }
651
            $file_path   = $this->get_upload_path($file->name);
652
            $append_file = $content_range && is_file($file_path) && $file->size > $this->get_file_size($file_path);
653 View Code Duplication
            if ($uploaded_file && is_uploaded_file($uploaded_file)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
654
                // multipart/formdata uploads (POST method uploads)
655
                if ($append_file) {
656
                    file_put_contents($file_path, fopen($uploaded_file, 'r'), FILE_APPEND);
657
                } else {
658
                    move_uploaded_file($uploaded_file, $file_path);
659
                }
660
            } else {
661
                // Non-multipart uploads (PUT method support)
662
                file_put_contents($file_path, fopen('php://input', 'r'), $append_file ? FILE_APPEND : 0);
663
            }
664
            $file_size = $this->get_file_size($file_path, $append_file);
665
            if ($file_size === $file->size) {
666
                if ($this->options['orient_image']) {
667
                    $this->orient_image($file_path);
668
                }
669
                $file->url = $this->get_download_url($file->name);
670
                foreach ($this->options['image_versions'] as $version => $options) {
0 ignored issues
show
Bug introduced by
The expression $this->options['image_versions'] of type string|integer|array<int...ight\":\"integer\"}>"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
671
                    if ($this->create_scaled_image($file->name, $version, $options)) {
672
                        if (!empty($version)) {
673
                            $file->{$version . '_url'} = $this->get_download_url($file->name, $version);
674
                        } else {
675
                            $file_size = $this->get_file_size($file_path, true);
676
                        }
677
                    }
678
                }
679
            } elseif (!$content_range && $this->options['discard_aborted_uploads']) {
680
                unlink($file_path);
681
                $file->error = 'abort';
682
            }
683
            $file->size = $file_size;
684
            $this->set_file_delete_properties($file);
685
        }
686
        return $file;
687
    }
688
689
    //function to return file extension from a path or file name
690
691
    /**
692
     * @param $path
693
     * @return mixed
694
     */
695
    public function getFileExtension($path)
696
    {
697
        $parts = pathinfo($path);
698
        return $parts['extension'];
699
    }
700
701
    /**
702
     * @param $file_path
703
     * @return false|int
704
     */
705
    protected function readfile($file_path)
706
    {
707
        return readfile($file_path);
708
    }
709
710
    /**
711
     * @param $str
712
     */
713
    protected function body($str)
714
    {
715
        echo $str;
716
    }
717
718
    /**
719
     * @param $str
720
     */
721
    protected function header($str)
722
    {
723
        header($str);
724
    }
725
726
    /**
727
     * @param      $content
728
     * @param bool $print_response
729
     */
730
    protected function generate_response($content, $print_response = true)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
731
    {
732
        if ($print_response) {
733
            $json     = json_encode($content);
734
            $redirect = isset($_REQUEST['redirect']) ? stripslashes($_REQUEST['redirect']) : null;
735
            if ($redirect) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $redirect of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
736
                $this->header('Location: ' . sprintf($redirect, rawurlencode($json)));
737
                return;
738
            }
739
            $this->head();
740
            if (isset($_SERVER['HTTP_CONTENT_RANGE'])) {
741
                $files = isset($content[$this->options['param_name']]) ? $content[$this->options['param_name']] : null;
742
                if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) {
743
                    $this->header('Range: 0-' . ($this->fix_integer_overflow((int)$files[0]->size) - 1));
744
                }
745
            }
746
            $this->body($json);
747
        }
748
        return $content;
749
    }
750
751
    /**
752
     * @return null|string
753
     */
754
    protected function get_version_param()
755
    {
756
        return isset($_GET['version']) ? basename(stripslashes($_GET['version'])) : null;
757
    }
758
759
    /**
760
     * @return null|string
761
     */
762
    protected function get_file_name_param()
763
    {
764
        return isset($_GET['file']) ? basename(stripslashes($_GET['file'])) : null;
765
    }
766
767
    /**
768
     * @param $file_path
769
     * @return string
770
     */
771
    protected function get_file_type($file_path)
772
    {
773
        switch (strtolower(pathinfo($file_path, PATHINFO_EXTENSION))) {
774
            case 'jpeg':
775
            case 'jpg':
776
                return 'image/jpeg';
777
            case 'png':
778
                return 'image/png';
779
            case 'gif':
780
                return 'image/gif';
781
            default:
782
                return '';
783
        }
784
    }
785
786
    protected function download()
787
    {
788
        if (!$this->options['download_via_php']) {
789
            $this->header('HTTP/1.1 403 Forbidden');
790
            return;
791
        }
792
        $file_name = $this->get_file_name_param();
793
        if ($this->is_valid_file_object($file_name)) {
794
            $file_path = $this->get_upload_path($file_name, $this->get_version_param());
0 ignored issues
show
Bug introduced by
It seems like $this->get_version_param() targeting XoopsModules\Smallworld\...er::get_version_param() can also be of type string; however, XoopsModules\Smallworld\...dler::get_upload_path() does only seem to accept null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
795
            if (is_file($file_path)) {
796
                if (!preg_match($this->options['inline_file_types'], $file_name)) {
797
                    $this->header('Content-Description: File Transfer');
798
                    $this->header('Content-Type: application/octet-stream');
799
                    $this->header('Content-Disposition: attachment; filename="' . $file_name . '"');
800
                    $this->header('Content-Transfer-Encoding: binary');
801
                } else {
802
                    // Prevent Internet Explorer from MIME-sniffing the content-type:
803
                    $this->header('X-Content-Type-Options: nosniff');
804
                    $this->header('Content-Type: ' . $this->get_file_type($file_path));
805
                    $this->header('Content-Disposition: inline; filename="' . $file_name . '"');
806
                }
807
                $this->header('Content-Length: ' . $this->get_file_size($file_path));
808
                $this->header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($file_path)));
809
                $this->readfile($file_path);
810
            }
811
        }
812
    }
813
814
    protected function send_content_type_header()
815
    {
816
        $this->header('Vary: Accept');
817
        if (isset($_SERVER['HTTP_ACCEPT']) && (false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/json'))) {
818
            $this->header('Content-type: application/json');
819
        } else {
820
            $this->header('Content-type: text/plain');
821
        }
822
    }
823
824
    protected function send_access_control_headers()
825
    {
826
        $this->header('Access-Control-Allow-Origin: ' . $this->options['access_control_allow_origin']);
827
        $this->header('Access-Control-Allow-Credentials: ' . ($this->options['access_control_allow_credentials'] ? 'true' : 'false'));
828
        $this->header('Access-Control-Allow-Methods: ' . implode(', ', $this->options['access_control_allow_methods']));
829
        $this->header('Access-Control-Allow-Headers: ' . implode(', ', $this->options['access_control_allow_headers']));
830
    }
831
832
    public function head()
833
    {
834
        $this->header('Pragma: no-cache');
835
        $this->header('Cache-Control: no-store, no-cache, must-revalidate');
836
        $this->header('Content-Disposition: inline; filename="files.json"');
837
        // Prevent Internet Explorer from MIME-sniffing the content-type:
838
        $this->header('X-Content-Type-Options: nosniff');
839
        if ($this->options['access_control_allow_origin']) {
840
            $this->send_access_control_headers();
841
        }
842
        $this->send_content_type_header();
843
    }
844
845
    /**
846
     * @param bool $print_response
847
     */
848
    public function get($print_response = true)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
849
    {
850
        if ($print_response && isset($_GET['download'])) {
851
            return $this->download();
852
        }
853
        $file_name = $this->get_file_name_param();
854
        if (null !== $file_name) {
855
            $response = [
856
                substr($this->options['param_name'], 0, -1) => $this->get_file_object($file_name),
857
            ];
858
        } else {
859
            $response = [
860
                $this->options['param_name'] => $this->get_file_objects(),
861
            ];
862
        }
863
        return $this->generate_response($response, $print_response);
864
    }
865
866
    /**
867
     * @param bool $print_response
868
     */
869
    public function post($print_response = true)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
870
    {
871
        if (isset($_REQUEST['_method']) && 'DELETE' === $_REQUEST['_method']) {
872
            return $this->delete($print_response);
873
        }
874
        $upload = isset($_FILES[$this->options['param_name']]) ? $_FILES[$this->options['param_name']] : null;
875
        // Parse the Content-Disposition header, if available:
876
        $file_name = isset($_SERVER['HTTP_CONTENT_DISPOSITION']) ? rawurldecode(preg_replace('/(^[^"]+")|("$)/', '', $_SERVER['HTTP_CONTENT_DISPOSITION'])) : null;
877
        // Parse the Content-Range header, which has the following form:
878
        // Content-Range: bytes 0-524287/2000000
879
        $content_range = isset($_SERVER['HTTP_CONTENT_RANGE']) ? preg_split('/[^0-9]+/', $_SERVER['HTTP_CONTENT_RANGE']) : null;
880
        $size          = $content_range ? $content_range[3] : null;
881
        $files         = [];
882
        if ($upload && is_array($upload['tmp_name'])) {
883
            // param_name is an array identifier like "files[]",
884
            // $_FILES is a multi-dimensional array:
885
            foreach ($upload['tmp_name'] as $index => $value) {
886
                $files[] = $this->handle_file_upload($upload['tmp_name'][$index], $file_name ?: $upload['name'][$index], $size ?: $upload['size'][$index], $upload['type'][$index], $upload['error'][$index], $index, $content_range);
0 ignored issues
show
Bug introduced by
It seems like $content_range defined by isset($_SERVER['HTTP_CON...CONTENT_RANGE']) : null on line 879 can also be of type array; however, XoopsModules\Smallworld\...r::handle_file_upload() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
887
            }
888
        } else {
889
            // param_name is a single object identifier like "file",
890
            // $_FILES is a one-dimensional array:
891
            $files[] = $this->handle_file_upload(
892
                isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
893
                $file_name ?: (isset($upload['name']) ? $upload['name'] : null),
894
                $size ?: (isset($upload['size']) ? $upload['size'] : $_SERVER['CONTENT_LENGTH']),
895
                isset($upload['type']) ? $upload['type'] : $_SERVER['CONTENT_TYPE'],
896
                isset($upload['error']) ? $upload['error'] : null,
897
                null,
898
                $content_range
0 ignored issues
show
Bug introduced by
It seems like $content_range defined by isset($_SERVER['HTTP_CON...CONTENT_RANGE']) : null on line 879 can also be of type array; however, XoopsModules\Smallworld\...r::handle_file_upload() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
899
            );
900
        }
901
        return $this->generate_response([$this->options['param_name'] => $files], $print_response);
902
    }
903
904
    /**
905
     * @param bool $print_response
906
     */
907
    public function delete($print_response = true)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
908
    {
909
        global $xoopsUser;
910
        $userid    = $xoopsUser->getVar('uid');
911
        $db        = new SwDatabase();
912
        $file_name = $this->get_file_name_param();
913
        $file_path = $this->get_upload_path($file_name);
914
        $success   = is_file($file_path) && '.' !== $file_name[0] && unlink($file_path);
915
916
        // Delete file based on user and filename
917
        $db->DeleteImage($userid, $file_name);
918
        $db->DeleteImage($userid, 'Thumbs.db');
919
920
        if ($success) {
921
            foreach ($this->options['image_versions'] as $version => $options) {
0 ignored issues
show
Bug introduced by
The expression $this->options['image_versions'] of type string|integer|array<int...ight\":\"integer\"}>"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
922
                if (!empty($version)) {
923
                    $file = $this->get_upload_path($file_name, $version);
924
                    if (is_file($file)) {
925
                        unlink($file);
926
                    }
927
                }
928
            }
929
        }
930
        return $this->generate_response(['success' => $success], $print_response);
931
    }
932
}
933