Passed
Push — main ( 01fb06...c551fb )
by Siad
06:07
created

FileSystem::checkAccess()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 27
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 27
rs 9.2222
c 0
b 0
f 0
ccs 10
cts 10
cp 1
cc 6
nc 6
nop 2
crap 6
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Io;
22
23
use Exception;
24
use FilesystemIterator;
25
use Phing\Phing;
26
27
/**
28
 * This is an abstract class for platform specific filesystem implementations
29
 * you have to implement each method in the platform specific filesystem implementation
30
 * classes Your local filesytem implementation must extend this class.
31
 * You should also use this class as a template to write your local implementation
32
 * Some native PHP filesystem specific methods are abstracted here as well. Anyway
33
 * you _must_ always use this methods via a PhingFile object (that by nature uses the
34
 * *FileSystem drivers to access the real filesystem via this class using natives.
35
 *
36
 * FIXME:
37
 *  - Error handling reduced to min fallthrough runtime exceptions
38
 *    more precise errorhandling is done by the PhingFile class
39
 *
40
 * @author Charlie Killian <[email protected]>
41
 * @author Hans Lellelid <[email protected]>
42
 */
43
abstract class FileSystem
44
{
45
    /**
46
     * @var int
47
     */
48
    public const BA_EXISTS = 0x01;
49
50
    /**
51
     * @var int
52
     */
53
    public const BA_REGULAR = 0x02;
54
55
    /**
56
     * @var int
57
     */
58
    public const BA_DIRECTORY = 0x04;
59
60
    /**
61
     * @var int
62
     */
63
    public const BA_HIDDEN = 0x08;
64
65
    /**
66
     * Instance for getFileSystem() method.
67
     *
68
     * @var FileSystem
69
     */
70
    private static $fs;
71
72
    /**
73
     * @var File[]
74
     */
75
    private static $filesToDelete = [];
76
77
    /**
78
     * Static method to return the FileSystem singelton representing
79
     * this platform's local filesystem driver.
80
     *
81
     * @throws IOException
82
     *
83
     * @return FileSystem
84
     */
85 875
    public static function getFileSystem()
86
    {
87 875
        if (null === self::$fs) {
88 8
            switch (Phing::getProperty('host.fstype')) {
89 8
                case 'UNIX':
90 6
                    self::$fs = new UnixFileSystem();
91
92 6
                    break;
93
94 2
                case 'WINDOWS':
95 1
                    self::$fs = new WindowsFileSystem();
96
97 1
                    break;
98
99
                default:
100 1
                    throw new IOException('Host uses unsupported filesystem, unable to proceed');
101
            }
102
        }
103
104 874
        return self::$fs;
105
    }
106
107
    // -- Normalization and construction --
108
109
    /**
110
     * Return the local filesystem's name-separator character.
111
     */
112
    abstract public function getSeparator();
113
114
    /**
115
     * Return the local filesystem's path-separator character.
116
     */
117
    abstract public function getPathSeparator();
118
119
    /**
120
     * Convert the given pathname string to normal form.  If the string is
121
     * already in normal form then it is simply returned.
122
     *
123
     * @param string $strPath
124
     */
125
    abstract public function normalize($strPath);
126
127
    /**
128
     * Compute the length of this pathname string's prefix.  The pathname
129
     * string must be in normal form.
130
     *
131
     * @param string $pathname
132
     */
133
    abstract public function prefixLength($pathname);
134
135
    /**
136
     * Resolve the child pathname string against the parent.
137
     * Both strings must be in normal form, and the result
138
     * will be a string in normal form.
139
     *
140
     * @param string $parent
141
     * @param string $child
142
     */
143
    abstract public function resolve($parent, $child);
144
145
    /**
146
     * Resolve the given abstract pathname into absolute form.  Invoked by the
147
     * getAbsolutePath and getCanonicalPath methods in the PhingFile class.
148
     */
149
    abstract public function resolveFile(File $f);
150
151
    /**
152
     * Return the parent pathname string to be used when the parent-directory
153
     * argument in one of the two-argument PhingFile constructors is the empty
154
     * pathname.
155
     */
156
    abstract public function getDefaultParent();
157
158
    /**
159
     * Post-process the given URI path string if necessary.  This is used on
160
     * win32, e.g., to transform "/c:/foo" into "c:/foo".  The path string
161
     * still has slash separators; code in the PhingFile class will translate them
162
     * after this method returns.
163
     *
164
     * @param string $path
165
     */
166
    abstract public function fromURIPath($path);
167
168
    // -- Path operations --
169
170
    /**
171
     * Tell whether or not the given abstract pathname is absolute.
172
     */
173
    abstract public function isAbsolute(File $f);
174
175
    /**
176
     * canonicalize filename by checking on disk.
177
     *
178
     * @param string $strPath
179
     *
180
     * @return mixed canonical path or false if the file doesn't exist
181
     */
182 838
    public function canonicalize($strPath)
183
    {
184 838
        return @realpath($strPath);
185
    }
186
187
    // -- Attribute accessors --
188
189
    /**
190
     * Check whether the file or directory denoted by the given abstract
191
     * pathname may be accessed by this process.  If the second argument is
192
     * false, then a check for read access is made; if the second
193
     * argument is true, then a check for write (not read-write)
194
     * access is made.  Return false if access is denied or an I/O error
195
     * occurs.
196
     *
197
     * @param bool $write
198
     *
199
     * @return bool
200
     */
201 847
    public function checkAccess(File $f, $write = false)
202
    {
203
        // we clear stat cache, its expensive to look up from scratch,
204
        // but we need to be sure
205 847
        @clearstatcache();
0 ignored issues
show
Bug introduced by
Are you sure the usage of clearstatcache() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
206
207
        // Shouldn't this be $f->GetAbsolutePath() ?
208
        // And why doesn't GetAbsolutePath() work?
209
210 847
        $strPath = (string) $f->getPath();
211
212
        // FIXME
213
        // if file object does denote a file that yet not existst
214
        // path rights are checked
215 847
        if (!@file_exists($strPath) && !is_dir($strPath)) {
216 243
            $strPath = $f->getParent();
217 243
            if (null === $strPath || !is_dir($strPath)) {
218 50
                $strPath = Phing::getProperty('user.dir');
219
            }
220
            //$strPath = dirname($strPath);
221
        }
222
223 847
        if (!$write) {
224 847
            return (bool) @is_readable($strPath);
225
        }
226
227 183
        return (bool) @is_writable($strPath);
228
    }
229
230
    /**
231
     * Whether file can be deleted.
232
     *
233
     * @return bool
234
     */
235
    public function canDelete(File $f)
236
    {
237
        clearstatcache();
238
        $dir = dirname($f->getAbsolutePath());
239
240
        return @is_writable($dir);
241
    }
242
243
    /**
244
     * Return the time at which the file or directory denoted by the given
245
     * abstract pathname was last modified, or zero if it does not exist or
246
     * some other I/O error occurs.
247
     *
248
     * @throws IOException
249
     *
250
     * @return int
251
     */
252 52
    public function getLastModifiedTime(File $f)
253
    {
254 52
        if (!$f->exists()) {
255 5
            return 0;
256
        }
257
258 52
        @clearstatcache();
0 ignored issues
show
Bug introduced by
Are you sure the usage of clearstatcache() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
259 52
        error_clear_last();
260 52
        $strPath = (string) $f->getPath();
261
262 52
        if (@is_link($strPath)) {
263 2
            $stats = @lstat($strPath);
264
265 2
            if (!isset($stats['mtime'])) {
266
                $mtime = false;
267
            } else {
268 2
                $mtime = $stats['mtime'];
269
            }
270
        } else {
271 52
            $mtime = @filemtime($strPath);
272
        }
273
274 52
        if (false === $mtime) {
275
            $lastError = error_get_last();
276
            $errormsg = $lastError['message'] ?? 'unknown error';
277
            $msg = "FileSystem::getLastModifiedTime() FAILED. Can not get modified time of {$strPath}. {$errormsg}";
278
279
            throw new IOException($msg);
280
        }
281
282 52
        return (int) $mtime;
283
    }
284
285
    /**
286
     * Return the length in bytes of the file denoted by the given abstract
287
     * pathname, or zero if it does not exist, is a directory, or some other
288
     * I/O error occurs.
289
     *
290
     * @throws IOException
291
     *
292
     * @return int
293
     */
294 27
    public function getLength(File $f)
295
    {
296 27
        error_clear_last();
297 27
        $strPath = (string) $f->getAbsolutePath();
298 27
        $fs = filesize((string) $strPath);
299 27
        if (false !== $fs) {
300 27
            return $fs;
301
        }
302
303
        $lastError = error_get_last();
304
        $errormsg = $lastError['message'] ?? 'unknown error';
305
        $msg = "FileSystem::Read() FAILED. Cannot get filesize of {$strPath}. {$errormsg}";
306
307
        throw new IOException($msg);
308
    }
309
310
    // -- File operations --
311
312
    /**
313
     * Create a new empty file with the given pathname.  Return
314
     * true if the file was created and false if a
315
     * file or directory with the given pathname already exists.  Throw an
316
     * IOException if an I/O error occurs.
317
     *
318
     * @param string $strPathname path of the file to be created
319
     *
320
     * @throws IOException
321
     *
322
     * @return bool
323
     */
324 79
    public function createNewFile($strPathname)
325
    {
326 79
        if (@file_exists($strPathname)) {
327
            return false;
328
        }
329
330
        // Create new file
331 79
        $fp = @fopen($strPathname, 'w');
332 79
        if (false === $fp) {
333 1
            $error = error_get_last();
334
335 1
            throw new IOException(
336 1
                "The file \"{$strPathname}\" could not be created: " . $error['message'] ?? 'unknown error'
337
            );
338
        }
339 78
        @fclose($fp);
340
341 78
        return true;
342
    }
343
344
    /**
345
     * Delete the file or directory denoted by the given abstract pathname,
346
     * returning true if and only if the operation succeeds.
347
     *
348
     * @param bool $recursive
349
     *
350
     * @throws IOException
351
     */
352 204
    public function delete(File $f, $recursive = false)
353
    {
354 204
        if ($f->isDirectory()) {
355 165
            $this->rmdir($f->getPath(), $recursive);
356
        } else {
357 153
            $this->unlink($f->getPath());
358
        }
359 204
    }
360
361
    /**
362
     * Arrange for the file or directory denoted by the given abstract
363
     * pathname to be deleted when Phing::shutdown is called, returning
364
     * true if and only if the operation succeeds.
365
     *
366
     * @throws IOException
367
     */
368
    public function deleteOnExit(File $f)
369
    {
370
        self::$filesToDelete[] = $f;
371
    }
372
373 1
    public static function deleteFilesOnExit()
374
    {
375 1
        foreach (self::$filesToDelete as $file) {
376
            $file->delete();
377
        }
378 1
    }
379
380
    /**
381
     * Create a new directory denoted by the given abstract pathname,
382
     * returning true if and only if the operation succeeds.
383
     *
384
     * The behaviour is the same as of "mkdir" command in Linux.
385
     * Without $mode argument, the directory is created with permissions
386
     * corresponding to the umask setting.
387
     * If $mode argument is specified, umask setting is ignored and
388
     * the permissions are set according to the $mode argument using chmod().
389
     *
390
     * @param File     $f
391
     * @param null|int $mode
392
     *
393
     * @return bool
394
     */
395 165
    public function createDirectory(&$f, $mode = null)
396
    {
397 165
        if (null === $mode) {
398 161
            $return = @mkdir($f->getAbsolutePath());
399
        } else {
400
            // If the $mode is specified, mkdir() is called with the $mode
401
            // argument so that the new directory does not temporarily have
402
            // higher permissions than it should before chmod() is called.
403 10
            $return = @mkdir($f->getAbsolutePath(), $mode);
404 10
            if ($return) {
405 10
                chmod($f->getAbsolutePath(), $mode);
406
            }
407
        }
408
409 165
        return $return;
410
    }
411
412
    /**
413
     * Rename the file or directory denoted by the first abstract pathname to
414
     * the second abstract pathname, returning true if and only if
415
     * the operation succeeds.
416
     *
417
     * @param File $f1 abstract source file
418
     * @param File $f2 abstract destination file
419
     *
420
     * @throws IOException if rename cannot be performed
421
     */
422 1
    public function rename(File $f1, File $f2)
423
    {
424 1
        error_clear_last();
425
        // get the canonical paths of the file to rename
426 1
        $src = $f1->getAbsolutePath();
427 1
        $dest = $f2->getAbsolutePath();
428 1
        if (false === @rename($src, $dest)) {
429
            $lastError = error_get_last();
430
            $errormsg = $lastError['message'] ?? 'unknown error';
431
            $msg = "Rename FAILED. Cannot rename {$src} to {$dest}. {$errormsg}";
432
433
            throw new IOException($msg);
434
        }
435 1
    }
436
437
    /**
438
     * Set the last-modified time of the file or directory denoted by the
439
     * given abstract pathname returning true if and only if the
440
     * operation succeeds.
441
     *
442
     * @param int $time
443
     *
444
     * @throws IOException
445
     */
446 77
    public function setLastModifiedTime(File $f, $time)
447
    {
448 77
        error_clear_last();
449 77
        $path = $f->getPath();
450 77
        $success = @touch($path, $time);
451 77
        if (!$success) {
452
            $lastError = error_get_last();
453
            $errormsg = $lastError['message'] ?? 'unknown error';
454
455
            throw new IOException("Could not touch '" . $path . "' due to: {$errormsg}");
456
        }
457 77
    }
458
459
    // -- Basic infrastructure --
460
461
    /**
462
     * Compare two abstract pathnames lexicographically.
463
     *
464
     * @throws IOException
465
     *
466
     * @return int
467
     */
468
    public function compare(File $f1, File $f2)
469
    {
470
        throw new IOException('compare() not implemented by local fs driver');
471
    }
472
473
    /**
474
     * Copy a file.
475
     *
476
     * @param File $src  source path and name file to copy
477
     * @param File $dest destination path and name of new file
478
     *
479
     * @throws IOException if file cannot be copied
480
     */
481 22
    public function copy(File $src, File $dest)
482
    {
483
        // Recursively copy a directory
484 22
        if ($src->isDirectory()) {
485
            $this->copyr($src->getAbsolutePath(), $dest->getAbsolutePath());
486
        }
487
488 22
        $srcPath = $src->getAbsolutePath();
489 22
        $destPath = $dest->getAbsolutePath();
490
491 22
        error_clear_last();
492 22
        if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
493
            // Add error from php to end of log message. $errormsg.
494
            $lastError = error_get_last();
495
            $errormsg = $lastError['message'] ?? 'unknown error';
496
            $msg = "FileSystem::copy() FAILED. Cannot copy {$srcPath} to {$destPath}. {$errormsg}";
497
498
            throw new IOException($msg);
499
        }
500
501 22
        $dest->setMode($src->getMode());
502 22
    }
