Completed
Push — master ( 8e591c...fd0c8a )
by
unknown
04:42 queued 02:15
created

UploadHandler   F

Complexity

Total Complexity 198

Size/Duplication

Total Lines 952
Duplicated Lines 3.15 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 30
loc 952
rs 1.648
c 0
b 0
f 0
wmc 198
lcom 1
cbo 1

42 Methods

Rating   Name   Duplication   Size   Complexity  
B initialize() 0 22 8
B __construct() 0 83 3
B get_full_url() 0 10 8
A get_user_id() 0 6 1
A get_user_path() 0 8 2
A get_upload_path() 0 7 3
A get_query_separator() 0 4 2
A get_download_url() 0 14 4
A set_file_delete_properties() 0 11 3
A fix_integer_overflow() 0 8 2
A get_file_size() 0 8 2
A is_valid_file_object() 0 6 2
A get_file_object() 0 21 5
A get_file_objects() 0 9 2
A count_file_objects() 0 4 1
C create_scaled_image() 5 60 17
A get_error_message() 0 4 2
A get_config_bytes() 0 17 4
D validate() 20 65 23
A upcount_name_callback() 0 7 3
A upcount_name() 0 4 1
A get_unique_filename() 0 16 4
A trim_file_name() 0 17 4
A get_file_name() 0 4 1
A handle_form_data() 0 4 1
B orient_image() 0 33 7
D handle_file_upload() 5 72 18
A getFileExtension() 0 6 1
A readfile() 0 4 1
A body() 0 4 1
A header() 0 4 1
B generate_response() 0 22 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 28 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 18 4
D post() 0 35 19
B delete() 0 25 7

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
2
3
namespace XoopsModules\Smallworld;
4
5
/*
6
 * jQuery File Upload Plugin PHP Class 6.1.2
7
 * https://github.com/blueimp/jQuery-File-Upload
8
 *
9
 * Copyright 2010, Sebastian Tschan
10
 * https://blueimp.net
11
 *
12
 * Licensed under the MIT license:
13
 * http://www.opensource.org/licenses/MIT
14
 */
15
16
/**
17
 * Class UploadHandler
18
 */
19
class UploadHandler
20
{
21
    protected $options;
22
23
    // PHP File Upload error message codes:
24
    // http://php.net/manual/en/features.file-upload.errors.php
25
    protected $error_messages = [
26
        1                     => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
27
        2                     => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
28
        3                     => 'The uploaded file was only partially uploaded',
29
        4                     => 'No file was uploaded',
30
        6                     => 'Missing a temporary folder',
31
        7                     => 'Failed to write file to disk',
32
        8                     => 'A PHP extension stopped the file upload',
33
        'post_max_size'       => 'The uploaded file exceeds the post_max_size directive in php.ini',
34
        'max_file_size'       => 'File is too big',
35
        'min_file_size'       => 'File is too small',
36
        'accept_file_types'   => 'Filetype not allowed',
37
        'max_number_of_files' => 'Maximum number of files exceeded',
38
        'max_width'           => 'Image exceeds maximum width',
39
        'min_width'           => 'Image requires a minimum width',
40
        'max_height'          => 'Image exceeds maximum height',
41
        'min_height'          => 'Image requires a minimum height',
42
    ];
43
44
    /**
45
     * UploadHandler constructor.
46
     * @param null $options
47
     * @param bool $initialize
48
     */
49
    public function __construct($options = null, $initialize = true)
50
    {
51
        $userID = $GLOBALS['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
163
        return ($https ? 'https://' : 'http://')
164
               . (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] . '@' : '')
165
               . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'] . ($https && 443 === $_SERVER['SERVER_PORT'] || 80 === $_SERVER['SERVER_PORT'] ? '' : ':'
166
                                                                                                                                                                                        . $_SERVER['SERVER_PORT'])))
167
               . mb_substr($_SERVER['SCRIPT_NAME'], 0, mb_strrpos($_SERVER['SCRIPT_NAME'], '/'));
168
    }
169
170
    /**
171
     * @return string
172
     */
173
    protected function get_user_id()
174
    {
175
        @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...
176
177
        return session_id();
178
    }
179
180
    /**
181
     * @return string
182
     */
183
    protected function get_user_path()
184
    {
185
        if ($this->options['user_dirs']) {
186
            return $this->get_user_id() . '/';
187
        }
188
189
        return '';
190
    }
