Completed
Branch master (02d3c2)
by Pierre-Henry
33:24
created

File::recursiveDirIterator()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 6
nop 3
dl 0
loc 27
rs 6.7272
c 0
b 0
f 0
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 27 and the first side effect is on line 14.

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
/**
3
 * @title            File Class
4
 * @desc             Useful methods for handling files.
5
 *
6
 * @author           Pierre-Henry Soria <[email protected]>
7
 * @copyright        (c) 2012-2017, Pierre-Henry Soria. All Rights Reserved.
8
 * @license          GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
9
 * @package          PH7 / Framework / File
10
 */
11
12
namespace PH7\Framework\File;
13
14
defined('PH7') or exit('Restricted access');
15
16
use PH7\Framework\Server\Server;
17
use PH7\Framework\Url\Url;
18
use PH7\Framework\Parse\Url as ParseUrl;
19
use PH7\Framework\Navigation\Browser;
20
use PH7\Framework\Registry\Registry;
21
use RecursiveDirectoryIterator;
22
use RecursiveIteratorIterator;
23
use SplFileObject;
24
use ZipArchive;
25
use PH7\Framework\Error\CException\PH7InvalidArgumentException;
26
27
class File
28
{
29
    const REGEX_BINARY_FILE = '/^(.*?)\.(gif|jpg|jpeg|png|ico|mp3|mp4|mov|avi|flv|mpg|mpeg|wmv|ogg|ogv|webm|pdf|ttf|eot|woff|svg|swf)$/i';
30
31
    // End Of Line relative to the operating system
32
    const EOL = PHP_EOL;
33
34
    /**
35
     * Mime Types list.
36
     *
37
     * @staticvar array $aMimeTypes
38
     */
39
    private static $aMimeTypes = [
40
        'pdf' => 'application/pdf',
41
        'txt' => 'text/plain',
42
        'html' => 'text/html',
43
        'htm' => 'text/html',
44
        'exe' => 'application/octet-stream',
45
        'zip' => 'application/zip',
46
        'doc' => 'application/msword',
47
        'xls' => 'application/vnd.ms-excel',
48
        'ppt' => 'application/vnd.ms-powerpoint',
49
        'gif' => 'image/gif',
50
        'png' => 'image/png',
51
        'jpeg' => 'image/jpg',
52
        'jpg' => 'image/jpg',
53
        'ico' => 'image/x-icon',
54
        'eot' => 'application/vnd.ms-fontobject',
55
        'otf' => 'application/octet-stream',
56
        'ttf' => 'application/octet-stream',
57
        'woff' => 'application/octet-stream',
58
        'svg' => 'application/octet-stream',
59
        'swf' => 'application/x-shockwave-flash',
60
        'mp3' => 'audio/mpeg',
61
        'mp4' => 'video/mp4',
62
        'mov' => 'video/quicktime',
63
        'avi' => 'video/x-msvideo',
64
        'php' => 'text/plain',
65
    ];
66
67
    /**
68
     * @param string $sExt Extension File.
69
     *
70
     * @return string (string | null) Returns the "mime type" if it is found, otherwise "null"
71
     */
72
    public function getMimeType($sExt)
73
    {
74
        return (array_key_exists($sExt, static::$aMimeTypes)) ? static::$aMimeTypes[$sExt] : null;
0 ignored issues
show
Bug introduced by
Since $aMimeTypes is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $aMimeTypes to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
75
    }
76
77
    /**
78
     * Get Extension file without the dot.
79
     *
80
     * @param string $sFile The File Name.
81
     *
82
     * @return string
83
     */
84
    public function getFileExt($sFile)
85
    {
86
        return strtolower(substr(strrchr($sFile, PH7_DOT), 1));
87
    }
88
89
    /**
90
     * Get File without Extension and dot.
91
     * This function is smarter than just a code like this, substr($sFile,0,strpos($sFile,'.'))
92
     * Just look at the example below for you to realize that the function removes only the extension and nothing else!
93
     * Example 1 "my_file.pl" The return value is "my_file"
94
     * Example 2 "my_file.inc.pl" The return value is "my_file.inc"
95
     * Example 3 "my_file.class.html.php" The return value is "my_file.class.html"
96
     *
97
     * @see File::getFileExt() To see the method that retrieves the file extension.
98
     *
99
     * @param string $sFile
100
     *
101
     * @return string
102
     */
103
    public function getFileWithoutExt($sFile)
104
    {
105
        $sExt = $this->getFileExt($sFile);
106
        return str_replace(PH7_DOT . $sExt, '', $sFile);
107
    }
108
109
    /**
110
     * Get File Contents.
111
     *
112
     * @param string $sFile File name.
113
     * @param boolean $bIncPath Default FALSE
114
     *
115
     * @return string|boolean Returns the read data or FALSE on failure.
116
     */
117
    public function getFile($sFile, $bIncPath = false)
118
    {
119
        return @file_get_contents($sFile, $bIncPath);
120
    }
121
122
    /**
123
     * Put File Contents.
124
     *
125
     * @param string $sFile File name.
126
     * @param string $sContents Contents file.
127
     * @param integer $iFlag Constant (see http://php.net/manual/function.file-put-contents.php).
128
     *
129
     * @return integer|boolean Returns the number of bytes that were written to the file, or FALSE on failure.
130
     */
131
    public function putFile($sFile, $sContents, $iFlag = 0)
132
    {
133
        return @file_put_contents($sFile, $sContents, $iFlag);
134
    }
135
136
    /**
137
     * Check if file exists.
138
     *
139
     * @param array|string $mFile
140
     *
141
     * @return boolean TRUE if file exists, FALSE otherwise.
142
     */
143
    public function existFile($mFile)
144
    {
145
        $bExists = false; // Default value
146
147
        if (is_array($mFile))
148
        {
149
            foreach ($mFile as $sFile) {
150
                if (!$bExists = $this->existFile($sFile)) {
151
                    return false;
152
                }
153
            }
154
        }
155
        else
156
        {
157
            $bExists = is_file($mFile);
158
        }
159
160
        return $bExists;
161
    }
162
163
    /**
164
     * Check if directory exists.
165
     *
166
     * @param array|string $mDir
167
     *
168
     * @return boolean TRUE if file exists, FALSE otherwise.
169
     */
170
    public function existDir($mDir)
171
    {
172
        $bExists = false; // Default value
173
174
        if (is_array($mDir))
175
        {
176
            foreach ($mDir as $sDir) {
177
                if (!$bExists = $this->existDir($sDir)) {
178
                    return false;
179
                }
180
            }
181
        }
182
        else
183
        {
184
            $bExists = is_dir($mDir);
185
        }
186
187
        return $bExists;
188
    }
189
190
    /**
191
     * @param string $sDir The directory.
192
     *
193
     * @return array The list of the folder that is in the directory.
194
     */
195
    public function getDirList($sDir)
196
    {
197
        $aDirList = array();
198
199
        if ($rHandle = opendir($sDir))
200
        {
201
            while (false !== ($sFile = readdir($rHandle)))
202
            {
203
                if ($sFile != '.' && $sFile != '..' && is_dir($sDir . PH7_DS . $sFile))
204
                    $aDirList[] = $sFile;
205
            }
206
            asort($aDirList);
207
            reset($aDirList);
208
        }
209
        closedir($rHandle);
210
        return $aDirList;
211
    }
212
213
    /**
214
     * Get file size.
215
     *
216
     * @param string $sFile
217
     *
218
     * @return integer The size of the file in bytes.
219
     */
220
    public function size($sFile)
221
    {
222
        return (int) @filesize($sFile);
223
    }
224
225
    /**
226
     * @param string $sDir
227
     * @param string|array $mExt Optional, retrieves only files with specific extensions. Default value is NULL.
228
     *
229
     * @return array List of files sorted alphabetically.
230
     */
231
    public function getFileList($sDir, $mExt = null)
232
    {
233
        $aTree = array();
234
        $sDir = $this->checkExtDir($sDir);
235
236
        if (is_dir($sDir) && $rHandle = opendir($sDir))
237
        {
238
            while (false !== ($sF = readdir($rHandle)))
239
            {
240
                if ($sF !== '.' && $sF !== '..')
241
                {
242
                    if (is_dir($sDir . $sF))
243
                    {
244
                        $aTree = array_merge($aTree, $this->getFileList($sDir . $sF, $mExt));
245
                    }
246
                    else
247
                    {
248
                        if (!empty($mExt))
249
                        {
250
                            $aExt = (array) $mExt;
251
252
                            foreach ($aExt as $sExt)
253
                            {
254
                                if (substr($sF, -strlen($sExt)) === $sExt)
255
                                    $aTree[] = $sDir . $sF;
256
                            }
257
                        }
258
                        else
259
                        {
260
                            $aTree[] = $sDir . $sF;
261
                        }
262
                    }
263
                }
264
            }
265
            sort($aTree);
266
        }
267
        closedir($rHandle);
0 ignored issues
show
Bug introduced by
The variable $rHandle 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...
268
        return $aTree;
269
    }
270
271
    /**
272
     * Make sure that folder names have a trailing.
273
     *
274
     * @param string $sDir The directory.
275
     * @param bool $bStart for check extension directory start. Default FALSE
276
     * @param bool $bEnd for check extension end. Default TRUE
277
     *
278
     * @return string $sDir Directory
279
     */
280
    public function checkExtDir($sDir, $bStart = false, $bEnd = true)
281
    {
282
        $bIsWindows = Server::isWindows();
283
284
        if (!$bIsWindows && $bStart === true && substr($sDir, 0, 1) !== PH7_DS) {
285
            $sDir = PH7_DS . $sDir;
286
        }
287
288
        if ($bEnd === true && substr($sDir, -1) !== PH7_DS) {
289
            $sDir .= PH7_DS;
290
        }
291
292
        return $sDir;
293
    }
294
295
    /**
296
     * Creates a directory if they are in an array. If it does not exist and
297
     * allows the creation of nested directories specified in the pathname.
298
     *
299
     * @param string|array $mDir
300
     * @param integer (octal) $iMode Default: 0777
301
     *
302
     * @return void
303
     *
304
     * @throws Exception If the file cannot be created.
305
     */
306
    public function createDir($mDir, $iMode = 0777)
307
    {
308
        if (is_array($mDir)) {
309
            foreach ($mDir as $sDir) {
310
                $this->createDir($sDir);
311
            }
312
        } else {
313
            if (!is_dir($mDir)) {
314
                if (!@mkdir($mDir, $iMode, true)) {
315
                    throw new Exception('Error to create file: \'' . $mDir . '\'<br /> Please verify that the directory permission is in writing mode.');
316
                }
317
            }
318
        }
319
    }
320
321
    /**
322
     * Copy files and checks if the "from file" exists.
323
     *
324
     * @param string $sFrom File.
325
     * @param string $sTo File.
326
     *
327
     * @return boolean
328
     */
329
    public function copy($sFrom, $sTo)
330
    {
331
        if (!is_file($sFrom)) {
332
            return false;
333
        }
334
335
        return @copy($sFrom, $sTo);
336
    }
337
338
    /**
339
     * Copy the contents of a directory into another.
340
     *
341
     * @param string $sFrom Old directory.
342
     * @param string $sTo New directory.
343
     *
344
     * @return boolean TRUE if everything went well, otherwise FALSE if the "from directory" couldn't be found or if it couldn't be copied.
345
     *
346
     * @throws PH7InvalidArgumentException
347
     */
348
    public function copyDir($sFrom, $sTo)
349
    {
350
        return $this->recursiveDirIterator($sFrom, $sTo, 'copy');
351
    }
352
353
    /**
354
     * Copy a file or directory with the Unix cp command.
355
     *
356
     * @param string $sFrom File or directory.
357
     * @param string $sTo File or directory.
358
     *
359
     * @return integer|boolean Returns the last line on success, and FALSE on failure.
360
     */
361
    public function systemCopy($sFrom, $sTo)
362
    {
363
        if (file_exists($sFrom)) {
364
            return system("cp -r $sFrom $sTo");
365
        }
366
367
        return false;
368
    }
369
370
    /**
371
     * Rename a file or directory and checks if the "from file" or directory exists with file_exists() function
372
     * since it checks the existance of a file or directory (because, as in the Unix OS, a directory is a file).
373
     *
374
     * @param string $sFrom File or directory.
375
     * @param string $sTo File or directory.
376
     *
377
     * @return boolean
378
     */
379
    public function rename($sFrom, $sTo)
380
    {
381
        if (!file_exists($sFrom)) {
382
            return false;
383
        }
384
385
        return @rename($sFrom, $sTo);
386
    }
387
388
    /**
389
     * Rename the contents of a directory into another.
390
     *
391
     * @param string $sFrom Old directory.
392
     * @param string $sTo New directory.
393
     *
394
     * @return boolean TRUE if everything went well, otherwise FALSE if the "from directory" couldn't be found or if it couldn't be renamed.
395
     *
396
     * @throws PH7InvalidArgumentException
397
     */
398
    public function renameDir($sFrom, $sTo)
399
    {
400
        return $this->recursiveDirIterator($sFrom, $sTo, 'rename');
401
    }
402
403
    /**
404
     * Rename a file or directory with the Unix mv command.
405
     *
406
     * @param string $sFrom File or directory.
407
     * @param string $sTo File or directory.
408
     *
409
     * @return integer|boolean Returns the last line on success, and FALSE on failure.
410
     */
411
    public function systemRename($sFrom, $sTo)
412
    {
413
        if (file_exists($sFrom)) {
414
            return system("mv $sFrom $sTo");
415
        }
416
417
        return false;
418
    }
419
420
    /**
421
     * Deletes a file or files if they are in an array.
422
     * If the file does not exist, the function does nothing.
423
     *
424
     * @param string|array $mFile
425
     *
426
     * @return void
427
     */
428
    public function deleteFile($mFile)
429
    {
430
        if (is_array($mFile)) {
431
            foreach ($mFile as $sF) {
432
                $this->deleteFile($sF);
433
            }
434
        } else {
435
            if (is_file($mFile)) {
436
                @unlink($mFile);
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...
437
            }
438
        }
439
    }
440
441
    /**
442
     * For deleting Directory and files!
443
     * A "rmdir" function improved PHP which also delete files in a directory.
444
     *
445
     * @param string $sPath The path
446
     *
447
     * @return boolean
448
     */
449
    public function deleteDir($sPath)
450
    {
451
        return (is_file($sPath) ? unlink($sPath) : (is_dir($sPath) ? array_map(array($this, 'deleteDir'), glob($sPath . '/*')) === @rmdir($sPath) : false));
452
    }
453
454
    /**
455
     * Remove the contents of a directory.
456
     *
457
     * @param string $sDir
458
     *
459
     * @return void
460
     */
461
    public function remove($sDir)
462
    {
463
        $oIterator = new RecursiveIteratorIterator($this->getDirIterator($sDir), RecursiveIteratorIterator::CHILD_FIRST);
464
465
        foreach ($oIterator as $sPath) {
466
            $sPath->isFile() ? unlink($sPath) : @rmdir($sPath);
467
        }
468
469
        @rmdir($sDir);
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...
470
    }
471
472
    /**
473
     * Get the creation/modification time of a file in the Unix timestamp.
474
     *
475
     * @param string $sFile Full path of the file.
476
     *
477
     * @return integer|boolean Returns the time the file was last modified, or FALSE if it not found.
478
     */
479
    public function getModifTime($sFile)
480
    {
481
        return is_file($sFile) ? filemtime($sFile) : false;
482
    }
483
484
    /**
485
     * Get the version of a file based on the its latest modification.
486
     * Shortened form of self::getModifTime()
487
     *
488
     * @param string $sFile Full path of the file.
489
     *
490
     * @return integer Returns the latest modification time of the file in Unix timestamp.
491
     */
492
    public static function version($sFile)
493
    {
494
        return @filemtime($sFile);
495
    }
496
497
    /**
498
     * Delay script execution.
499
     *
500
     * @param integer $iSleep Halt time in seconds. Optional parameter, default value is 5.
501
     *
502
     * @return integer|boolean Returns "0" on success, or "false" on error.
503
     */
504
    public function sleep($iSleep = null)
505
    {
506
        $iSleep = (!empty($iSleep)) ? $iSleep : 5;
507
        return sleep($iSleep);
508
    }
509
510
    /**
511
     * Changes permission on a file or directory.
512
     *
513
     * @param string $sFile
514
     * @param integer $iMode Octal Permission for the file.
515
     *
516
     * @return boolean
517
     */
518
    public function chmod($sFile, $iMode)
519
    {
520
        // file_exists function verify the existence of a "file" or "folder"!
521
        if (file_exists($sFile) && $this->getOctalAccess($sFile) !== $iMode) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->getOctalAccess($sFile) (string) and $iMode (integer) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
522
            return @chmod($sFile, $iMode);
523
        }
524
525
        return false;
526
    }