503
504
    /**
505
     * Copy a file, or recursively copy a folder and its contents.
506
     *
507
     * @param string $source Source path
508
     * @param string $dest   Destination path
509
     *
510
     * @return bool Returns TRUE on success, FALSE on failure
511
     *
512
     * @author  Aidan Lister <[email protected]>
513
     *
514
     * @version 1.0.1
515
     *
516
     * @see    http://aidanlister.com/repos/v/function.copyr.php
517
     */
518
    public function copyr($source, $dest)
519
    {
520
        // Check for symlinks
521
        if (is_link($source)) {
522
            return symlink(readlink($source), $dest);
523
        }
524
525
        // Simple copy for a file
526
        if (is_file($source)) {
527
            return copy($source, $dest);
528
        }
529
530
        // Make destination directory
531
        if (!is_dir($dest) && !mkdir($dest) && !is_dir($dest)) {
532
            return false;
533
        }
534
535
        // Loop through the folder
536
        $dir = dir($source);
537
        while (false !== $entry = $dir->read()) {
538
            // Skip pointers
539
            if ('.' == $entry || '..' == $entry) {
540
                continue;
541
            }
542
543
            // Deep copy directories
544
            $this->copyr("{$source}/{$entry}", "{$dest}/{$entry}");
545
        }
546
547
        // Clean up
548
        $dir->close();
549
550
        return true;
551
    }