191
192
    /**
193
     * @param null|string $file_name
194
     * @param null        $version
195
     * @return string
196
     */
197
    protected function get_upload_path($file_name = null, $version = null)
198
    {
199
        $file_name    = $file_name ?: '';
200
        $version_path = empty($version) ? '' : $version . '/';
201
202
        return $this->options['upload_dir'] . $this->get_user_path() . $version_path . $file_name;
203
    }
204
205
    /**
206
     * @param $url
207
     * @return string
208
     */
209
    protected function get_query_separator($url)
210
    {
211
        return false === mb_strpos($url, '?') ? '?' : '&';
212
    }
213
214
    /**
215
     * @param      $file_name
216
     * @param null $version
217
     * @return string
218
     */
219
    protected function get_download_url($file_name, $version = null)
220
    {
221
        if ($this->options['download_via_php']) {
222
            $url = $this->options['script_url'] . $this->get_query_separator($this->options['script_url']) . 'file=' . rawurlencode($file_name);
223
            if ($version) {
224
                $url .= '&version=' . rawurlencode($version);
225
            }
226
227
            return $url . '&download=1';
228
        }
229
        $version_path = empty($version) ? '' : rawurlencode($version) . '/';
230
231
        return $this->options['upload_url'] . $this->get_user_path() . $version_path . rawurlencode($file_name);
232
    }
233
234
    /**
235
     * @param $file
236
     */
237
    protected function set_file_delete_properties($file)
238
    {
239
        $file->delete_url  = $this->options['script_url'] . $this->get_query_separator($this->options['script_url']) . 'file=' . rawurlencode($file->name);
240
        $file->delete_type = $this->options['delete_type'];
241
        if ('DELETE' !== $file->delete_type) {
242
            $file->delete_url .= '&_method=DELETE';
243
        }
244
        if ($this->options['access_control_allow_credentials']) {
245
            $file->delete_with_credentials = true;
246
        }
247
    }
248
249
    // Fix for overflowing signed 32 bit integers,
250
    // works for sizes up to 2^32-1 bytes (4 GiB - 1):
251
252
    /**
253
     * @param $size
254
     * @return float|int
255
     */
256
    protected function fix_integer_overflow($size)
257
    {
258
        if ($size < 0) {
259
            $size += 2.0 * (PHP_INT_MAX + 1);
260
        }
261
262
        return $size;
263
    }
264
265
    /**
266
     * @param      $file_path
267
     * @param bool $clear_stat_cache
268
     * @return float|int
269
     */
270
    protected function get_file_size($file_path, $clear_stat_cache = false)
271
    {
272
        if ($clear_stat_cache) {
273
            clearstatcache(true, $file_path);
274
        }
275
276
        return $this->fix_integer_overflow(filesize($file_path));
277
    }
278
279
    /**
280
     * @param $file_name
281
     * @return bool
282
     */
283
    protected function is_valid_file_object($file_name)
284
    {
285
        $file_path = $this->get_upload_path($file_name);
286
287
        return is_file($file_path) && '.' !== $file_name[0];
288
    }
289
290
    /**
291
     * @param $file_name
292
     * @return null|\stdClass
293
     */
294
    protected function get_file_object($file_name)
295
    {
296
        if ($this->is_valid_file_object($file_name)) {
297
            $file       = new \stdClass();
298
            $file->name = $file_name;
299
            $file->size = $this->get_file_size($this->get_upload_path($file_name));
300
            $file->url  = $this->get_download_url($file->name);
301
            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...
302
                if (!empty($version)) {
303
                    if (is_file($this->get_upload_path($file_name, $version))) {
304
                        $file->{$version . '_url'} = $this->get_download_url($file->name, $version);
305
                    }
306
                }
307
            }
308
            $this->set_file_delete_properties($file);
309
310
            return $file;
311
        }
312
313
        return null;
314
    }
315
316
    /**
317
     * @param string $iteration_method
318
     * @return array
319
     */
320
    protected function get_file_objects($iteration_method = 'get_file_object')
321
    {
322
        $upload_dir = $this->get_upload_path();
323
        if (!is_dir($upload_dir)) {
324
            return [];
325
        }
326
327
        return array_values(array_filter(array_map([$this, $iteration_method], scandir($upload_dir, SCANDIR_SORT_NONE))));
328
    }
