Completed
Push — master ( ca3f3f...3c943f )
by Michael
03:20
created

XoopsMediaUploader   C

Complexity

Total Complexity 65

Size/Duplication

Total Lines 401
Duplicated Lines 3.99 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 65
c 1
b 0
f 0
lcom 1
cbo 0
dl 16
loc 401
rs 5.7894

19 Methods

Rating   Name   Duplication   Size   Complexity  
A setNoAdminSizeCheck() 0 4 1
A setTargetFileName() 0 4 1
A setPrefix() 0 4 1
A getMediaName() 0 4 1
A getMediaType() 0 4 1
A getMediaSize() 0 4 1
A getMediaTmpName() 0 4 1
A getSavedFileName() 0 4 1
A getSavedDestination() 0 4 1
A __construct() 0 14 4
F fetchMedia() 8 88 21
D upload() 8 32 10
C copyFile() 0 27 7
A checkMaxFileSize() 0 7 2
A checkMaxWidth() 0 7 2
A checkMaxHeight() 0 7 2
A checkMimeType() 0 8 3
A setErrors() 0 4 1
A getErrors() 0 16 4

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

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 85 and the first side effect is on line 80.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
// $Id: uploader.php 10374 2012-12-12 23:39:48Z trabis $
3
// ------------------------------------------------------------------------ //
4
// XOOPS - PHP Content Management System                      //
5
// Copyright (c) 2000 XOOPS.org                           //
6
// <http://www.xoops.org/>                             //
7
// ------------------------------------------------------------------------ //
8
// This program is free software; you can redistribute it and/or modify     //
9
// it under the terms of the GNU General Public License as published by     //
10
// the Free Software Foundation; either version 2 of the License, or        //
11
// (at your option) any later version.                                      //
12
// //
13
// You may not change or alter any portion of this comment or credits       //
14
// of supporting developers from this source code or any supporting         //
15
// source code which is considered copyrighted (c) material of the          //
16
// original comment or credit authors.                                      //
17
// //
18
// This program is distributed in the hope that it will be useful,          //
19
// but WITHOUT ANY WARRANTY; without even the implied warranty of           //
20
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            //
21
// GNU General Public License for more details.                             //
22
// //
23
// You should have received a copy of the GNU General Public License        //
24
// along with this program; if not, write to the Free Software              //
25
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA //
26
// ------------------------------------------------------------------------ //
27
// Author: Kazumi Ono (AKA onokazu)                                          //
28
// URL: http://www.myweb.ne.jp/, http://www.xoops.org/, http://jp.xoops.org/ //
29
// Project: The XOOPS Project                                                //
30
// ------------------------------------------------------------------------- //
31
/**
32
 * !
33
 * Example
34
 * include_once 'uploader.php';
35
 * $allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
36
 * $maxfilesize = 50000;
37
 * $maxfilewidth = 120;
38
 * $maxfileheight = 120;
39
 * $uploader = new XoopsMediaUploader('/home/xoops/uploads', $allowed_mimetypes, $maxfilesize, $maxfilewidth, $maxfileheight);
40
 * if ($uploader->fetchMedia($HTTP_POST_VARS['uploade_file_name'])) {
41
 * if (!$uploader->upload()) {
42
 * echo $uploader->getErrors();
43
 * } else {
44
 * echo '<h4>File uploaded successfully!</h4>'
45
 * echo 'Saved as: ' . $uploader->getSavedFileName() . '<br />';
46
 * echo 'Full path: ' . $uploader->getSavedDestination();
47
 * }
48
 * } else {
49
 * echo $uploader->getErrors();
50
 * }
51
 */
52
/**
53
 * Upload Media files
54
 * Example of usage:
55
 * <code>
56
 * include_once 'uploader.php';
57
 * $allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
58
 * $maxfilesize = 50000;
59
 * $maxfilewidth = 120;
60
 * $maxfileheight = 120;
61
 * $uploader = new XoopsMediaUploader('/home/xoops/uploads', $allowed_mimetypes, $maxfilesize, $maxfilewidth, $maxfileheight);
62
 * if ($uploader->fetchMedia($HTTP_POST_VARS['uploade_file_name'])) {
63
 *            if (!$uploader->upload()) {
64
 *               echo $uploader->getErrors();
65
 *            } else {
66
 *               echo '<h4>File uploaded successfully!</h4>'
67
 *               echo 'Saved as: ' . $uploader->getSavedFileName() . '<br />';
68
 *               echo 'Full path: ' . $uploader->getSavedDestination();
69
 *            }
70
 * } else {
71
 *            echo $uploader->getErrors();
72
 * }
73
 * </code>
74
 *
75
 * @package        kernel
76
 * @subpackage     core
77
 * @author         Kazumi Ono <[email protected]>
78
 * @copyright  (c) 2000-2003 The Xoops Project - www.xoops.org
79
 */