552
553
    /**
554
     * Change the ownership on a file or directory.
555
     *
556
     * @param string $pathname path and name of file or directory
557
     * @param string $user     The user name or number of the file or directory. See http://us.php.net/chown
558
     *
559
     * @throws IOException if operation failed
560
     */
561
    public function chown($pathname, $user)
562
    {
563
        error_clear_last();
564
        if (false === @chown($pathname, $user)) { // FAILED.
565
            $lastError = error_get_last();
566
            $errormsg = $lastError['message'] ?? 'unknown error';
567
568
            throw new IOException("FileSystem::chown() FAILED. Cannot chown {$pathname}. User {$user} {$errormsg}");
569
        }
570
    }
571
572
    /**
573
     * Change the group on a file or directory.
574
     *
575
     * @param string $pathname path and name of file or directory
576
     * @param string $group    The group of the file or directory. See http://us.php.net/chgrp
577
     *
578
     * @throws IOException if operation failed
579
     */
580
    public function chgrp($pathname, $group)
581
    {
582
        error_clear_last();
583
        if (false === @chgrp($pathname, $group)) { // FAILED.
584
            $lastError = error_get_last();
585
            $errormsg = $lastError['message'] ?? 'unknown error';
586
587
            throw new IOException("FileSystem::chgrp() FAILED. Cannot chown {$pathname}. Group {$group} {$errormsg}");
588
        }
589
    }