329
330
    /**
331
     * @return int
332
     */
333
    protected function count_file_objects()
334
    {
335
        return count($this->get_file_objects('is_valid_file_object'));
336
    }
337
338
    /**
339
     * @param $file_name
340
     * @param $version
341
     * @param $options
342
     * @return bool
343
     */
344
    protected function create_scaled_image($file_name, $version, $options)
345
    {
346
        $file_path = $this->get_upload_path($file_name);
347
        if (!empty($version)) {
348
            $version_dir = $this->get_upload_path(null, $version);
349 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...
350
                if (!mkdir($version_dir, $this->options['mkdir_mode'], true) && !is_dir($version_dir)) {
351
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $version_dir));
352
                }
353
            }
354
            $new_file_path = $version_dir . '/' . $file_name;
355
        } else {
356
            $new_file_path = $file_path;
357
        }
358
        list($img_width, $img_height) = @getimagesize($file_path);
359
        if (!$img_width || !$img_height) {
360
            return false;
361
        }
362
        $scale = min($options['max_width'] / $img_width, $options['max_height'] / $img_height);
363
        if ($scale >= 1) {
364
            if ($file_path !== $new_file_path) {
365
                return copy($file_path, $new_file_path);
366
            }
367
368
            return true;
369
        }
370
        $new_width  = $img_width * $scale;
371
        $new_height = $img_height * $scale;
372
        $new_img    = @imagecreatetruecolor($new_width, $new_height);
373
        switch (mb_strtolower(mb_substr(mb_strrchr($file_name, '.'), 1))) {
374
            case 'jpg':
375
            case 'jpeg':
376
                $src_img       = @imagecreatefromjpeg($file_path);
377
                $write_image   = 'imagejpeg';
378
                $image_quality = isset($options['jpeg_quality']) ? $options['jpeg_quality'] : 75;
379
                break;
380
            case 'gif':
381
                @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...
382
                $src_img       = @imagecreatefromgif($file_path);
383
                $write_image   = 'imagegif';
384
                $image_quality = null;
385
                break;
386
            case 'png':
387
                @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...
388
                @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...
389
                @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...
390
                $src_img       = @imagecreatefrompng($file_path);
391
                $write_image   = 'imagepng';
392
                $image_quality = isset($options['png_quality']) ? $options['png_quality'] : 9;
393
                break;
394
            default:
395
                $src_img = null;
396
        }
397
        $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...
398
        // Free up memory (imagedestroy does not delete files):
399
        @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...
400
        @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...
401
402
        return $success;
403
    }
404
405
    /**
406
     * @param $error
407
     * @return mixed
408
     */
409
    protected function get_error_message($error)
410
    {
411
        return array_key_exists($error, $this->error_messages) ? $this->error_messages[$error] : $error;
412
    }
413
414
    /**
415
     * @param $val
416
     * @return float|int
417
     */
418
    public function get_config_bytes($val)
419
    {
420
        $val  = trim($val);
421
        $last = mb_strtolower($val[mb_strlen($val) - 1]);
422
        switch ($last) {
423
            case 'g':
424
                $val *= 1024;
425
            // no break
426
            case 'm':
427
                $val *= 1024;
428
            // no break
429
            case 'k':
430
                $val *= 1024;
431
        }
432
433
        return $this->fix_integer_overflow($val);
434
    }
435
436
    /**
437
     * @param $uploaded_file
438
     * @param $file
439
     * @param $error
440
     * @param $index
441
     * @return bool
442
     */
443
    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...
