Completed
Push — master ( 9a4ef0...878baf )
by
unknown
01:47
created

UploadHandler   F

Complexity

Total Complexity 198

Size/Duplication

Total Lines 953
Duplicated Lines 3.15 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 30
loc 953
rs 1.647
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 73 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("null, '" . $userid . "', '" . $file->name . "', '" . addslashes($img) . "', '" . time() . "', ''");
673
		//$swDB->saveImage("'1', '" . $userid . "', '" . $file->name . "', '" . addslashes($img) . "', '" . time() . "', '1'");
674
		
675
        if ($this->validate($uploaded_file, $file, $error, $index)) {
676
            $this->handle_form_data($file, $index);
677
            $upload_dir = $this->get_upload_path();
678 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...
679
                if (!mkdir($upload_dir, $this->options['mkdir_mode'], true) && !is_dir($upload_dir)) {
680
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $upload_dir));
681
                }
682
            }
683
            $file_path   = $this->get_upload_path($file->name);
684
            $append_file = $content_range && is_file($file_path) && $file->size > $this->get_file_size($file_path);
685
            if ($uploaded_file && is_uploaded_file($uploaded_file)) {
686
                // multipart/formdata uploads (POST method uploads)
687
                if ($append_file) {
688
                    file_put_contents($file_path, fopen($uploaded_file, 'rb'), FILE_APPEND);
689
                } else {
690
                    move_uploaded_file($uploaded_file, $file_path);
691
                }
692
            } else {
693
                // Non-multipart uploads (PUT method support)
694
                file_put_contents($file_path, fopen('php://input', 'rb'), $append_file ? FILE_APPEND : 0);
695
            }
696
            $file_size = $this->get_file_size($file_path, $append_file);
697
            if ($file_size === $file->size) {
698
                if ($this->options['orient_image']) {
699
                    $this->orient_image($file_path);
700
                }
701
                $file->url = $this->get_download_url($file->name);
702
                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...
703
                    if ($this->create_scaled_image($file->name, $version, $options)) {
704
                        if (!empty($version)) {
705
                            $file->{$version . '_url'} = $this->get_download_url($file->name, $version);
706
                        } else {
707
                            $file_size = $this->get_file_size($file_path, true);
708
                        }
709
                    }
710
                }
711
            } elseif (!$content_range && $this->options['discard_aborted_uploads']) {
712
                unlink($file_path);
713
                $file->error = 'abort';
714
            }
715
            $file->size = $file_size;
716
            $this->set_file_delete_properties($file);
717
        }
718
719
        return $file;
720
    }
721
722
    //function to return file extension from a path or file name
723
724
    /**
725
     * @param $path
726
     * @return mixed
727
     */
728
    public function getFileExtension($path)
729
    {
730
        $parts = pathinfo($path);
731
732
        return $parts['extension'];
733
    }
734
735
    /**
736
     * @param $file_path
737
     * @return false|int
738
     */
739
    protected function readfile($file_path)
740
    {
741
        return readfile($file_path);
742
    }
743
744
    /**
745
     * @param $str
746
     */
747
    protected function body($str)
748
    {
749
        echo $str;
750
    }
751
752
    /**
753
     * @param $str
754
     */
755
    protected function header($str)
756
    {
757
        header($str);
758
    }
759
760
    /**
761
     * @param      $content
762
     * @param bool $print_response
763
     */
764
    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...
765
    {
766
        if ($print_response) {
767
            $json     = json_encode($content);
768
            $redirect = isset($_REQUEST['redirect']) ? stripslashes($_REQUEST['redirect']) : null;
769
            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...
770
                $this->header('Location: ' . sprintf($redirect, rawurlencode($json)));
771
772
                return;
773
            }
774
            $this->head();
775
            if (isset($_SERVER['HTTP_CONTENT_RANGE'])) {
776
                $files = isset($content[$this->options['param_name']]) ? $content[$this->options['param_name']] : null;
777
                if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) {
778
                    $this->header('Range: 0-' . ($this->fix_integer_overflow((int)$files[0]->size) - 1));
779
                }
780
            }
781
            $this->body($json);
782
        }
783
784
        return $content;
785
    }
786
787
    /**
788
     * @return null|string
789
     */
790
    protected function get_version_param()
791
    {
792
        return isset($_GET['version']) ? basename(stripslashes($_GET['version'])) : null;
793
    }
794
795
    /**
796
     * @return null|string
797
     */
798
    protected function get_file_name_param()
799
    {
800
        return isset($_GET['file']) ? basename(stripslashes($_GET['file'])) : null;
801
    }
802
803
    /**
804
     * @param $file_path
805
     * @return string
806
     */
807
    protected function get_file_type($file_path)
808
    {
809
        switch (mb_strtolower(pathinfo($file_path, PATHINFO_EXTENSION))) {
810
            case 'jpeg':
811
            case 'jpg':
812
                return 'image/jpeg';
813
            case 'png':
814
                return 'image/png';
815
            case 'gif':
816
                return 'image/gif';
817
            default:
818
                return '';
819
        }
820
    }
821
822
    protected function download()
823
    {
824
        if (!$this->options['download_via_php']) {
825
            $this->header('HTTP/1.1 403 Forbidden');
826
827
            return;
828
        }
829
        $file_name = $this->get_file_name_param();
830
        if ($this->is_valid_file_object($file_name)) {
831
            $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...
832
            if (is_file($file_path)) {
833
                if (!preg_match($this->options['inline_file_types'], $file_name)) {
834
                    $this->header('Content-Description: File Transfer');
835
                    $this->header('Content-Type: application/octet-stream');
836
                    $this->header('Content-Disposition: attachment; filename="' . $file_name . '"');
837
                    $this->header('Content-Transfer-Encoding: binary');
838
                } else {
839
                    // Prevent Internet Explorer from MIME-sniffing the content-type:
840
                    $this->header('X-Content-Type-Options: nosniff');
841
                    $this->header('Content-Type: ' . $this->get_file_type($file_path));
842
                    $this->header('Content-Disposition: inline; filename="' . $file_name . '"');
843
                }
844
                $this->header('Content-Length: ' . $this->get_file_size($file_path));
845
                $this->header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($file_path)));
846
                $this->readfile($file_path);
847
            }