590
591
    /**
592
     * Change the permissions on a file or directory.
593
     *
594
     * @param string $pathname path and name of file or directory
595
     * @param int    $mode     The mode (permissions) of the file or
596
     *                         directory. If using octal add leading
597
     *                         0. eg. 0777. Mode is affected by the
598
     *                         umask system setting.
599
     *
600
     * @throws IOException if operation failed
601
     */
602 60
    public function chmod($pathname, $mode)
603
    {
604 60
        error_clear_last();
605 60
        $str_mode = decoct($mode); // Show octal in messages.
606 60
        if (false === @chmod($pathname, $mode)) { // FAILED.
607
            $lastError = error_get_last();
608
            $errormsg = $lastError['message'] ?? 'unknown error';
609
610
            throw new IOException("FileSystem::chmod() FAILED. Cannot chmod {$pathname}. Mode {$str_mode} {$errormsg}");
611
        }
612 60
    }
613
614
    /**
615
     * Locks a file and throws an Exception if this is not possible.
616
     *
617
     * @throws IOException
618
     */
619
    public function lock(File $f)
620
    {
621
        $filename = $f->getPath();
622
        $fp = @fopen($filename, 'w');
623
        $result = @flock($fp, LOCK_EX);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of flock() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

623
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_EX);
Loading history...
624
        @fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