444
    {
445
        if ($error) {
446
            $file->error = $this->get_error_message($error);
447
448
            return false;
449
        }
450
        $content_length = $this->fix_integer_overflow((int)$_SERVER['CONTENT_LENGTH']);
451
        $post_max_size  = $this->get_config_bytes(ini_get('post_max_size'));
452
        if ($post_max_size && ($content_length > $post_max_size)) {
453
            $file->error = $this->get_error_message('post_max_size');
454
455
            return false;
456
        }
457
        if (!preg_match($this->options['accept_file_types'], $file->name)) {
458
            $file->error = $this->get_error_message('accept_file_types');
459
460
            return false;
461
        }
462
        if ($uploaded_file && is_uploaded_file($uploaded_file)) {
463
            $file_size = $this->get_file_size($uploaded_file);
464
        } else {
465
            $file_size = $content_length;
466
        }
467
        if ($this->options['max_file_size'] && ($file_size > $this->options['max_file_size'] || $file->size > $this->options['max_file_size'])) {
468
            $file->error = $this->get_error_message('max_file_size');
469
470
            return false;
471
        }
472
        if ($this->options['min_file_size'] && $file_size < $this->options['min_file_size']) {
473
            $file->error = $this->get_error_message('min_file_size');
474
475
            return false;
476
        }
477
        if (is_int($this->options['max_number_of_files']) && ($this->count_file_objects() >= $this->options['max_number_of_files'])) {
478
            $file->error = $this->get_error_message('max_number_of_files');
479
480
            return false;
481
        }
482
        list($img_width, $img_height) = @getimagesize($uploaded_file);
483
        if (is_int($img_width)) {
484 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...
485
                $file->error = $this->get_error_message('max_width');
486
487
                return false;
488
            }
489 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...
490
                $file->error = $this->get_error_message('max_height');
491
492
                return false;
493
            }
494 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...
495
                $file->error = $this->get_error_message('min_width');
496
497
                return false;
498
            }
499 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...
500
                $file->error = $this->get_error_message('min_height');
501
502
                return false;
503
            }
504
        }
505
506
        return true;
507
    }
508
509
    /**
510
     * @param $matches
511
     * @return string
512
     */
513
    protected function upcount_name_callback($matches)
514
    {
515
        $index = isset($matches[1]) ? (int)$matches[1] + 1 : 1;
516
        $ext   = isset($matches[2]) ? $matches[2] : '';
517
518
        return ' (' . $index . ')' . $ext;
519
    }
520
521
    /**
522
     * @param $name
523
     * @return null|string|string[]
524
     */
525
    protected function upcount_name($name)
526
    {
527
        return preg_replace_callback('/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', [$this, 'upcount_name_callback'], $name, 1);
528
    }
529
530
    /**
531
     * @param $name
532
     * @param $type
533
     * @param $index
534
     * @param $content_range
535
     * @return null|string|string[]
536
     */
537
    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...
538
    {
539
        while (is_dir($this->get_upload_path($name))) {
540
            $name = $this->upcount_name($name);
541
        }
542
        // Keep an existing filename if this is part of a chunked upload:
543
        $uploaded_bytes = $this->fix_integer_overflow((int)$content_range[1]);
544
        while (is_file($this->get_upload_path($name))) {
545
            if ($uploaded_bytes === $this->get_file_size($this->get_upload_path($name))) {
546
                break;
547
            }
548
            $name = $this->upcount_name($name);
549
        }
550
551
        return $name;
552
    }
553
554
    /**
555
     * @param $name
556
     * @param $type
557
     * @param $index
558
     * @param $content_range
559
     * @return mixed|string
560
     */
561
    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...
562
    {
563
        // Remove path information and dots around the filename, to prevent uploading
564
        // into different directories or replacing hidden system files.
565
        // Also remove control characters and spaces (\x00..\x20) around the filename:
566
        $name = trim(basename(stripslashes($name)), ".\x00..\x20");
567
        // Use a timestamp for empty filenames:
568
        if (!$name) {
569
            $name = str_replace('.', '-', microtime(true));
570
        }
571
        // Add missing file extension for known image types:
572
        if (false === mb_strpos($name, '.') && preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
573
            $name .= '.' . $matches[1];
574
        }
575
576
        return $name;
577
    }
578
579
    /**
580
     * @param $name
581
     * @param $type
582
     * @param $index
583
     * @param $content_range
584
     * @return null|string|string[]
585
     */
586
    protected function get_file_name($name, $type, $index, $content_range)
587
    {
588
        return $this->get_unique_filename($this->trim_file_name($name, $type, $index, $content_range), $type, $index, $content_range);
589
    }
590
591
    /**
592
     * @param $file
593
     * @param $index
594
     */
595
    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...
596
    {
597
        // Handle form data, e.g. $_REQUEST['description'][$index]
598
    }
599
600
    /**
601
     * @param $file_path
602
     * @return bool
603
     */
604
    protected function orient_image($file_path)