527
528
    /**
529
     * @param string $sFile
530
     *
531
     * @return string Octal Permissions.
532
     */
533
    public function getOctalAccess($sFile)
534
    {
535
        clearstatcache();
536
        return substr(sprintf('%o', fileperms($sFile)), -4);
537
    }
538
539
    /**
540
     * @param string $sData
541
     *
542
     * @return string
543
     */
544
    public function pack($sData)
545
    {
546
        return urlencode(serialize($sData));
547
    }
548
549
    /**
550
     * Get the size of a directory.
551
     *
552
     * @param string $sPath
553
     *
554
     * @return integer The size of the file in bytes.
555
     */
556
    public function getDirSize($sPath)
557
    {
558
        if (!is_dir($sPath)) {
559
            return 0;
560
        }
561
562
        if (!($rHandle = opendir($sPath))) {
563
            return 0;
564
        }
565
566
        $iSize = 0;
567
        while (false !== ($sFile = readdir($rHandle))) {
568
            if ($sFile != '.' && $sFile != '..') {
569
                $sFullPath = $sPath . PH7_DS . $sFile;
570
571
                if (is_dir($sFullPath)) {
572
                    $iSize = $this->getDirSize($sFullPath);
573
                } else {
574
                    $iSize += $this->size($sFullPath);
575
                }
576
            }
577
        }
578
        closedir($rHandle);
579
580
        return $iSize;
581
    }