80
mt_srand((double)microtime() * 1000000);
81
82
/**
83
 * Class XoopsMediaUploader
84
 */
85
class XoopsMediaUploader
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
86
{
87
    public $mediaName;
88
    public $mediaType;
89
    public $mediaSize;
90
    public $mediaTmpName;
91
    public $mediaError;
92
    public $uploadDir        = '';
93
    public $allowedMimeTypes = array();
94
    public $maxFileSize      = 0;
95
    public $maxWidth;
96
    public $maxHeight;
97
    public $targetFileName;
98
    public $prefix;
99
    public $ext;
100
    public $dimension;
101
    public $errors           = array();
102
    public $savedDestination;
103
    public $savedFileName;
104
    /**
105
     * No admin check for uploads
106
     */
107
    private $noAdminSizeCheck;
108
109
    /**
110
     * Constructor
111
     *
112
     * @param string    $uploadDir
113
     * @param int|array $allowedMimeTypes
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $allowedMimeTypes a bit more specific; maybe use integer.
Loading history...
114
     * @param int       $maxFileSize
115
     * @param int       $maxWidth
116
     * @param int       $maxHeight
117
     */
118
    public function __construct($uploadDir, $allowedMimeTypes = 0, $maxFileSize, $maxWidth = 0, $maxHeight = 0)
119
    {
120
        if (is_array($allowedMimeTypes)) {
121
            $this->allowedMimeTypes = $allowedMimeTypes;
122
        }
123
        $this->uploadDir   = $uploadDir;
124
        $this->maxFileSize = (int)$maxFileSize;
125
        if (isset($maxWidth)) {
126
            $this->maxWidth = (int)$maxWidth;
127
        }
128
        if (isset($maxHeight)) {
129
            $this->maxHeight = (int)$maxHeight;
130
        }
131
    }
132
133
    /**
134
     * @param $value
135
     */
136
    public function setNoAdminSizeCheck($value)
137
    {
138
        $this->noAdminSizeCheck = $value;
139
    }
140
141
    /**
142
     * Fetch the uploaded file
143
     *
144
     * @param string $mediaName Name of the file field
145
     * @param int    $index     Index of the file (if more than one uploaded under that name)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $index not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
146
     *
147
     * @global       $HTTP_POST_FILES
148
     * @return bool
149
     */
150
    public function fetchMedia($mediaName, $index = null)
0 ignored issues
show
Coding Style introduced by
fetchMedia uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
151
    {
152
        global $_FILES;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
153
        if (!isset($_FILES[$mediaName])) {
154
            $this->setErrors('You either did not choose a file to upload or the server has insufficient read/writes to upload this file.!');
155
156
            return false;
157
        } elseif (isset($index) && is_array($_FILES[$mediaName]['name'])) {
158
            $index              = (int)$index;
159
            $this->mediaName    = $_FILES[$mediaName]['name'][$index];
160
            $this->mediaType    = $_FILES[$mediaName]['type'][$index];
161
            $this->mediaSize    = $_FILES[$mediaName]['size'][$index];
162
            $this->mediaTmpName = $_FILES[$mediaName]['tmp_name'][$index];
163
            $this->mediaError   = !empty($_FILES[$mediaName]['error'][$index]) ? $_FILES[$mediaName]['errir'][$index] : 0;
164
        } else {
165
            $mediaName         = @$_FILES[$mediaName];
166
            $this->mediaName    = $mediaName['name'];
167
            $this->mediaName    = $mediaName['name'];
168
            $this->mediaType    = $mediaName['type'];
169
            $this->mediaSize    = $mediaName['size'];
170
            $this->mediaTmpName = $mediaName['tmp_name'];
171
            $this->mediaError   = !empty($mediaName['error']) ? $mediaName['error'] : 0;
172
        }
173
        $this->dimension = getimagesize($this->mediaTmpName);
174
        $this->errors    = array();
175
        if ((int)$this->mediaSize < 0) {
176
            $this->setErrors('Invalid File Size');
177
178
            return false;
179
        }
180
        if ('' === $this->mediaName) {
181
            $this->setErrors('Filename Is Empty');
182
183
            return false;
184
        }
185
        if ('none' === $this->mediaTmpName) {
186
            $this->setErrors('No file uploaded, this is a error');
187
188
            return false;
189
        }
190
        if (!$this->checkMaxFileSize()) {
191
            $this->setErrors(sprintf('File Size: %u. Maximum Size Allowed: %u', $this->mediaSize, $this->maxFileSize));
192
        }
193 View Code Duplication
        if (is_array($this->dimension)) {
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...
194
            if (!$this->checkMaxWidth($this->dimension[0])) {
195
                $this->setErrors(sprintf('File width: %u. Maximum width allowed: %u', $this->dimension[0], $this->maxWidth));
196
            }
197
            if (!$this->checkMaxHeight($this->dimension[1])) {
198
                $this->setErrors(sprintf('File height: %u. Maximum height allowed: %u', $this->dimension[1], $this->maxHeight));
199
            }
200
        }
201
        if (count($this->errors) > 0) {
202
            return false;
203
        }
204
        if (!$this->checkMimeType()) {
205
            $this->setErrors('MIME type not allowed: ' . $this->mediaType);
206
        }
207
        if (!is_uploaded_file($this->mediaTmpName)) {
208
            switch ($this->mediaError) {
209
                case 0: // no error; possible file attack!
210
                    $this->setErrors('There was a problem with your upload. Error: 0');
211
                    break;
212
                case 1: // uploaded file exceeds the upload_max_filesize directive in php.ini
213
                    //if ($this->noAdminSizeCheck)
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
214
                    //{
215
                    //    return true;
216
                    //}
217
                    $this->setErrors('The file you are trying to upload is too big. Error: 1');
218
                    break;
219
                case 2: // uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form
220
                    $this->setErrors('The file you are trying to upload is too big. Error: 2');
221
                    break;
222
                case 3: // uploaded file was only partially uploaded
223
                    $this->setErrors('The file you are trying upload was only partially uploaded. Error: 3');
224
                    break;
225
                case 4: // no file was uploaded
226
                    $this->setErrors('No file selected for upload. Error: 4');
227
                    break;
228
                default: // a default error, just in case!  :)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
229
                    $this->setErrors('No file selected for upload. Error: 5');
230
                    break;
231
            }
232
233
            return false;
234
        }
235
236
        return true;
237
    }
238
239
    /**
240
     * Set the target filename
241
     *
242
     * @param string $value
243
     */
244
    public function setTargetFileName($value)
245
    {
246
        $this->targetFileName = (string)trim($value);
247
    }
248
249
    /**
250
     * Set the prefix
251
     *
252
     * @param string $value
253
     */
254
    public function setPrefix($value)
255
    {
256
        $this->prefix = (string)trim($value);
257
    }
258
259
    /**
260
     * Get the uploaded filename
261
     *
262
     * @return string
263
     */
264
    public function getMediaName()
265
    {
266
        return $this->mediaName;
267
    }
268
269
    /**
270
     * Get the type of the uploaded file
271
     *
272
     * @return string
273
     */
274
    public function getMediaType()
275
    {
276
        return $this->mediaType;
277
    }
278
279
    /**
280
     * Get the size of the uploaded file
281
     *
282
     * @return int
283
     */
284
    public function getMediaSize()
285
    {
286
        return $this->mediaSize;
287
    }
288
289
    /**
290
     * Get the temporary name that the uploaded file was stored under
291
     *
292
     * @return string
293
     */
294
    public function getMediaTmpName()
295
    {
296
        return $this->mediaTmpName;
297
    }
298
299
    /**
300
     * Get the saved filename
301
     *
302
     * @return string
303
     */
304
    public function getSavedFileName()
305
    {
306
        return $this->savedFileName;
307
    }
308
309
    /**
310
     * Get the destination the file is saved to
311
     *
312
     * @return string
313
     */
314
    public function getSavedDestination()
315
    {
316
        return $this->savedDestination;
317
    }
318
319
    /**
320
     * Check the file and copy it to the destination
321
     *
322
     * @param int $chmod
323
     *
324
     * @return bool
325
     */
326
    public function upload($chmod = 0644)
327
    {
328
        if ($this->uploadDir == '') {
329
            $this->setErrors('Upload directory not set');
330
331
            return false;
332
        }
333
        if (!is_dir($this->uploadDir)) {
334
            $this->setErrors('Failed opening directory: ' . $this->uploadDir);
335
        }
336
        if (!is_writable($this->uploadDir)) {
337
            $this->setErrors('Failed opening directory with write permission: ' . $this->uploadDir);
338
        }
339
        if (!$this->checkMaxFileSize()) {
340
            $this->setErrors(sprintf('File Size: %u. Maximum Size Allowed: %u', $this->mediaSize, $this->maxFileSize));
341
        }
342 View Code Duplication
        if (is_array($this->dimension)) {
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...
343
            if (!$this->checkMaxWidth($this->dimension[0])) {
344
                $this->setErrors(sprintf('File width: %u. Maximum width allowed: %u', $this->dimension[0], $this->maxWidth));
345
            }
346
            if (!$this->checkMaxHeight($this->dimension[1])) {
347
                $this->setErrors(sprintf('File height: %u. Maximum height allowed: %u', $this->dimension[1], $this->maxHeight));
348
            }
349
        }
350
        if (!$this->checkMimeType()) {
351
            $this->setErrors('MIME type not allowed: ' . $this->mediaType);
352
        }
353
        if (!$this->copyFile($chmod)) {
354
            $this->setErrors('Failed uploading file: ' . $this->mediaName);
355
        }
356
        return !(count($this->errors) > 0);
357
    }
358
359
    /**
360
     * Copy the file to its destination
361
     *
362
     * @param $chmod
363
     *
364
     * @return bool
365
     */
366
    public function copyFile($chmod)
367
    {
368
        $matched = array();
369
        if (!preg_match("/\.([a-zA-Z0-9]+)$/", $this->mediaName, $matched)) {
370
            return false;
371
        }
372
        if (isset($this->targetFileName)) {
373
            $this->savedFileName = $this->targetFileName;
374
        } elseif (isset($this->prefix)) {
375
            $this->savedFileName = uniqid($this->prefix, true) . '.' . strtolower($matched[1]);
376
        } else {
377
            $this->savedFileName = strtolower($this->mediaName);
378
        }
379
        $this->savedFileName    = preg_replace('!\s+!', '_', $this->savedFileName);
380
        $this->savedDestination = $this->uploadDir . $this->savedFileName;
381
        if (is_file($this->savedDestination) && !!is_dir($this->savedDestination)) {
382
            $this->setErrors('File ' . $this->mediaName . ' already exists on the server. Please rename this file and try again.<br />');
383
384
            return false;
385
        }
386
        if (!move_uploaded_file($this->mediaTmpName, $this->savedDestination)) {
0 ignored issues
show
Security File Manipulation introduced by
$this->savedDestination can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
387
            return false;
388
        }
389
        @chmod($this->savedDestination, $chmod);
0 ignored issues
show
Security File Manipulation introduced by
$this->savedDestination can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
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
391
        return true;
392
    }
393
394
    /**
395
     * Is the file the right size?
396
     *
397
     * @return bool
398
     */
399
    public function checkMaxFileSize()
400
    {
401
        if ($this->noAdminSizeCheck) {
402
            return true;
403
        }
404
        return !($this->mediaSize > $this->maxFileSize);
405
    }
406
407
    /**
408
     * Is the picture the right width?
409
     *
410
     * @param $dimension
411
     *
412
     * @return bool
413
     */
414
    public function checkMaxWidth($dimension)
415
    {
416
        if (!isset($this->maxWidth)) {
417
            return true;
418
        }
419
        return !($dimension > $this->maxWidth);
420
    }
421
422
    /**
423
     * Is the picture the right height?
424
     *
425
     * @param $dimension
426
     *
427
     * @return bool
428
     */
429
    public function checkMaxHeight($dimension)
430
    {
431
        if (!isset($this->maxHeight)) {
432
            return true;
433
        }
434
        return !($dimension > $this->maxWidth);
435
    }
436
437
    /**
438
     * Is the file the right Mime type
439
     * (is there a right type of mime? ;-)
440
     *
441
     * @return bool
442
     */
443
    public function checkMimeType()
444
    {
445
        if (count($this->allowedMimeTypes) > 0 && !in_array($this->mediaType, $this->allowedMimeTypes)) {
446
            return false;
447
        } else {
448
            return true;
449
        }
450
    }
451
452
    /**
453
     * Add an error
454
     *
455
     * @param string $error
456
     */
457
    public function setErrors($error)
458
    {
459
        $this->errors[] = trim($error);
460
    }
461
462
    /**
463
     * Get generated errors
464
     *
465
     * @param bool $ashtml Format using HTML?
466
     *
467
     * @return array |string    Array of array messages OR HTML string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
468
     */
469
    public function &getErrors($ashtml = true)
470
    {
471
        if (!$ashtml) {
472
            return $this->errors;
473
        } else {
474
            $ret = '';
475
            if (count($this->errors) > 0) {
476
                $ret = '<h4>Errors Returned While Uploading</h4>';
477
                foreach ($this->errors as $error) {
478
                    $ret .= $error . '<br />';
479
                }
480
            }
481
482
            return $ret;
483
        }
484
    }
485
}
486