Completed
Pull Request — master (#5)
by Michael
01:37
created

UploadHandler::count_file_objects()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
    public    $xoopsUser;
22
    protected $options;
23
24
    // PHP File Upload error message codes:
25
    // http://php.net/manual/en/features.file-upload.errors.php
26
    protected $error_messages = [
27
        1                     => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
28
        2                     => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
29
        3                     => 'The uploaded file was only partially uploaded',
30
        4                     => 'No file was uploaded',
31
        6                     => 'Missing a temporary folder',
32
        7                     => 'Failed to write file to disk',
33
        8                     => 'A PHP extension stopped the file upload',
34
        'post_max_size'       => 'The uploaded file exceeds the post_max_size directive in php.ini',
35
        'max_file_size'       => 'File is too big',
36
        'min_file_size'       => 'File is too small',
37
        'accept_file_types'   => 'Filetype not allowed',
38
        'max_number_of_files' => 'Maximum number of files exceeded',
39
        'max_width'           => 'Image exceeds maximum width',
40
        'min_width'           => 'Image requires a minimum width',
41
        'max_height'          => 'Image exceeds maximum height',
42
        'min_height'          => 'Image requires a minimum height',
43
    ];
44
45
    /**
46
     * UploadHandler constructor.
47
     * @param null $options
48
     * @param bool $initialize
49
     */
50
    public function __construct($options = null, $initialize = true)
51
    {
52
        global $xoopsUser;
53
        $userID        = $xoopsUser->getVar('uid');
54
        $this->options = [
55
            'script_url'                       => $this->get_full_url() . '/imgupload.php',
56
            'upload_dir'                       => XOOPS_ROOT_PATH . '/uploads/albums_smallworld/' . $userID . '/',
57
            'upload_url'                       => XOOPS_URL . '/uploads/albums_smallworld/' . $userID . '/',
58
            'user_dirs'                        => false,
59
            'mkdir_mode'                       => 0755,
60
            'param_name'                       => 'files',
61
            // Set the following option to 'POST', if your server does not support
62
            // DELETE requests. This is a parameter sent to the client:
63
            'delete_type'                      => 'DELETE',
64
            'access_control_allow_origin'      => '*',
65
            'access_control_allow_credentials' => false,
66
            'access_control_allow_methods'     => [
67
                'OPTIONS',
68
                'HEAD',
69
                'GET',
70
                'POST',
71
                'PUT',
72
                'PATCH',
73
                'DELETE',
74
            ],
75
            'access_control_allow_headers'     => [
76
                'Content-Type',
77
                'Content-Range',
78
                'Content-Disposition',
79
            ],
80
            // Enable to provide file downloads via GET requests to the PHP script:
81
            'download_via_php'                 => false,
82
            // Defines which files can be displayed inline when downloaded:
83
            'inline_file_types'                => '/\.(gif|jpe?g|png|JPE?G)$/i',
84
            // Defines which files (based on their names) are accepted for upload:
85
            'accept_file_types'                => '/.+$/i',
86
            // The php.ini settings upload_max_filesize and post_max_size
87
            // take precedence over the following max_file_size setting:
88
            'max_file_size'                    => null,
89
            'min_file_size'                    => 1,
90
            // The maximum number of files for the upload directory:
91
            'max_number_of_files'              => null,
92
            // Image resolution restrictions:
93
            'max_width'                        => null,
94
            'max_height'                       => null,
95
            'min_width'                        => 1,
96
            'min_height'                       => 1,
97
            // Set the following option to false to enable resumable uploads:
98
            'discard_aborted_uploads'          => true,
99
            // Set to true to rotate images based on EXIF meta data, if available:
100
            'orient_image'                     => false,
101
            'image_versions'                   => [
102
                // Uncomment the following version to restrict the size of
103
                // uploaded images:
104
                /*
105
                '' => array(
106
                    'max_width' => 1920,
107
                    'max_height' => 1200,
108
                    'jpeg_quality' => 95
109
                ),
110
                */
111
                // Uncomment the following to create medium sized images:
112
                /*
113
                'medium' => array(
114
                    'max_width' => 800,
115
                    'max_height' => 600,
116
                    'jpeg_quality' => 80
117
                ),
118
                */
119
                'thumbnails' => [
120
                    'upload_dir' => XOOPS_ROOT_PATH . '/uploads/albums_smallworld' . '/' . $userID . '/thumbnails/',
121
                    'upload_url' => XOOPS_URL . '/uploads/albums_smallworld' . '/' . $userID . '/thumbnails/',
122
                    'max_width'  => 80,
123
                    'max_height' => 80,
124
                ],
125
            ],
126
        ];
127
        if ($options) {
128
            $this->options = array_merge($this->options, $options);
129
        }
130
        if ($initialize) {
131
            $this->initialize();
132
        }
133
    }
134
135
    protected function initialize()
136
    {
137
        switch ($_SERVER['REQUEST_METHOD']) {
138
            case 'OPTIONS':
139
            case 'HEAD':
140
                $this->head();
141
                break;
142
            case 'GET':
143
                $this->get();
144
                break;
145
            case 'PATCH':
146
            case 'PUT':
147
            case 'POST':
148
                $this->post();
149
                break;
150
            case 'DELETE':
151
                $this->delete();
152
                break;
153
            default:
154
                $this->header('HTTP/1.1 405 Method Not Allowed');
155
        }
156
    }
157
158
    /**
159
     * @return string
160
     */
161
    protected function get_full_url()
162
    {
163
        $https = !empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS'];
164
165
        return ($https ? 'https://' : 'http://')
166
               . (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] . '@' : '')
167
               . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'] . ($https && 443 === $_SERVER['SERVER_PORT'] || 80 === $_SERVER['SERVER_PORT'] ? '' : ':'
168
                                                                                                                                                                                        . $_SERVER['SERVER_PORT'])))