582
583
    /**
584
     * Get free space of a directory.
585
     *
586
     * @param string $sPath
587
     *
588
     * @return float The number of available bytes as a float.
589
     */
590
    public function getDirFreeSpace($sPath)
591
    {
592
        return disk_free_space($sPath);
593
    }
594
595
    /**
596
     * @param string $sData
597
     *
598
     * @return boolean|integer|float|string|array|object
599
     */
600
    public function unpack($sData)
601
    {
602
        return unserialize(urldecode($sData));
603
    }
604
605
    /**
606
     * For download file.
607
     *
608
     * @param string $sFile File to download.
609
     * @param string $sName A name for the file to download.
610
     * @param string $sMimeType Optional, default value is NULL.
611
     *
612
     * @return void
613
     */
614
    public function download($sFile, $sName, $sMimeType = null)
615
    {
616
        /*
617
          This function takes a path to a file to output ($sFile),
618
          the filename that the browser will see ($sName) and
619
          the MIME type of the file ($sMimeType, optional).
620
621
          If you want to do something on download abort/finish,
622
          register_shutdown_function('function_name');
623
         */
624
625
        //if (!is_readable($sFile)) exit('File not found or inaccessible!');
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% 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...
626
627
        $sName = Url::decode($sName); // Clean the name file
628
629
        /* Figure out the MIME type (if not specified) */
630
631
632
        if (empty($sMimeType)) {
633
            $sFileExtension = $this->getFileExt($sFile);
634
635
            $mGetMimeType = $this->getMimeType($sFileExtension);
636
637
            $sMimeType = 'application/force-download';
638
            if (!empty($mGetMimeType)) {
639
                $sMimeType = $mGetMimeType;
640
            }
641
        }
642
643
        @ob_end_clean(); // Turn off output buffering to decrease CPU usage
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...
644
645
        (new Browser)->noCache(); // No cache
646
647
        $sPrefix = Registry::getInstance()->site_name . '_'; // the prefix
648
        header('Content-Type: ' . $sMimeType);
649
        header('Content-Disposition: attachment; filename=' . ParseUrl::clean($sPrefix) . $sName);
650
        header('Content-Transfer-Encoding: binary');
651
        header('Accept-Ranges: bytes');
652
        header('Content-Length: ' . $this->size($sFile));
653
        readfile($sFile);
654
    }