605
    {
606
        if (!function_exists('exif_read_data')) {
607
            return false;
608
        }
609
        $exif = @exif_read_data($file_path);
610
        if (false === $exif) {
611
            return false;
612
        }
613
        $orientation = (int)(@$exif['Orientation']);
614
        if (!in_array($orientation, [3, 6, 8])) {
615
            return false;
616
        }
617
        $image = @imagecreatefromjpeg($file_path);
618
        switch ($orientation) {
619
            case 3:
620
                $image = @imagerotate($image, 180, 0);
621
                break;
622
            case 6:
623
                $image = @imagerotate($image, 270, 0);
624
                break;
625
            case 8:
626
                $image = @imagerotate($image, 90, 0);
627
                break;
628
            default:
629
                return false;
630
        }
631
        $success = imagejpeg($image, $file_path);
632
        // Free up memory (imagedestroy does not delete files):
633
        @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...
634
635
        return $success;
636
    }
637
638
    /**
639
     * @param      $uploaded_file
640
     * @param      $name
641
     * @param      $size
642
     * @param      $type
643
     * @param      $error
644
     * @param null $index
645
     * @param null $content_range
646
     * @return \stdClass
647
     */
648
    protected function handle_file_upload(
649
        $uploaded_file,
650
        $name,
651
        $size,
652
        $type,
653
        $error,
654
        $index = null,
655
        $content_range = null
656
    )
657
    {
658
        $file = new \stdClass();
659
660
        $file->name = $this->get_file_name($name, $type, $index, $content_range);
661
        $file->size = $this->fix_integer_overflow((int)$size);
662
        $file->type = $type;
663
664
        // Save to database for later use
665
        $swDB   = new SwDatabase();
666
        $userid = $GLOBALS['xoopsUser']->getVar('uid');
667
668
        // Generate new name for file
669
        $file->name = basename(stripslashes($name));
670
        $file->name = time() . mt_rand(0, 99999) . '.' . $this->getFileExtension($name);
671
        $img        = XOOPS_URL . '/uploads/albums_smallworld/' . $userid . '/' . $file->name;
672
        $swDB->saveImage("'', '" . $userid . "', '" . $file->name . "', '" . addslashes($img) . "', '" . time() . "', ''");
673
674
        if ($this->validate($uploaded_file, $file, $error, $index)) {
675
            $this->handle_form_data($file, $index);
676
            $upload_dir = $this->get_upload_path();
677 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...
678
                if (!mkdir($upload_dir, $this->options['mkdir_mode'], true) && !is_dir($upload_dir)) {
679
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $upload_dir));
680
                }
681
            }
682
            $file_path   = $this->get_upload_path($file->name);
683
            $append_file = $content_range && is_file($file_path) && $file->size > $this->get_file_size($file_path);
684
            if ($uploaded_file && is_uploaded_file($uploaded_file)) {
685
                // multipart/formdata uploads (POST method uploads)
686
                if ($append_file) {
687
                    file_put_contents($file_path, fopen($uploaded_file, 'rb'), FILE_APPEND);
688
                } else {
689
                    move_uploaded_file($uploaded_file, $file_path);
690
                }
691
            } else {
692
                // Non-multipart uploads (PUT method support)
693
                file_put_contents($file_path, fopen('php://input', 'rb'), $append_file ? FILE_APPEND : 0);
694
            }
695
            $file_size = $this->get_file_size($file_path, $append_file);
696
            if ($file_size === $file->size) {
697
                if ($this->options['orient_image']) {
698
                    $this->orient_image($file_path);
699
                }
700
                $file->url = $this->get_download_url($file->name);
701
                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...
702
                    if ($this->create_scaled_image($file->name, $version, $options)) {
703
                        if (!empty($version)) {
704
                            $file->{$version . '_url'} = $this->get_download_url($file->name, $version);
705
                        } else {
706
                            $file_size = $this->get_file_size($file_path, true);
707
                        }
708
                    }
709
                }
710
            } elseif (!$content_range && $this->options['discard_aborted_uploads']) {
711
                unlink($file_path);
712
                $file->error = 'abort';
713
            }
714
            $file->size = $file_size;
715
            $this->set_file_delete_properties($file);
716
        }
717
718
        return $file;
719
    }
720
721
    //function to return file extension from a path or file name
722
723
    /**
724
     * @param $path
725
     * @return mixed
726
     */
727
    public function getFileExtension($path)
728
    {
729
        $parts = pathinfo($path);
730
731
        return $parts['extension'];
732
    }