169
               . mb_substr($_SERVER['SCRIPT_NAME'], 0, mb_strrpos($_SERVER['SCRIPT_NAME'], '/'));
170
    }
171
172
    /**
173
     * @return string
174
     */
175
    protected function get_user_id()
176
    {
177
        @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...
178
179
        return session_id();
180
    }
181
182
    /**
183
     * @return string
184
     */
185
    protected function get_user_path()
186
    {
187
        if ($this->options['user_dirs']) {
188
            return $this->get_user_id() . '/';
189
        }
190
191
        return '';
192
    }
193
194
    /**
195
     * @param null|string $file_name
196
     * @param null        $version
197
     * @return string
198
     */
199
    protected function get_upload_path($file_name = null, $version = null)
200
    {
201
        $file_name    = $file_name ?: '';
202
        $version_path = empty($version) ? '' : $version . '/';
203
204
        return $this->options['upload_dir'] . $this->get_user_path() . $version_path . $file_name;
205
    }
206
207
    /**
208
     * @param $url
209
     * @return string
210
     */
211
    protected function get_query_separator($url)
212
    {
213
        return false === mb_strpos($url, '?') ? '?' : '&';
214
    }
215
216
    /**
217
     * @param      $file_name
218
     * @param null $version
219
     * @return string
220
     */
221
    protected function get_download_url($file_name, $version = null)
222
    {
223
        if ($this->options['download_via_php']) {
224
            $url = $this->options['script_url'] . $this->get_query_separator($this->options['script_url']) . 'file=' . rawurlencode($file_name);
225
            if ($version) {
226
                $url .= '&version=' . rawurlencode($version);
227
            }
228
229
            return $url . '&download=1';
230
        }
231
        $version_path = empty($version) ? '' : rawurlencode($version) . '/';
232
233
        return $this->options['upload_url'] . $this->get_user_path() . $version_path . rawurlencode($file_name);
234
    }
235
236
    /**
237
     * @param $file
238
     */
239
    protected function set_file_delete_properties($file)
240
    {
241
        $file->delete_url  = $this->options['script_url'] . $this->get_query_separator($this->options['script_url']) . 'file=' . rawurlencode($file->name);
242
        $file->delete_type = $this->options['delete_type'];
243
        if ('DELETE' !== $file->delete_type) {
244
            $file->delete_url .= '&_method=DELETE';
245
        }
246
        if ($this->options['access_control_allow_credentials']) {
247
            $file->delete_with_credentials = true;
248
        }
249
    }
250
251
    // Fix for overflowing signed 32 bit integers,
252
    // works for sizes up to 2^32-1 bytes (4 GiB - 1):
253
254
    /**
255
     * @param $size
256
     * @return float|int
257
     */
258
    protected function fix_integer_overflow($size)
259
    {
260
        if ($size < 0) {
261
            $size += 2.0 * (PHP_INT_MAX + 1);
262
        }
263
264
        return $size;
265
    }