655
656
    /**
657
     * Write Header Contents.
658
     *
659
     * @param string $sHeader Text to be shown in the headers
660
     * @param array $aFile
661
     *
662
     * @return void
663
     */
664
    public function writeHeader($sHeader, $aFile = array())
665
    {
666
        for ($i = 0, $iCountFiles = count($aFile); $i < $iCountFiles; $i++) {
667
            $rHandle = fopen($aFile[$i], 'wb+');
668
669
            if ($this->size($aFile[$i]) > 0) {
670
                $sData = fread($rHandle, $this->size($aFile[$i]));
671
                fwrite($rHandle, $sHeader . static::EOL . $sData);
672
            }
673
            fclose($rHandle);
674
        }
675
    }
676
677
    /**
678
     * Writes and saves the contents to a file.
679
     * It also creates a temporary file to not delete the original file if something goes wrong during the recording file.
680
     *
681
     * @param string $sFile
682
     * @param string $sData
683
     *
684
     * @return integer Returns the number of bytes written, or NULL on error.
685
     */
686
    public function save($sFile, $sData)
687
    {
688
        $sTmpFile = $this->getFileWithoutExt($sFile) . '.tmp.' . $this->getFileExt($sFile);
689
        $iWritten = (new SplFileObject($sTmpFile, 'wb'))->fwrite($sData);
690
691
        if ($iWritten !== null) {
692
            // Copy of the temporary file to the original file if no problem occurred.
693
            copy($sTmpFile, $sFile);
694
        }
695
696
        // Deletes the temporary file.
697
        $this->deleteFile($sTmpFile);
698
699
        return $iWritten;
700
    }