624
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
625
        if (!$result) {
626
            throw new IOException("Could not lock file '{$filename}'");
627
        }
628
    }
629
630
    /**
631
     * Unlocks a file and throws an IO Error if this is not possible.
632
     *
633
     * @throws IOException
634
     */
635
    public function unlock(File $f)
636
    {
637
        $filename = $f->getPath();
638
        $fp = @fopen($filename, 'w');
639
        $result = @flock($fp, LOCK_UN);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of flock() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

639
        $result = @flock(/** @scrutinizer ignore-type */ $fp, LOCK_UN);
Loading history...
640
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

640
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
641
        if (!$result) {
642
            throw new IOException("Could not unlock file '{$filename}'");
643
        }
644
    }
645
646
    /**
647
     * Delete a file.
648
     *
649
     * @param string $file path and/or name of file to delete
650
     *
651
     * @throws IOException - if an error is encountered
652
     */
653 154
    public function unlink($file)
654
    {
655 154
        error_clear_last();
656 154
        if (false === @unlink($file)) {
657
            $lastError = error_get_last();
658
            $errormsg = $lastError['message'] ?? 'unknown error';
659
            $msg = "FileSystem::unlink() FAILED. Cannot unlink '{$file}'. {$errormsg}";
660
661
            throw new IOException($msg);
662
        }
663 154
    }
664
665
    /**
666
     * Symbolically link a file to another name.
667
     *
668
     * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
669
     *
670
     * @param string $target path and/or name of file to link
671
     * @param string $link   path and/or name of link to be created
672
     *
673
     * @throws IOException
674
     */
675 15
    public function symlink($target, $link)
676
    {
677 15
        error_clear_last();
678
        // If Windows OS then symlink() will report it is not supported in
679
        // the build. Use this error instead of checking for Windows as the OS.
680
681 15
        if (false === @symlink($target, $link)) {
682
            $lastError = error_get_last();
683
            $errormsg = $lastError['message'] ?? 'unknown error';
684
            // Add error from php to end of log message.
685
            $msg = "FileSystem::Symlink() FAILED. Cannot symlink '{$target}' to '{$link}'. {$errormsg}";
686
687
            throw new IOException($msg);
688
        }
689 15
    }
690
691
    /**
692
     * Set the modification and access time on a file to the present time.
693
     *
694
     * @param string $file path and/or name of file to touch
695
     * @param int    $time
696
     *
697
     * @throws Exception
698
     */
699
    public function touch($file, $time = null)