848
        }
849
    }
850
851
    protected function send_content_type_header()
852
    {
853
        $this->header('Vary: Accept');
854
        if (isset($_SERVER['HTTP_ACCEPT']) && (false !== mb_strpos($_SERVER['HTTP_ACCEPT'], 'application/json'))) {
855
            $this->header('Content-type: application/json');
856
        } else {
857
            $this->header('Content-type: text/plain');
858
        }
859
    }
860
861
    protected function send_access_control_headers()
862
    {
863
        $this->header('Access-Control-Allow-Origin: ' . $this->options['access_control_allow_origin']);
864
        $this->header('Access-Control-Allow-Credentials: ' . ($this->options['access_control_allow_credentials'] ? 'true' : 'false'));
865
        $this->header('Access-Control-Allow-Methods: ' . implode(', ', $this->options['access_control_allow_methods']));
866
        $this->header('Access-Control-Allow-Headers: ' . implode(', ', $this->options['access_control_allow_headers']));
867
    }
868
869
    public function head()
870
    {
871
        $this->header('Pragma: no-cache');
872
        $this->header('Cache-Control: no-store, no-cache, must-revalidate');
873
        $this->header('Content-Disposition: inline; filename="files.json"');
874
        // Prevent Internet Explorer from MIME-sniffing the content-type:
875
        $this->header('X-Content-Type-Options: nosniff');
876
        if ($this->options['access_control_allow_origin']) {
877
            $this->send_access_control_headers();
878
        }
879
        $this->send_content_type_header();
880
    }
881
882
    /**
883
     * @param bool $print_response
884
     */
885
    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...
886
    {
887
        if ($print_response && isset($_GET['download'])) {
888
            return $this->download();
889
        }
890
        $file_name = $this->get_file_name_param();
891
        if (null !== $file_name) {
892
            $response = [
893
                mb_substr($this->options['param_name'], 0, -1) => $this->get_file_object($file_name),
894
            ];
895
        } else {
896
            $response = [
897
                $this->options['param_name'] => $this->get_file_objects(),
898
            ];
899
        }
900
901
        return $this->generate_response($response, $print_response);
902
    }
903
904
    /**
905
     * @param bool $print_response
906
     */
907
    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...
908
    {
909
        if (isset($_REQUEST['_method']) && 'DELETE' === $_REQUEST['_method']) {
910
            return $this->delete($print_response);
911
        }
912
        $upload = isset($_FILES[$this->options['param_name']]) ? $_FILES[$this->options['param_name']] : null;
913
        // Parse the Content-Disposition header, if available:
914
        $file_name = isset($_SERVER['HTTP_CONTENT_DISPOSITION']) ? rawurldecode(preg_replace('/(^[^"]+")|("$)/', '', $_SERVER['HTTP_CONTENT_DISPOSITION'])) : null;
915
        // Parse the Content-Range header, which has the following form:
916
        // Content-Range: bytes 0-524287/2000000
917
        $content_range = isset($_SERVER['HTTP_CONTENT_RANGE']) ? preg_split('/[^0-9]+/', $_SERVER['HTTP_CONTENT_RANGE']) : null;
918
        $size          = $content_range ? $content_range[3] : null;
919
        $files         = [];
920
        if ($upload && is_array($upload['tmp_name'])) {
921
            // param_name is an array identifier like "files[]",
922
            // $_FILES is a multi-dimensional array:
923
            foreach ($upload['tmp_name'] as $index => $value) {
924
                $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 917 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...
925
            }
926
        } else {
927
            // param_name is a single object identifier like "file",
928
            // $_FILES is a one-dimensional array:
929
            $files[] = $this->handle_file_upload(
930
                isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
931
                $file_name ?: (isset($upload['name']) ? $upload['name'] : null),
932
                $size ?: (isset($upload['size']) ? $upload['size'] : $_SERVER['CONTENT_LENGTH']),
933
                isset($upload['type']) ? $upload['type'] : $_SERVER['CONTENT_TYPE'],
934
                isset($upload['error']) ? $upload['error'] : null,
935
                null,
936
                $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 917 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...
937
            );
938
        }
939
940
        return $this->generate_response([$this->options['param_name'] => $files], $print_response);
941
    }
942
943
    /**
944
     * @param bool $print_response
945
     */
946
    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...
947
    {
948
        $userid    = $GLOBALS['xoopsUser']->getVar('uid');
949
        $swDB      = new SwDatabase();
950
        $file_name = $this->get_file_name_param();
951
        $file_path = $this->get_upload_path($file_name);
952
        $success   = is_file($file_path) && '.' !== $file_name[0] && unlink($file_path);
953
954
        // Delete file based on user and filename
955
        $swDB->deleteImage($userid, $file_name);
956
        $swDB->deleteImage($userid, 'Thumbs.db');
957
958
        if ($success) {
959
            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...
960
                if (!empty($version)) {
961
                    $file = $this->get_upload_path($file_name, $version);
962
                    if (is_file($file)) {
963
                        unlink($file);
964
                    }
965
                }
966
            }
967
        }
968
969
        return $this->generate_response(['success' => $success], $print_response);
970
    }
971
}
972