701
702
    /**
703
     * @param string $sPath
704
     * @param array|string $mFiles
705
     *
706
     * @return array|string The Files.
707
     */
708
    public function readFiles($sPath = './', &$mFiles)
709
    {
710
        if (!($rHandle = opendir($sPath))) {
711
            return false;
712
        }
713
714
        while (false !== ($sFile = readdir($rHandle))) {
715
            if ($sFile != '.' && $sFile != '..') {
716
                if (strpos($sFile, '.') === false) {
717
                    $this->readFiles($sPath . PH7_DS . $sFile, $mFiles);
718
                } else {
719
                    $mFiles[] = $sPath . PH7_DS . $sFile;
720
                }
721
            }
722
        }
723
        closedir($rHandle);
724
725
        return $mFiles;
726
    }
727
728
    /**
729
     * Reading Directories.
730
     *
731
     * @param string $sPath
732
     *
733
     * @return array|boolean Returns an ARRAY with the folders or FALSE if the folder could not be opened.
734
     */
735
    public function readDirs($sPath = './')
736
    {
737
        if (!($rHandle = opendir($sPath))) {
738
            return false; // Return when yield is used will be OK with PHP 7
739
        }
740
741
        $aRet = array();//remove it for yield
742
743
        while (false !== ($sFolder = readdir($rHandle))) {
744
            if ('.' == $sFolder || '..' == $sFolder || !is_dir($sPath . $sFolder)) {
745
                continue;
746
            }
747
748
            //yield $sFolder; // PHP 7
749
            $aRet[] = $sFolder;//remove it for yield
750
        }
751
        closedir($rHandle);
752
753
        return $aRet;//remove it for yield
754
    }