700
    {
701
        error_clear_last();
702
        if (null === $time) {
703
            $error = @touch($file);
704
        } else {
705
            $error = @touch($file, $time);
706
        }
707
708
        if (false === $error) { // FAILED.
709
            $lastError = error_get_last();
710
            $errormsg = $lastError['message'] ?? 'unknown error';
711
            // Add error from php to end of log message.
712
            $msg = "FileSystem::touch() FAILED. Cannot touch '{$file}'. {$errormsg}";
713
714
            throw new Exception($msg);
715
        }
716
    }
717
718
    /**
719
     * Delete an empty directory OR a directory and all of its contents.
720
     *
721
     * @param string $dir      path and/or name of directory to delete
722
     * @param bool   $children False: don't delete directory contents.
723
     *                         True: delete directory contents.
724
     *
725
     * @throws Exception
726
     */
727 165
    public function rmdir($dir, $children = false)
728
    {
729 165
        error_clear_last();
730
731
        // If children=FALSE only delete dir if empty.
732 165
        if (false === $children) {
733 164
            if (false === @rmdir($dir)) { // FAILED.
734
                $lastError = error_get_last();
735
                $errormsg = $lastError['message'] ?? 'unknown error';
736
                // Add error from php to end of log message.
737
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir {$dir}. {$errormsg}";
738
739 164
                throw new Exception($msg);
740
            }
741
        } else { // delete contents and dir.
742 2
            $handle = @opendir($dir);
743 2
            $lastError = error_get_last();
744 2
            $errormsg = $lastError['message'] ?? 'unknown error';
745
746 2
            if (false === $handle) { // Error.
747
                $msg = "FileSystem::rmdir() FAILED. Cannot opendir() {$dir}. {$errormsg}";
748
749
                throw new Exception($msg);
750
            }
751
            // Read from handle.
752
            // Don't error on readdir().
753 2
            while (false !== ($entry = @readdir($handle))) {
754 2
                if ('.' != $entry && '..' != $entry) {
755
                    // Only add / if it isn't already the last char.
756
                    // This ONLY serves the purpose of making the Logger
757
                    // output look nice:)
758
759
                    if (0 === strpos(strrev($dir), DIRECTORY_SEPARATOR)) { // there is a /
760
                        $next_entry = $dir . $entry;
761
                    } else { // no /
762
                        $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
763
                    }
764
765
                    // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
766
                    // returns 0. So use == not ===.
767
768
                    // Don't error on is_dir()
769
                    if (false == @is_dir($next_entry)) { // Is file.
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
770
                        try {
771
                            $this->unlink($next_entry); // Delete.
772
                        } catch (Exception $e) {
773
                            $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() {$next_entry}. " . $e->getMessage();
774
775
                            throw new Exception($msg);
776
                        }
777
                    } else { // Is directory.
778
                        try {
779
                            $this->rmdir($next_entry, true); // Delete
780
                        } catch (Exception $e) {
781
                            $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() {$next_entry}. " . $e->getMessage();
782
783
                            throw new Exception($msg);
784
                        }
785
                    }
786
                }
787
            }
788
789
            // Don't error on closedir()
790 2
            @closedir($handle);
0 ignored issues
show
Bug introduced by
Are you sure the usage of closedir($handle) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
791
792 2
            error_clear_last();
793 2
            if (false === @rmdir($dir)) { // FAILED.
794
                // Add error from php to end of log message.
795
                $lastError = error_get_last();
796
                $errormsg = $lastError['message'] ?? 'unknown error';
797
                $msg = "FileSystem::rmdir() FAILED. Cannot rmdir {$dir}. {$errormsg}";
798
799
                throw new Exception($msg);
800
            }
801
        }
802 165
    }
803
804
    /**
805
     * Set the umask for file and directory creation.
806
     *
807
     * @param int $mode
808
     *
809
     * @throws Exception
810
     *
811
     * @internal param Int $mode . Permissions usually in ocatal. Use leading 0 for
812
     *                    octal. Number between 0 and 0777.
813
     */
814
    public function umask($mode)
815
    {
816
        error_clear_last();
817
        // CONSIDERME:
818
        // Throw a warning if mode is 0. PHP converts illegal octal numbers to
819
        // 0 so 0 might not be what the user intended.
820
821
        $str_mode = decoct($mode); // Show octal in messages.
822
823
        if (false === @umask($mode)) { // FAILED.
824
            $lastError = error_get_last();
825
            $errormsg = $lastError['message'] ?? 'unknown error';
826
            // Add error from php to end of log message.
827
            $msg = "FileSystem::Umask() FAILED. Value {$str_mode}. {$errormsg}";
828
829
            throw new Exception($msg);
830
        }
831
    }