733
734
    /**
735
     * @param $file_path
736
     * @return false|int
737
     */
738
    protected function readfile($file_path)
739
    {
740
        return readfile($file_path);
741
    }
742
743
    /**
744
     * @param $str
745
     */
746
    protected function body($str)
747
    {
748
        echo $str;
749
    }
750
751
    /**
752
     * @param $str
753
     */
754
    protected function header($str)
755
    {
756
        header($str);
757
    }
758
759
    /**
760
     * @param      $content
761
     * @param bool $print_response
762
     */
763
    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...
764
    {
765
        if ($print_response) {
766
            $json     = json_encode($content);
767
            $redirect = isset($_REQUEST['redirect']) ? stripslashes($_REQUEST['redirect']) : null;
768
            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...
769
                $this->header('Location: ' . sprintf($redirect, rawurlencode($json)));
770
771
                return;
772
            }
773
            $this->head();
774
            if (isset($_SERVER['HTTP_CONTENT_RANGE'])) {
775
                $files = isset($content[$this->options['param_name']]) ? $content[$this->options['param_name']] : null;
776
                if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) {
777
                    $this->header('Range: 0-' . ($this->fix_integer_overflow((int)$files[0]->size) - 1));
778
                }
779
            }
780
            $this->body($json);
781
        }
782
783
        return $content;
784
    }
785
786
    /**
787
     * @return null|string
788
     */
789
    protected function get_version_param()
790
    {
791
        return isset($_GET['version']) ? basename(stripslashes($_GET['version'])) : null;
792
    }
793
794
    /**
795
     * @return null|string
796
     */
797
    protected function get_file_name_param()
798
    {
799
        return isset($_GET['file']) ? basename(stripslashes($_GET['file'])) : null;
800
    }
801
802
    /**
803
     * @param $file_path
804
     * @return string
805
     */
806
    protected function get_file_type($file_path)
807
    {
808
        switch (mb_strtolower(pathinfo($file_path, PATHINFO_EXTENSION))) {
809
            case 'jpeg':
810
            case 'jpg':
811
                return 'image/jpeg';
812
            case 'png':
813
                return 'image/png';
814
            case 'gif':
815
                return 'image/gif';
816
            default:
817
                return '';
818
        }
819
    }
820
821
    protected function download()
822
    {
823
        if (!$this->options['download_via_php']) {
824
            $this->header('HTTP/1.1 403 Forbidden');
825
826
            return;
827
        }
828
        $file_name = $this->get_file_name_param();
829
        if ($this->is_valid_file_object($file_name)) {
830
            $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...
831
            if (is_file($file_path)) {
832
                if (!preg_match($this->options['inline_file_types'], $file_name)) {
833
                    $this->header('Content-Description: File Transfer');
834
                    $this->header('Content-Type: application/octet-stream');
835
                    $this->header('Content-Disposition: attachment; filename="' . $file_name . '"');
836
                    $this->header('Content-Transfer-Encoding: binary');
837
                } else {
838
                    // Prevent Internet Explorer from MIME-sniffing the content-type:
839
                    $this->header('X-Content-Type-Options: nosniff');
840
                    $this->header('Content-Type: ' . $this->get_file_type($file_path));
841
                    $this->header('Content-Disposition: inline; filename="' . $file_name . '"');
842
                }
843
                $this->header('Content-Length: ' . $this->get_file_size($file_path));
844
                $this->header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($file_path)));
845
                $this->readfile($file_path);
846
            }
847
        }
848
    }
849
850
    protected function send_content_type_header()
851
    {
852
        $this->header('Vary: Accept');
853
        if (isset($_SERVER['HTTP_ACCEPT']) && (false !== mb_strpos($_SERVER['HTTP_ACCEPT'], 'application/json'))) {
854
            $this->header('Content-type: application/json');
855
        } else {
856
            $this->header('Content-type: text/plain');
857
        }
858
    }
859
860
    protected function send_access_control_headers()
861
    {
862
        $this->header('Access-Control-Allow-Origin: ' . $this->options['access_control_allow_origin']);
863
        $this->header('Access-Control-Allow-Credentials: ' . ($this->options['access_control_allow_credentials'] ? 'true' : 'false'));
864
        $this->header('Access-Control-Allow-Methods: ' . implode(', ', $this->options['access_control_allow_methods']));
865
        $this->header('Access-Control-Allow-Headers: ' . implode(', ', $this->options['access_control_allow_headers']));
866
    }