755
756
    /**
757
     * Get the URL contents (For URLs, it is better to use CURL because it is faster than file_get_contents function).
758
     *
759
     * @param string $sUrl URL to be read contents.
760
     *
761
     * @return string|boolean Return the result content on success, FALSE on failure.
762
     */
763
    public function getUrlContents($sUrl)
764
    {
765
        $rCh = curl_init();
766
        curl_setopt($rCh, CURLOPT_URL, $sUrl);
767
        curl_setopt($rCh, CURLOPT_HEADER, 0);
768
        curl_setopt($rCh, CURLOPT_RETURNTRANSFER, 1);
769
        curl_setopt($rCh, CURLOPT_FOLLOWLOCATION, 1);
770
        $mRes = curl_exec($rCh);
771
        curl_close($rCh);
772
        unset($rCh);
773
774
        return $mRes;
775
    }
776
777
    /**
778
     * Extract Zip archive.
779
     *
780
     * @param string $sFile Zip file.
781
     * @param string $sDir Destination to extract the file.
782
     *
783
     * @return boolean
784
     */
785
    public function zipExtract($sFile, $sDir)
786
    {
787
        $oZip = new ZipArchive;
788
        $mRes = $oZip->open($sFile);
789
790
        if ($mRes === true) {
791
            $oZip->extractTo($sDir);
792
            $oZip->close();
793
            return true;
794
        }
795
796
        return false; // Return error value
797
    }