266
267
    /**
268
     * @param      $file_path
269
     * @param bool $clear_stat_cache
270
     * @return float|int
271
     */
272
    protected function get_file_size($file_path, $clear_stat_cache = false)
273
    {
274
        if ($clear_stat_cache) {
275
            clearstatcache(true, $file_path);
276
        }
277
278
        return $this->fix_integer_overflow(filesize($file_path));
279
    }
280
281
    /**
282
     * @param $file_name
283
     * @return bool
284
     */
285
    protected function is_valid_file_object($file_name)
286
    {
287
        $file_path = $this->get_upload_path($file_name);
288
289
        return is_file($file_path) && '.' !== $file_name[0];
290
    }
291
292
    /**
293
     * @param $file_name
294
     * @return null|\stdClass
295
     */
296
    protected function get_file_object($file_name)
297
    {
298
        if ($this->is_valid_file_object($file_name)) {
299
            $file       = new \stdClass();
300
            $file->name = $file_name;
301
            $file->size = $this->get_file_size($this->get_upload_path($file_name));
302
            $file->url  = $this->get_download_url($file->name);
303
            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...
304
                if (!empty($version)) {
305
                    if (is_file($this->get_upload_path($file_name, $version))) {
306
                        $file->{$version . '_url'} = $this->get_download_url($file->name, $version);
307
                    }
308
                }
309
            }
310
            $this->set_file_delete_properties($file);
311
312
            return $file;
313
        }
314
315
        return null;
316
    }
317
318
    /**
319
     * @param string $iteration_method
320
     * @return array
321
     */
322
    protected function get_file_objects($iteration_method = 'get_file_object')
323
    {
324
        $upload_dir = $this->get_upload_path();
325
        if (!is_dir($upload_dir)) {
326
            return [];
327
        }
328
329
        return array_values(array_filter(array_map([$this, $iteration_method], scandir($upload_dir, SCANDIR_SORT_NONE))));
330
    }
331
332
    /**
333
     * @return int
334
     */
335
    protected function count_file_objects()
336
    {
337
        return count($this->get_file_objects('is_valid_file_object'));
338
    }
339
340
    /**
341
     * @param $file_name
342
     * @param $version
343
     * @param $options
344
     * @return bool
345
     */
346
    protected function create_scaled_image($file_name, $version, $options)
347
    {
348
        $file_path = $this->get_upload_path($file_name);
349
        if (!empty($version)) {
350
            $version_dir = $this->get_upload_path(null, $version);
351 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...
352
                if (!mkdir($version_dir, $this->options['mkdir_mode'], true) && !is_dir($version_dir)) {
353
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $version_dir));
354
                }
355
            }
356
            $new_file_path = $version_dir . '/' . $file_name;
357
        } else {
358
            $new_file_path = $file_path;
359
        }
360
        list($img_width, $img_height) = @getimagesize($file_path);
361
        if (!$img_width || !$img_height) {
362
            return false;
363
        }
364
        $scale = min($options['max_width'] / $img_width, $options['max_height'] / $img_height);
365
        if ($scale >= 1) {
366
            if ($file_path !== $new_file_path) {
367
                return copy($file_path, $new_file_path);
368
            }
369
370
            return true;
371
        }
372
        $new_width  = $img_width * $scale;
373
        $new_height = $img_height * $scale;
374
        $new_img    = @imagecreatetruecolor($new_width, $new_height);
375
        switch (mb_strtolower(mb_substr(mb_strrchr($file_name, '.'), 1))) {
376
            case 'jpg':
377
            case 'jpeg':
378
                $src_img       = @imagecreatefromjpeg($file_path);
379
                $write_image   = 'imagejpeg';
380
                $image_quality = isset($options['jpeg_quality']) ? $options['jpeg_quality'] : 75;
381
                break;
382
            case 'gif':
383
                @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...
384
                $src_img       = @imagecreatefromgif($file_path);
385
                $write_image   = 'imagegif';
386
                $image_quality = null;
387
                break;
388
            case 'png':
389
                @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...
390
                @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...
391
                @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...
392
                $src_img       = @imagecreatefrompng($file_path);
393
                $write_image   = 'imagepng';
394
                $image_quality = isset($options['png_quality']) ? $options['png_quality'] : 9;
395
                break;
396
            default:
397
                $src_img = null;
398
        }
399
        $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...
400
        // Free up memory (imagedestroy does not delete files):
401
        @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...
402
        @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...