867
868
    public function head()
869
    {
870
        $this->header('Pragma: no-cache');
871
        $this->header('Cache-Control: no-store, no-cache, must-revalidate');
872
        $this->header('Content-Disposition: inline; filename="files.json"');
873
        // Prevent Internet Explorer from MIME-sniffing the content-type:
874
        $this->header('X-Content-Type-Options: nosniff');
875
        if ($this->options['access_control_allow_origin']) {
876
            $this->send_access_control_headers();
877
        }
878
        $this->send_content_type_header();
879
    }
880
881
    /**
882
     * @param bool $print_response
883
     */
884
    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...
885
    {
886
        if ($print_response && isset($_GET['download'])) {
887
            return $this->download();
888
        }
889
        $file_name = $this->get_file_name_param();
890
        if (null !== $file_name) {
891
            $response = [
892
                mb_substr($this->options['param_name'], 0, -1) => $this->get_file_object($file_name),
893
            ];
894
        } else {
895
            $response = [
896
                $this->options['param_name'] => $this->get_file_objects(),
897
            ];
898
        }
899
900
        return $this->generate_response($response, $print_response);
901
    }
902
903
    /**
904
     * @param bool $print_response
905
     */
906
    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...
907
    {
908
        if (isset($_REQUEST['_method']) && 'DELETE' === $_REQUEST['_method']) {
909
            return $this->delete($print_response);
910
        }
911
        $upload = isset($_FILES[$this->options['param_name']]) ? $_FILES[$this->options['param_name']] : null;
912
        // Parse the Content-Disposition header, if available:
913
        $file_name = isset($_SERVER['HTTP_CONTENT_DISPOSITION']) ? rawurldecode(preg_replace('/(^[^"]+")|("$)/', '', $_SERVER['HTTP_CONTENT_DISPOSITION'])) : null;
914
        // Parse the Content-Range header, which has the following form:
915
        // Content-Range: bytes 0-524287/2000000
916
        $content_range = isset($_SERVER['HTTP_CONTENT_RANGE']) ? preg_split('/[^0-9]+/', $_SERVER['HTTP_CONTENT_RANGE']) : null;
917
        $size          = $content_range ? $content_range[3] : null;
918
        $files         = [];
919
        if ($upload && is_array($upload['tmp_name'])) {
920
            // param_name is an array identifier like "files[]",
921
            // $_FILES is a multi-dimensional array:
922
            foreach ($upload['tmp_name'] as $index => $value) {
923
                $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 916 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...
924
            }
925
        } else {
926
            // param_name is a single object identifier like "file",
927
            // $_FILES is a one-dimensional array:
928
            $files[] = $this->handle_file_upload(
929
                isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
930
                $file_name ?: (isset($upload['name']) ? $upload['name'] : null),
931
                $size ?: (isset($upload['size']) ? $upload['size'] : $_SERVER['CONTENT_LENGTH']),
932
                isset($upload['type']) ? $upload['type'] : $_SERVER['CONTENT_TYPE'],
933
                isset($upload['error']) ? $upload['error'] : null,
934
                null,
935
                $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 916 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...
936
            );
937
        }
938
939
        return $this->generate_response([$this->options['param_name'] => $files], $print_response);
940
    }
941
942
    /**
943
     * @param bool $print_response
944
     */
945
    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...
946
    {
947
        $userid    = $GLOBALS['xoopsUser']->getVar('uid');
948
        $swDB      = new SwDatabase();
949
        $file_name = $this->get_file_name_param();
950
        $file_path = $this->get_upload_path($file_name);
951
        $success   = is_file($file_path) && '.' !== $file_name[0] && unlink($file_path);
952
953
        // Delete file based on user and filename
954
        $swDB->deleteImage($userid, $file_name);
955
        $swDB->deleteImage($userid, 'Thumbs.db');
956
957
        if ($success) {
958
            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...
959
                if (!empty($version)) {
960
                    $file = $this->get_upload_path($file_name, $version);
961
                    if (is_file($file)) {
962
                        unlink($file);
963
                    }
964
                }
965
            }
966
        }
967
968
        return $this->generate_response(['success' => $success], $print_response);
969
    }
970
}
971