798
799
    /**
800
     * Check if the file is binary.
801
     *
802
     * @param string $sFile
803
     *
804
     * @return boolean
805
     */
806
    public function isBinary($sFile)
807
    {
808
        if (file_exists($sFile)) {
809
            if (!is_file($sFile)) {
810
                return false;
811
            }
812
813
            if (preg_match(self::REGEX_BINARY_FILE, $sFile)) {
814
                return true;
815
            }
816
817
            $rHandle  = fopen($sFile, 'r');
818
            $sContents = fread($rHandle, 512); // Get 512 bytes of the file.
819
            fclose($rHandle);
820
            clearstatcache();
821
822
            if (!function_exists('is_binary')) // PHP 6
823
                return is_binary($sContents);
824
825
            return (
826
                0 or substr_count($sContents, "^ -~", "^\r\n")/512 > 0.3
827
                or substr_count($sContents, "\x00") > 0
828
            );
829
        }
830
831
        return false;
832
    }
833
834
    /**
835
     * Create a recurive directory iterator for a given directory.
836
     *
837
     * @param string $sPath
838
     *
839
     * @return RecursiveDirectoryIterator
840
     */
841
    private function getDirIterator($sPath)
842
    {
843
        return new RecursiveDirectoryIterator($sPath);
844
    }
845
846
    /**
847
     * Recursive Directory Iterator.
848
     *
849
     * @param string $sFuncName The function name. Choose between 'copy' and 'rename'.
850
     * @param string $sFrom Directory.
851
     * @param string $sTo Directory.
852
     *
853
     * @return boolean
854
     *
855
     * @throws PH7InvalidArgumentException If the type is bad.
856
     */
857
    private function recursiveDirIterator($sFrom, $sTo, $sFuncName)
858
    {
859
        if ($sFuncName !== 'copy' && $sFuncName !== 'rename') {
860
            throw new PH7InvalidArgumentException('Bad function name: \'' . $sFuncName . '\'');
861
        }
862
863
        if (!is_dir($sFrom)) {
864
            return false;
865
        }
866
867
        $bRet = false; // Default value
868
        $oIterator = new RecursiveIteratorIterator($this->getDirIterator($sFrom), RecursiveIteratorIterator::SELF_FIRST);
869
870
        foreach ($oIterator as $sFromFile) {
871
            $sDest = $sTo . PH7_DS . $oIterator->getSubPathName();
872
873
            if ($sFromFile->isDir()) {
874
                $this->createDir($sDest);
875
            } else {
876
                if (!$bRet = $this->$sFuncName($sFromFile, $sDest)) {
877
                    return false;
878
                }
879
            }
880
        }
881
882
        return $bRet;
883
    }
884
}
885