832
833
    /**
834
     * Compare the modified time of two files.
835
     *
836
     * @param string $file1 path and name of file1
837
     * @param string $file2 path and name of file2
838
     *
839
     * @throws exception - if cannot get modified time of either file
840
     *
841
     * @return int 1 if file1 is newer.
842
     *             -1 if file2 is newer.
843
     *             0 if files have the same time.
844
     *             Err object on failure.
845
     */
846
    public function compareMTimes($file1, $file2)
847
    {
848
        $mtime1 = filemtime($file1);
849
        $mtime2 = filemtime($file2);
850
851
        if (false === $mtime1) { // FAILED. Log and return err.
852
            // Add error from php to end of log message.
853
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of {$file1}.";
854
855
            throw new Exception($msg);
856
        }
857
858
        if (false === $mtime2) { // FAILED. Log and return err.
859
            // Add error from php to end of log message.
860
            $msg = "FileSystem::compareMTimes() FAILED. Cannot can not get modified time of {$file2}.";
861
862
            throw new Exception($msg);
863
        }
864
865
        // Worked. Log and return compare.
866
        // Compare mtimes.
867
        if ($mtime1 == $mtime2) {
868
            return 0;
869
        }
870
871
        return ($mtime1 < $mtime2) ? -1 : 1; // end compare
872
    }
873
874
    /**
875
     * returns the contents of a directory in an array.
876
     *
877
     * @return string[]
878
     */
879 216
    public function listContents(File $f)
880
    {
881 216
        return array_map('strval', array_keys(
882 216
            iterator_to_array(
883 216
                new FilesystemIterator(
884 216
                    $f->getAbsolutePath(),
885 216
                    FilesystemIterator::KEY_AS_FILENAME
886
                )
887
            )
888
        ));
889
    }
890
891
    /**
892
     * PHP implementation of the 'which' command.
893
     *
894
     * Used to retrieve/determine the full path for a command.
895
     *
896
     * @param string $executable Executable file to search for
897
     * @param mixed  $fallback   default to fallback to
898
     *
899
     * @return string full path for the specified executable/command
900
     */
901 8
    public function which($executable, $fallback = false)
902
    {
903 8
        if (is_string($executable)) {
0 ignored issues
show
introduced by
The condition is_string($executable) is always true.
Loading history...
904 7
            if ('' === trim($executable)) {
905 7
                return $fallback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fallback could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
906
            }
907
        } else {
908 1
            return $fallback;
909
        }
910 7
        if (basename($executable) === $executable) {
911 7
            $path = getenv('PATH');
912
        } else {
913
            $path = dirname($executable);
914
        }
915 7
        $dirSeparator = $this->getSeparator();
916 7
        $pathSeparator = $this->getPathSeparator();
917 7
        $elements = explode($pathSeparator, $path);
918 7
        $amount = count($elements);
919 7
        $fstype = Phing::getProperty('host.fstype');
920
921 7
        switch ($fstype) {
922 7
            case 'UNIX':
923 7
                for ($count = 0; $count < $amount; ++$count) {
924 7
                    $file = $elements[$count] . $dirSeparator . $executable;
925 7
                    if (file_exists($file) && is_executable($file)) {
926 4
                        return $file;
927
                    }
928
                }
929
930 3
                break;
931
932
            case 'WINDOWS':
933
                $exts = getenv('PATHEXT');
934
                if (false === $exts) {
935
                    $exts = ['.exe', '.bat', '.cmd', '.com'];
936
                } else {
937
                    $exts = explode($pathSeparator, $exts);
938
                }
939
                for ($count = 0; $count < $amount; ++$count) {
940
                    foreach ($exts as $ext) {
941
                        $file = $elements[$count] . $dirSeparator . $executable . $ext;
942
                        // Not all of the extensions above need to be set executable on Windows for them to be executed.
943
                        // I'm sure there's a joke here somewhere.
944
                        if (file_exists($file)) {
945
                            return $file;
946
                        }
947
                    }
948
                }
949
950
                break;
951
        }
952 3
        if (file_exists($executable) && is_executable($executable)) {
953
            return $executable;
954
        }
955
956 3
        return $fallback;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fallback could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
957
    }
958
}
959