403
404
        return $success;
405
    }
406
407
    /**
408
     * @param $error
409
     * @return mixed
410
     */
411
    protected function get_error_message($error)
412
    {
413
        return array_key_exists($error, $this->error_messages) ? $this->error_messages[$error] : $error;
414
    }
415
416
    /**
417
     * @param $val
418
     * @return float|int
419
     */
420
    public function get_config_bytes($val)
421
    {
422
        $val  = trim($val);
423
        $last = mb_strtolower($val[mb_strlen($val) - 1]);
424
        switch ($last) {
425
            case 'g':
426
                $val *= 1024;
427
            // no break
428
            case 'm':
429
                $val *= 1024;
430
            // no break
431
            case 'k':
432
                $val *= 1024;
433
        }
434
435
        return $this->fix_integer_overflow($val);
436
    }
437
438
    /**
439
     * @param $uploaded_file
440
     * @param $file
441
     * @param $error
442
     * @param $index
443
     * @return bool
444
     */
445
    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...
446
    {
447
        if ($error) {
448
            $file->error = $this->get_error_message($error);
449
450
            return false;
451
        }
452
        $content_length = $this->fix_integer_overflow((int)$_SERVER['CONTENT_LENGTH']);
453
        $post_max_size  = $this->get_config_bytes(ini_get('post_max_size'));
454
        if ($post_max_size && ($content_length > $post_max_size)) {
455
            $file->error = $this->get_error_message('post_max_size');
456
457
            return false;
458
        }
459
        if (!preg_match($this->options['accept_file_types'], $file->name)) {
460
            $file->error = $this->get_error_message('accept_file_types');
461
462
            return false;
463
        }
464
        if ($uploaded_file && is_uploaded_file($uploaded_file)) {
465
            $file_size = $this->get_file_size($uploaded_file);
466
        } else {
467
            $file_size = $content_length;
468
        }
469
        if ($this->options['max_file_size'] && ($file_size > $this->options['max_file_size'] || $file->size > $this->options['max_file_size'])) {
470
            $file->error = $this->get_error_message('max_file_size');
471
472
            return false;
473
        }
474
        if ($this->options['min_file_size'] && $file_size < $this->options['min_file_size']) {
475
            $file->error = $this->get_error_message('min_file_size');
476
477
            return false;
478
        }
479
        if (is_int($this->options['max_number_of_files']) && ($this->count_file_objects() >= $this->options['max_number_of_files'])) {
480
            $file->error = $this->get_error_message('max_number_of_files');
481
482
            return false;
483
        }
484
        list($img_width, $img_height) = @getimagesize($uploaded_file);
485
        if (is_int($img_width)) {
486 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...
487
                $file->error = $this->get_error_message('max_width');
488
489
                return false;
490
            }
491 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...
492
                $file->error = $this->get_error_message('max_height');
493
494
                return false;
495
            }
496 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...
497
                $file->error = $this->get_error_message('min_width');
498
499
                return false;
500
            }
501 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...
502
                $file->error = $this->get_error_message('min_height');
503
504
                return false;
505
            }
506
        }
507
508
        return true;
509
    }
510
511
    /**
512
     * @param $matches
513
     * @return string
514
     */
515
    protected function upcount_name_callback($matches)
516
    {
517
        $index = isset($matches[1]) ? (int)$matches[1] + 1 : 1;
518
        $ext   = isset($matches[2]) ? $matches[2] : '';
519
520
        return ' (' . $index . ')' . $ext;
521
    }
522
523
    /**
524
     * @param $name
525
     * @return null|string|string[]
526
     */
527
    protected function upcount_name($name)
528
    {
529
        return preg_replace_callback('/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', [$this, 'upcount_name_callback'], $name, 1);
530
    }
531
532
    /**
533
     * @param $name
534
     * @param $type
535
     * @param $index
536
     * @param $content_range
537
     * @return null|string|string[]
538
     */
539
    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...
540
    {
541
        while (is_dir($this->get_upload_path($name))) {
542
            $name = $this->upcount_name($name);
543
        }
544
        // Keep an existing filename if this is part of a chunked upload:
545
        $uploaded_bytes = $this->fix_integer_overflow((int)$content_range[1]);
546
        while (is_file($this->get_upload_path($name))) {
547
            if ($uploaded_bytes === $this->get_file_size($this->get_upload_path($name))) {
548
                break;
549
            }
550
            $name = $this->upcount_name($name);
551
        }
552
553
        return $name;
554
    }
555
556
    /**
557
     * @param $name
558
     * @param $type
559
     * @param $index
560
     * @param $content_range
561
     * @return mixed|string
562
     */
563
    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...
564
    {
565
        // Remove path information and dots around the filename, to prevent uploading
566
        // into different directories or replacing hidden system files.
567
        // Also remove control characters and spaces (\x00..\x20) around the filename:
568
        $name = trim(basename(stripslashes($name)), ".\x00..\x20");
569
        // Use a timestamp for empty filenames:
570
        if (!$name) {
571
            $name = str_replace('.', '-', microtime(true));
572
        }
573
        // Add missing file extension for known image types:
574
        if (false === mb_strpos($name, '.') && preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
575
            $name .= '.' . $matches[1];
576
        }
577
578
        return $name;
579
    }
580
581
    /**
582
     * @param $name
583
     * @param $type
584
     * @param $index
585
     * @param $content_range
586
     * @return null|string|string[]
587
     */
588
    protected function get_file_name($name, $type, $index, $content_range)
589
    {
590
        return $this->get_unique_filename($this->trim_file_name($name, $type, $index, $content_range), $type, $index, $content_range);
591
    }
592
593
    /**
594
     * @param $file
595
     * @param $index
596
     */
597
    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...
598
    {
599
        // Handle form data, e.g. $_REQUEST['description'][$index]
600
    }
601
602
    /**
603
     * @param $file_path
604
     * @return bool
605
     */
606
    protected function orient_image($file_path)
607
    {
608
        if (!function_exists('exif_read_data')) {
609
            return false;
610
        }
611
        $exif = @exif_read_data($file_path);
612
        if (false === $exif) {
613
            return false;
614
        }
615
        $orientation = (int)(@$exif['Orientation']);
616
        if (!in_array($orientation, [3, 6, 8])) {
617
            return false;
618
        }
619
        $image = @imagecreatefromjpeg($file_path);
620
        switch ($orientation) {
621
            case 3:
622
                $image = @imagerotate($image, 180, 0);
623
                break;
624
            case 6:
625
                $image = @imagerotate($image, 270, 0);
626
                break;
627
            case 8:
628
                $image = @imagerotate($image, 90, 0);
629
                break;
630
            default:
631
                return false;
632
        }
633
        $success = imagejpeg($image, $file_path);
634
        // Free up memory (imagedestroy does not delete files):
635
        @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...
636
637
        return $success;
638
    }
639
640
    /**
641
     * @param      $uploaded_file
642
     * @param      $name
643
     * @param      $size
644
     * @param      $type
645
     * @param      $error
646
     * @param null $index
647
     * @param null $content_range
648
     * @return \stdClass
649
     */
650
    protected function handle_file_upload(
651
        $uploaded_file,
652
        $name,
653
        $size,
654
        $type,
655
        $error,
656
        $index = null,
657
        $content_range = null
658
    ) {
659
        global $xoopsUser, $SwDatabase;
660
        $file = new \stdClass();
661
662
        $file->name = $this->get_file_name($name, $type, $index, $content_range);
663
        $file->size = $this->fix_integer_overflow((int)$size);
664
        $file->type = $type;
665
666
        // Save to database for later use
667
        $db     = new SwDatabase();
668
        $userid = $xoopsUser->getVar('uid');
669
670
        // Generate new name for file
671
        $file->name = basename(stripslashes($name));
672
        $file->name = time() . mt_rand(0, 99999) . '.' . $this->getFileExtension($name);
673
        $img        = XOOPS_URL . '/uploads/albums_smallworld/' . $userid . '/' . $file->name;
674
        $db->saveImage("'', '" . $userid . "', '" . $file->name . "', '" . addslashes($img) . "', '" . time() . "', ''");
675
676
        if ($this->validate($uploaded_file, $file, $error, $index)) {
677
            $this->handle_form_data($file, $index);
678
            $upload_dir = $this->get_upload_path();
679 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...
680
                if (!mkdir($upload_dir, $this->options['mkdir_mode'], true) && !is_dir($upload_dir)) {
681
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $upload_dir));
682
                }
683
            }
684
            $file_path   = $this->get_upload_path($file->name);
685
            $append_file = $content_range && is_file($file_path) && $file->size > $this->get_file_size($file_path);
686 View Code Duplication
            if ($uploaded_file && is_uploaded_file($uploaded_file)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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